# S3 requirements

> The exact object and bucket permissions, the startup compatibility probe and its three conditional-write guarantees, custom endpoints and path style, and what is not required.

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

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

The bucket must exist. slivingdoc does not create or configure it.

## Permissions

The server needs these permissions, and no more:

| Scope                               | Actions                                                                                                                                                                                |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| On the objects (`arn:...:bucket/*`) | `s3:GetObject`, `s3:PutObject`, `s3:DeleteObject`, `s3:CreateMultipartUpload`, `s3:UploadPart`, `s3:CompleteMultipartUpload`, `s3:AbortMultipartUpload`, `s3:ListMultipartUploadParts` |
| On the bucket (`arn:...:bucket`)    | `s3:ListBucket`, `s3:ListBucketMultipartUploads`                                                                                                                                       |

Object get, put, delete, multipart upload, and multipart abort are what
normal operation needs below the configured prefix. The bucket listing is
what the cleanup pass needs, and it can be restricted to that same
prefix.

The reusable Terraform module in the tool repository grants exactly this
policy. It provisions the private bucket together with a least-privilege
IAM user whose inline policy is scoped to that one bucket: object
get, put and delete, multipart upload and abort, and the bucket listing.
The user cannot manage the bucket, IAM, or any other AWS resource. See
[Set up a bucket](/docs/guides/bucket/) for how to apply it.

## The compatibility probe

Before it serves any call, the server runs a disposable compatibility
probe below the configured prefix. The probe proves that the store
enforces the three guarantees the publication protocol depends on:

| Guarantee               | Proved by                                |
| ----------------------- | ---------------------------------------- |
| Conditional creation    | `If-None-Match: *` on a new object       |
| Conditional replacement | `If-Match: <etag>` on an existing object |
| Read-after-write        | Reading back what was just written       |

It runs against a unique `probe/<uuidv7>` key below the prefix, and the
sequence is exact: it conditionally creates known bytes, proves that a
second create fails, reads the bytes and the ETag, proves that a wrong
ETag fails without mutating anything, replaces with the correct ETag, and
reads the replacement immediately. It deletes the probe key on success
and after any recoverable failure.

A store that fails the probe is refused at startup with the
`INCOMPATIBLE_STORE` category. When the failure is an operational error
rather than a missing capability, the diagnostic names the underlying
reason — the S3 `AccessDenied` or `InvalidAccessKeyId` error, for example
— while the probe key and any secret stay redacted. `pull` and `commit`
run this same startup sequence, so an expired short-lived login surfaces
as a redacted startup refusal rather than a mid-operation error.

> **Note:** `HTTP 412` from a conditional write is the ordinary,
> expected precondition failure: another writer won the race. A timeout or
> connection error after the request bytes were sent is an _ambiguous_
> result, not a precondition failure, and is resolved by rereading
> `current`. See [Storage model](/docs/concepts/storage-model/).

## Custom endpoints and path style

A custom S3-compatible service is configured with an absolute `http` or
`https` `--endpoint` (environment `AWS_ENDPOINT_URL_S3`). The URL carries
no user information, query, or fragment — a URL with user information is
refused outright, so a secret cannot echo into a diagnostic.
Configuration lowercases the scheme and host, removes a trailing slash,
and preserves a non-root path, and that normalised value becomes part of
the private-state storage identity.

The server always uses path-style addressing for a custom endpoint.
`--path-style` (environment `SLIVINGDOC_PATH_STYLE`, default `false`)
extends path-style addressing to the default AWS endpoint as well.

## Credentials

slivingdoc has no authentication layer of its own. Credentials come from
the AWS SDK default credential chain, resolved at startup:

1. Environment variables — `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`,
   plus `AWS_SESSION_TOKEN`.
2. The shared config and credentials files (`~/.aws/credentials`,
   `~/.aws/config`), honouring `AWS_PROFILE`.
3. Ambient identity — SSO sessions, ECS and EKS task roles, and the EC2
   instance metadata service.

`--bucket`, `--prefix`, `--region`, and `--endpoint` shape _where_ the
client points, never _who it is_: no flag carries a credential.
`serve` resolves the chain once and holds the session, while each `pull`
or `commit` invocation resolves it fresh — so with short-lived STS or SSO
credentials, every invocation needs a currently valid session.
[Connect an MCP host](/docs/guides/mcp-hosts/) covers the three delivery
routes.

## Known-compatible stores

| Store     | Standing                                                                                                                                                                                        |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AWS S3    | The reference store. The Terraform module provisions a bucket and its least-privilege user.                                                                                                     |
| SeaweedFS | Proven continuously: the tool's own integration suites run against a pinned SeaweedFS testcontainer (`chrislusf/seaweedfs:4.42`), and the repository ships a compose file for local evaluation. |

Any other S3-compatible store is a question the probe answers at startup.
[Set up a bucket](/docs/guides/bucket/) walks through AWS, a hosted
alternative, and the local SeaweedFS container.

> **Warning:** The tool's automated test suites never touch AWS. They use
> their own S3 test containers, so "it passes CI" is a statement about
> SeaweedFS, and your own store still has to pass the probe.

## What is not required

- **Bucket versioning.** Not required, and not a prerequisite of the
  publication protocol.
- **Replication, object lock, lifecycle rules, and external backups.**
  These are deployment recovery policies. They complement slivingdoc;
  choose them for your own recovery requirements.
- **A bare repository or a `.git` directory in the bucket.** There is
  none, and the server never uses a bucket listing to decide which state
  is current.

If you do enable versioning, note that deleted object versions can be
retained, and your lifecycle rules decide when those noncurrent versions
stop consuming storage.

## Next

- [Set up a bucket](/docs/guides/bucket/) — AWS, a hosted store, or a
  local container.
- [Storage model](/docs/concepts/storage-model/) — what those permissions
  are actually used for.
- [Configuration](/docs/reference/configuration/) — `--bucket`,
  `--prefix`, `--region`, `--endpoint`, `--path-style`.
