Provoz
Dokumentace/Provoz serveru
Provoz12 min čtení

Operations

Zdrojová dokumentace je zatím dostupná v angličtině. Ovládání čtečky odpovídá zvolenému jazyku.

Na této stránce

For launch preparation, use the production checklist. For incidents, use troubleshooting; for planned credential and certificate changes, use certificates and credentials. To set up, grow or upgrade a deployment without scribe-ctl, see run Scribe.

Layout and ownership

text
<data-dir>/SCRIBE_FORMAT     descriptor and versions (required feature auth-ownership-v1 once auth-init ran)
<data-dir>/LOCK              exclusive OS lock held by serve or any maintenance command
<data-dir>/catalog.redb
<data-dir>/auth-state.redb   workspace and operation ownership, newest auth revision seen (only after auth-init)
<data-dir>/repos/<id>/       metadata.redb, objects/, SCRIBE_FORMAT
<log-dir>/                   disposable diagnostics (daily files, bounded retention)
<runtime-dir>/               disposable, private: scribe-server.pid, listen, shutdown-request

Authoritative data, logs, and runtime files are separate directories. init refuses non-empty directories; serve refuses directories without a recognized descriptor; no command uses the current directory implicitly. The data-root, backup, and checkpoint directories Scribe creates are private (0700 on Unix). Every runtime directory (scribe-server, scribe-auth, scribe-gateway, scribe-ctl) must not be writable by group or other accounts, because its files stop the service and request backups; startup refuses an existing one that they can write. The auth service has its own data directory with the same one-owner rule. Its password pepper and revision witness file live outside that directory and outside its backups; see rollback protection.

Routine health checks

The serving process performs online object integrity checking by default, at a bounded rate while foreground work is idle. Use scribe-server integrity-status --runtime-dir <runtime> for saved coverage and findings. This verifies indexed object bytes; the broader offline checks below remain necessary for metadata and pack-inventory consistency.

scribe-server doctor --data-dir <data> (alias scan) is the complete read-only check: it takes the ownership lock, so it cannot run beside the server, examines metadata consistency and every pack, and re-hashes every object in parallel. Schedule it before each backup and after any unclean shutdown; --quick skips chunk hashing for a faster structural pass. Exit 0 healthy, 1 problems (listed, never repaired), 2 could not run.

scribe-server usage --data-dir <data> (alias df) reports per-repository counts and sizes, the root total, and the filesystem's free space. It is the first look when a disk fills up and the way to size a backup destination.

doctor also checks the commit chain, operation records, workspace ownership and lock indexes, retirement records, active workspace counts, live and historical manifests, both collision indexes, change_paths against history, recorded move predecessors, the completeness of file_lineage, and packs end to end. Interrupted-publication leftovers are warnings. --quick checks headers and lengths and hashes structured objects while skipping chunk hashing.

verify is the single-threaded structural alternative; --full hashes all objects. Both checkers preserve staging leftovers, pack tails, uncommitted packs, and database files, including repositories outside a --repo selection. If a database requires recovery, they report it. Normal server startup performs database recovery; stop it before rerunning the checker. Neither command repairs data.

For damaged or missing object bytes, use scribe-server repair-objects with a verified donor backup or offline restored root. Preview is the default; --apply preserves current history and retains the original store as evidence. No automatic repair occurs.

Index repair is scribe-server rebuild-locator --data-dir <data> [--repo NAME]. Run it when doctor reports pack entries that are not indexed or a repository's objects/locator.redb is lost or unreadable. It takes the ownership lock, re-hashes every pack entry and loose file, indexes only bytes that match their identity, replaces the locator atomically, then verifies the repository structurally. Exit 0 when everything was indexed and verified, 1 when entries were skipped or problems remain (both are listed).

backup also opens the root for inspection only. It never truncates a pack tail, removes an uncommitted pack, or deletes an interrupted repository creation on the source: it copies the bytes that are there and reports those leftovers, so the copy matches what a doctor run of the same moment describes. The restored server performs the ordinary startup repair on its own copy.

Remote workspaces and locks

Use workspace administration to list or retire registrations from a second computer and to force-unlock without a checkout. These client operations run while the server is serving, directly or through a gateway. Owners with read access can list and retire their own registrations; listing or retiring other owners' registrations and force-unlocking require Maintainer or namespace-administrator authority. Without auth, any connected client can perform these operations. Auth-administrator credentials alone do not grant storage access. Doctor, backup/restore, repair, and migration remain server-local operations under the exclusive data-root lock; stop the storage server before running them.

Unfinished branch creations

Branch creation reserves its name durably before copying the source tree, so a name is never taken twice. If the client that owns the operation disappears before the branch activates — a lost .scribe/_state/, a discarded workspace — the reservation stays and scribe branch create reports that the name is already reserved. Nothing else can complete it, and there is no branch deletion.

scribe-server staging-branches --data-dir <data> lists those unfinished snapshots with their pending operation ids, staged row counts, and the change they were pinned to. Confirm with the owner that the operation will never be retried (a client that still holds the intent finishes it with scribe branch --continue), then release the name:

sh
scribe-server staging-branches --data-dir <data>
scribe-server staging-branches --data-dir <data> --repo game --abandon lighting

The command takes the ownership lock, deletes the staged rows in bounded durable transactions, and removes the reservation last, so an interrupted run is simply repeated. Active branches are history: they are never listed and never removed.

Configuration precedence

Flag, then SCRIBE_LOG (log filter) and SCRIBE_SERVER_CONFIG (config path), then the configuration file, then defaults. Relative paths in the file resolve against the file's directory; relative flag paths against the current directory. Startup logs every resolved non-secret path and limit. The full file is shown in server-usage.md.

Shutdown

SIGINT, SIGTERM, or creating <runtime-dir>/shutdown-request stops admission and closes idle connections. A request file left behind by a killed server is discarded at startup, before the run publishes <runtime-dir>/listen; once that address exists, every request written afterwards is honored, and the file is removed again when the server exits. Queued requests stop; started requests get up to drain_timeout_secs for network I/O. The deadline interrupts sockets, then the process waits for started storage jobs before releasing its root lock. A stalled disk can extend shutdown; durable commits are never cancelled.

Forced termination leaves recoverable state: acknowledged submits are durable and the next start truncates unreferenced pack tails and removes staging leftovers.

Security

Two deployment modes exist; choose one per data root.

Authenticated (recommended beyond one machine). Every Scribe endpoint (auth, storage, gateway) serves TLS 1.3 only and accepts only the hybrid post-quantum key exchange X25519MLKEM768; a peer that cannot offer it, including a TLS-terminating proxy, fails the handshake. Every caller holds its own device key and proves it with mutual TLS: clients present one-hour access certificates issued by the shared scribe-auth service, and no bearer secret crosses a Scribe connection. Every operation is authorized per repository through auth, which remains authoritative: revoking a credential or disabling a user takes effect for new admissions within 30 seconds, whatever the certificate's lifetime. Submit and lock attribution comes from the certificate identity, not from a client-supplied author. Clients pin the server's CA on first use and refuse a changed CA. Setup, roles, certificate lifetimes, and revocation are in auth.md. Keep CA keys offline; give each server its own leaf TLS certificate and its own server credential file.

Storage servers bound each source address's unauthenticated connections ([limits].max_pending_per_source, evicting the oldest pending connection when full), each user's concurrent connections ([limits].max_connections_per_principal), and each user's in-flight request bodies (a quarter of the listener's frame budget). The last two are keyed by the user id, not by a gateway that forwards for the user; see server configuration. Another user's operation id answers exactly like an unknown one, so operation ids reveal nothing across accounts.

Each storage server records the newest auth revision it has seen and refuses auth answers from behind it, so restoring the auth data directory from an older copy cannot revive revoked credentials. Restore auth only with scribe-auth restore, which starts a new auth incarnation that storage servers accept (logging auth.incarnation_changed); see rollback protection.

Plaintext (loopback or tunnel only). TCP v2 without auth has no authentication: workspace ids and author labels are self-declared, and any peer that can reach the port can submit, lock, and unlock as anyone. The server refuses non-loopback listen addresses unless allow_non_loopback = true and logs server.plaintext_exposed when it is. Acceptable only behind an authenticated tunnel, for example ssh -L 7447:127.0.0.1:7447 vcs-host on each client with listen = "tcp://127.0.0.1:7447" on the server.

Team lock policy

Track .scribe/config.toml in the repository. Its accepted locks.rules on main controls exclusive editing for every branch and can match any file type; feature-branch changes remain proposals until accepted on main. In an authenticated deployment, changing the ordered active rules requires a Maintainer; profile-only edits and formatting changes preserving those rules require Writer. Profiles in the same config are selected from each workspace's branch; see repository configuration. Inspect effective rules with scribe lock rules PATH, not the workspace copy.

Locking a file before it is added, a deletion, or a move destination is normal, so those lock rows are bounded rather than refused: a repository may hold up to 4,096 locks ([limits].max_untracked_locks) on paths that do not exist at its head, over and above locks on tracked paths. Acquisition beyond that is limit_exceeded; submitting the new file, or releasing a lock nobody needs, restores the room.

Online edit acquires required locks before opening files. During an outage, users can explicitly record local intent with edit --offline, then run reconcile when connected. This cannot reserve a file while offline. Locks belong to workspaces and do not expire on disconnect. Successful submissions release locks on submitted paths unless --keep-locks is selected; other locks remain held until explicitly released. status --remote shows ownership and policy alongside local work, and reports unavailable remote information as unknown. See lock rules and offline recovery.

Capacity planning

Size the server for retained unique content, repository metadata, and room for new uploads. History is retained indefinitely; deleting a file from the current tree does not reclaim its stored objects. Recorded operation outcomes are also kept forever by design, so that a retry arriving long after its submission resolves to its recorded result instead of applying a second time: budget under 100 bytes per accepted or rejected operation, roughly 35 MB per year at a thousand changes a day. Nothing prunes them; see on-disk format. Compression and chunk reuse can reduce physical storage, depending on the assets.

The object store caches at most 64 pack read handles per repository, evicting the least recently used handle before opening another. Active readers retain their own handles until they finish. Size process descriptor limits for these caches across active repositories, in-flight readers, databases, and sockets; historical scans do not retain a handle for every pack ever visited.

Each workspace needs space for its selected files, cached objects, temporary reconstruction files, and .scribe/_state/recovery/. Recovery retains every displaced file, including unchanged baselines, so allow space for old versions from updates and view narrowing. These files remain until explicit recovery pruning. The client caches objects up to 256 KiB plus manifests.

Acceptance is one durable database transaction taken under the repository's publication gate, and it writes roughly seven index rows per change-set entry across current state, the fold index, history, change paths, fold history, lineage, and accepted manifests. Gate hold time therefore grows linearly with the entry count, and every other submit on that repository waits for the whole transaction. The change-set format permits 4,000,000 entries in one submit, which would hold the gate for minutes; [limits].max_submit_entries caps what this server accepts and defaults to 100,000 entries, a few seconds of hold on the reference hardware and the same budget merge planning and cherry-pick ancestry already use. A submit above the cap is refused with LIMIT_EXCEEDED before the gate is taken: it consumes no change sequence number and is recorded as a rejection like any other refusal, so a retry of the same operation id resolves to the same answer. The refusal names the entry count, the cap, and this configuration key, so the user can add and submit fewer paths at a time while an administrator decides whether to raise it.

A first population of a repository from an existing project needs no configuration change: scribe import submits the tree as a sequence of changes, each within the cap, reading the cap from server_info and halving a batch the server still refuses. Point users at it rather than at this key. Raising the value remains available for a one-off import when nobody else is publishing, and for deliberately measuring acceptance at a given change-set size — see the million-paths profile below — but the raised cap applies to every submit the server takes, so lower it afterwards. The cap is read at startup and applies per submit, not per repository.

Submit latency includes durable filesystem and database flushes. Lock and workspace transactions share the repository's database writer with accepted changes, so concurrent submits can also increase coordination latency. Measure representative workloads on the intended filesystem and hardware before sizing a deployment. The testing guide explains how to run and compare performance reports.

Performance profiles

Run profiles through node tools/scenario.js --profile NAME:

  • ci: 1544 mixed source and asset files, roughly 100 MiB depending on seed; deterministic correctness gates suitable for shared CI.
  • million-paths: 1,000,000 files of 1 to 4 KiB plus medium binaries and a compressible CDC fixture; opt-in on dedicated hardware. --quick uses 100,000 small files. Both runs import every path in one submit, above the default per-submit entry cap, so the harness starts its measurement server with [limits].max_submit_entries raised to the profile's own path count — measuring acceptance at that size is the point of the profile, so it deliberately does not route the fixture through scribe import, which would measure a sequence of smaller acceptances instead.
  • scale: at least 500 GiB of generated compressible/incompressible data; requires an explicit --disk-budget-gib large enough for fixtures, history, backup/restore, and every concurrent working copy. Dedicated hardware only.

The Rust helper retains direct library measurements; JS owns the service lifecycle and baseline comparison. Performance profiles use plain TCP; authenticated TLS workflows are covered by the collaboration plans. Baselines are machine-specific. Comparison checks dataset hashes, transport, OS, architecture, run settings, durability, and build profile. The default policy also requires matching hostname and CPU count. Matching metadata alone does not establish comparable system load. Old Rust-harness baselines require explicit replacement after the dataset change.

Windows notes

Windows is the primary client platform. The client sets the read-only attribute in --readonly workspaces, retries renames and deletes on sharing violations and transient access-denied results from scanners, opens directories without delete sharing, rejects reparse points, and opens long paths with the \\?\ prefix whether or not LongPathsEnabled is set (other tools on the machine may not handle such paths). Directory-handle flushing on NTFS is best effort and reported as such. Windows-specific tests require a Windows runner; results from another platform do not establish Windows coverage.

On Windows, private directories Scribe creates (the configuration root, session and credential directories, key output directories) get a protected access list allowing only the creating account, SYSTEM and Administrators, which files created in them inherit. A private file written elsewhere gets the same list right after it is created. Reading a credential file, device key, pepper or witness refuses one that allows any other account more than reading its attributes, or whose directory lets another account delete its entries; the error names that account's SID. Directories that already exist keep their access lists, so a --config-dir or SCRIBE_CONFIG_DIR you create yourself must be user-private, as the default %LOCALAPPDATA%\Scribe is.

Zdroj docs/operations.mdVerze 2b8dcccb