# Guarantees and limits

> What slivingdoc guarantees, what stays the operator's job, how it behaves at each failure point, the notebook content rules, path security, and the credentials boundary.

Canonical URL: https://www.slivingdoc.dev/docs/concepts/guarantees/ · Version: 0.1 · Updated: 2026-09-21

Full site index: https://www.slivingdoc.dev/llms.txt

slivingdoc promises current-state durability under concurrency. It does
not promise historical recovery, and it is not a backup product. This
page is the line between the two, and the rules your content has to obey
to stay inside it.

## What slivingdoc guarantees

- Accepted state is durably indexed by one authoritative manifest.
- A failed concurrent publication cannot silently overwrite accepted
  state.
- Accepted packs are immutable.
- A successful commit is durably referenced by `current`.
- Merge conflicts do not advance remote state.
- Checkpoint and cleanup failures do not corrupt current state.

`notes_commit` returns `OK` only after the server has proved that
`current` accepted the proposal, or that a later manifest records its
publication ID. It never reports success while acceptance is uncertain.

## What the deployment owns

These are recovery policies, not hidden prerequisites of the
synchronisation algorithm. Choose them according to your own recovery
requirements:

- bucket versioning
- noncurrent-version retention
- replication
- object lock
- backup export
- recovery procedures
- storage lifecycle policy

The bucket must already exist: slivingdoc does not create or configure
it. See [Set up a bucket](/docs/guides/bucket/) and
[S3 requirements](/docs/reference/s3/).

## Failure guarantees

The publication order is load-bearing — local state, then the immutable
pack, then the conditional replacement of `current` — and each
interruption point has a defined outcome:

| Failure point                 | Guaranteed result                                                                         |
| ----------------------------- | ----------------------------------------------------------------------------------------- |
| Before pack upload            | Remote state is unchanged.                                                                |
| During pack upload            | Remote state is unchanged.                                                                |
| After pack upload, before CAS | The pack is an unreferenced proposal.                                                     |
| Pack upload response lost     | Read the unique key and validate SHA-256 and size.                                        |
| CAS precondition failure      | Another writer won. Merge and retry.                                                      |
| CAS response lost             | Read `current` and search for the publication ID.                                         |
| Merge conflict                | Remote state is unchanged. The visible directory is rewritten with the merge and markers. |
| Retry exhaustion              | The remote result is not reported as success. Caller files remain available.              |
| Checkpoint failure            | Accepted notebook state is unchanged.                                                     |
| Cleanup failure               | Accepted notebook state is unchanged. Obsolete storage remains.                           |
| Corrupt pack or checksum      | Refuse the import and report a storage-integrity error.                                   |

There is one more case that no table of interruption points can cover:
an unexpected failure _after_ local mutation has already started. There
is no bespoke recovery algorithm per interruption. Instead the operation
stops its normal work, reports `RECOVERY_FAILURE`, and tries to
reconstruct the private state and the visible directory from the
authoritative `current`. The error names the failed stage, whether remote
acceptance is known (`yes`, `no`, or `unknown`), and whether
resynchronisation succeeded. A successful repair does not turn the
anomalous call into `OK`.

> **Warning:** Recovery can replace the visible directory, because that
> directory is not a durability boundary. The error says so candidly. If
> immediate repair is impossible, the private state records that recovery
> is required, and the next call must resynchronise from `current` before
> it does any new work.

## Concurrency and scaling

One notebook has one ordered accepted state, so the `current` object is
the final serialisation point. Merge work, pack creation, uploads, and
downloads all happen concurrently; the conditional write is the only
serialised publication action.

The planning workload V1 is designed around is:

```text
100 agents
one commit per agent per minute
approximately 1 kB of new note content per commit
```

That is roughly 1.67 accepted commits per second. V1 does not promise a
fixed throughput before benchmarks run against the intended S3 service;
what the architecture does promise is the absence of quadratic
full-history uploads and long writer critical sections.

Throughput past that depends on your note layout, not on the storage:

- Changes to separate files usually merge with no caller action.
- Frequent overlapping changes to one file create semantic conflicts that
  no storage layer can remove.
- Prefer many focused files over one global append file. slivingdoc does
  not enforce agent namespaces; a convention such as one directory per
  agent is yours to set, and
  [path policies](/docs/guides/path-policies/) can enforce it.

## Notebook rules

The notebook holds directories and regular UTF-8 text files, and nothing
else:

- Files must be valid UTF-8 text without the NUL character (U+0000).
  Empty files are valid. Bytes and line endings are preserved, and text
  is not normalised.
- Symbolic links, devices, sockets, and named pipes are rejected, as are
  hard-link semantics. A hard-linked regular file is read as an
  independent path, and a rewrite does not preserve the link.
- Executable bits and other platform-specific modes are not notebook
  state; slivingdoc writes one normal file mode.
- Empty directories are not notebook state, because Git does not store
  them. Materialisation creates the parent directories it needs and
  removes obsolete empty ones.
- Binary files, and anything else that fails the text rule, are not
  notebook state.

Path names are portable by construction, so one accepted notebook works
on every supported host. Each internal path is valid UTF-8 in Unicode NFC
form, uses `/` separators, and is at most 4,096 UTF-8 bytes. Each segment
is 1 to 255 UTF-8 bytes, contains no control character and none of
`/\:*?"<>|`, and does not end in a space or a dot. The names `.`, `..`,
`.git`, Windows device names, and names that collide under Unicode case
folding are all rejected.

The request and message bounds are:

| Input              | Rule                                                                                                                                                                                                                                                                    |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MCP request `path` | Optional. Omitted or empty means the server's notebook directory, which every result reports. May begin with `~/`, which expands to the current user's home directory. The resulting absolute host path is 1 to 4,096 bytes and must be at or below the workspace root. |
| Subcommand path    | Optional, and may be relative: it resolves against the working directory before the same root rule applies.                                                                                                                                                             |
| Commit `message`   | Non-blank UTF-8 without U+0000, at most 16,384 bytes. A message of only Unicode white space is rejected; every other message is preserved byte for byte. Retained in recent internal history only.                                                                      |

Finally, one content rule exists purely to protect you from yourself: a
complete conflict-marker block — the exact lines `<<<<<<< local`,
`=======`, and `>>>>>>> remote` at column zero, in that order — is never
accepted into the notebook, even when you wrote it by hand. The rule
survives a process restart and will reject a literal marker example in a
document; change one character of one signature line and the block is
ordinary text again. See [Resolve conflicts](/docs/guides/conflicts/).

## Path security

Every request path goes through the same rules:

- canonicalise the requested path
- require it to stay below the configured workspace root
- reject symlink traversal in every existing path component
- reject special files during scans
- avoid following links during replacement and cleanup
- use private directories that callers cannot select

Filesystem access uses root-relative operations rather than a
check-then-open sequence, so an existing or newly substituted symlink
component cannot escape the root. A configured read-only or writable
entry obeys the same path rules as any other notebook path and is
validated at startup, so a malformed entry refuses startup instead of
silently protecting nothing.

The visible directory keeps its identity for the lifetime of the
workspace: slivingdoc creates it when missing and thereafter only changes
its contents — it never renames it aside, replaces it, or removes it, so
a working directory, an open editor, and a file watcher all stay valid.
Individual files are replaced through a temporary file renamed over the
target, so each file changes atomically.

> **Note:** A path policy is a guardrail at the MCP tool boundary, not a
> security boundary against the agent. The serve process holds the S3
> credentials, so an agent that can read that environment or start its own
> slivingdoc process bypasses the setting — the same model as an
> operator's sftp configuration, where the policy lives in the server
> configuration and never in the data.

## The credentials boundary

slivingdoc has no authentication layer of its own. Credentials come from
the AWS SDK default credential chain, resolved at startup, and the tool's
own flags shape only _where_ the client points — `--bucket`, `--prefix`,
`--region`, `--endpoint` — never _who it is_. No flag carries a
credential, and an `--endpoint` URL containing user information is
refused outright, so a secret cannot echo into a diagnostic.

Credentials stay inside the process. They never cross the MCP protocol:
the client sees only `notes_pull`, `notes_commit`, and their result
envelopes. A redaction layer keeps key material, S3 keys, private paths,
and Git object IDs out of every error and log line as defence in depth.
[Connect an MCP host](/docs/guides/mcp-hosts/) covers the three ways to
deliver credentials to the process.

## Outside V1

Some things are deliberately not here, and are not planned for this
version:

- byte-based checkpoint thresholds
- multi-level pack compaction
- multiple independently ordered notebook partitions
- permanent or configurable logical history
- a public backup and restore API
- an administrative status API
- remote file-editing tools
- Windows arm64 artifacts
- a public Go SDK

There is also no Git executable dependency, no public Git remote, no
writer lock or lease object in S3, and no branch, tag, ref, revision,
checkout, or rollback API. The supported programmatic interface is MCP;
the CLI mirrors the same two operations for humans.

## Next

- [Storage model](/docs/concepts/storage-model/) — the manifest, packs,
  checkpoints, and cleanup.
- [Errors](/docs/reference/errors/) — every code and reason, and what to
  do about each one.
- [S3 requirements](/docs/reference/s3/) — the permissions and guarantees
  a store must provide.
