# Access audit

CipherFS records logical access and actual commits in Core, and separate delivery observations at HTTP, local API, S3 and FUSE boundaries. The desktop **Access** panel queries by FileId or DeviceId and shows retention coverage, writer health, backlog and loss. The current entry integrations are implemented; the default mode and boundary of each entry are exposed by operational status.

## Modes and boundaries

| Entry | Default | Identity and observed boundary |
| --- | --- | --- |
| Core, Rust, CLI, C/Python/Go, desktop | off | Local process; Core operation and return/commit boundaries |
| HTTP JSON | required | Verified session public identity; Core plus response-body handoff |
| S3 | required | Registered identity after SigV4 verification; Core plus plaintext body handoff |
| Private local API | off | Verified protected endpoint credential public identity; Core plus body handoff |
| Standalone FUSE | off | Actual callback UID/GID/PID when supplied; Core plus reply handoff |
| Daemon mount | best effort | Same OS callback observations |
| Embedded background work | off | System identity and maintenance facts |
| Daemon background work | best effort | System identity and maintenance facts |

`off` makes no audit guarantee. `best_effort` attempts every event and reports loss; gap counts are lower bounds across crashes. `required` rejects business access when intent cannot persist and keeps commits atomic with audit facts. A failed finish can follow a proven business commit: inspect the structured known receipt before retrying a mutation. HTTP returns `AUDIT_UNAVAILABLE` with status 503; bindings use error class 32. A writer fault affects required readiness while operational diagnostics remain available.

A denial that races the signing owner's shutdown keeps its unauthorized response and enters the existing bounded denial summary. Required audit failures with a live owner still return unavailable; this exception admits no business access.

Actors never retain session tokens, passwords, headers, paths or body contents. Unknown authentication remains an unknown actor and does not resolve a requested file. OS observations do not authenticate a named executable. Counts at different layers must not be added. FUSE delivery remains unknown because its reply has no kernel receipt; no delivery record proves consumption by a person.

## Query and configure

Use the normal password source or an already-running local owner. IDs are canonical public identities, not paths.

```sh
cipherfs audit status --vault "$VAULT_PATH"
cipherfs audit config --vault "$VAULT_PATH" > audit-config.json
cipherfs audit file "$FILE_ID" --vault "$VAULT_PATH" --limit-count 100
cipherfs audit device "$DEVICE_ID" --vault "$VAULT_PATH" --limit-count 100
cipherfs audit set-config --vault "$VAULT_PATH" --input-file-path audit-config.json
```

Edit the complete configuration before `set-config`. `mode_overrides` maps entry names such as `http`, `bindings` or `daemon_mount` to `off`, `best_effort` or `required`; omitted entries retain the defaults above. Positive finite budgets include event/queue/spool/segment sizes, flush/shutdown timeouts and independent `audit_retention_days`. Invalid budgets or a conflicting active operation reject the update.

Move observations and transaction commit facts each have a finite limit of `min(4096, max(1, queue_max_bytes / 256))` per operation or transaction. Split larger required batches: exceeding the transaction fact limit rolls that transaction back. Best effort continues with visible loss and omits a single target from an incomplete summary.

Authenticated JSON routes are relative to `/api/v1`:

| Method and route | Operation |
| --- | --- |
| GET `/audit/status` | Operational status and declared entry coverage |
| GET or PUT `/audit/config` | Read or replace the complete local configuration |
| POST `/audit/query` | File/device query or continuation |
| POST `/audit/flush` | Seal pending events to local encrypted spool |
| DELETE `/audit/cache` | Clear only the local encrypted query cache |
| POST `/audit/cleanup` | Explicit bounded cleanup with store/device/limit selection |

A query body has `{"op":"query","query":{"file_id":"<FileId>","limit_count":100}}`; use `device_id` for a device query. Optional time filters use `from_epoch_ms` and `through_epoch_ms`; `result` is a finite result name. To continue, submit `{"op":"page","cursor":<next>,"limit_count":100}` using the returned `next` object. CLI `audit page` takes `--snapshot-id`, `--offset-count` and `--limit-count`.

Every source object is authenticated before filtering. Conflicting sequence objects, internal gaps, invalid signatures and exhausted budgets return errors. Coverage reports missing retained prefixes and always leaves `remote_tail_confirmed` false. Pages come from an encrypted local snapshot, capped at eight snapshots/64 MiB and five minutes; expiration or cache clearing requires a new query. The independent Python reader verifies the public `CFSAUD01` vector using the epoch/device key domain.

## Storage and retention

Audit objects use store prefix `a/` and never enter the shared index or ordinary content GC. The private spool contains final encrypted, signed segments. All configured stores must positively confirm an object before the local spool is removed. Seal revokes key copies; unfinished attempts recover as interrupted/unknown, and shutdown timeout reports pending work.

After stopping admission, shutdown drains live attempts, queued events and deferred returns. Once all three are empty, it resolves orphaned durable starts in bounded batches within the existing shutdown deadline, using the same `OperationRecovered` events as startup recovery. These records preserve proven commitment and mark result and delivery as unknown; a lost best-effort completion never becomes a successful operation. Live attempts remain pending and can still cause shutdown to fail.

```sh
cipherfs audit cleanup --vault "$VAULT_PATH" --store-id "$STORE_ID" --device-id "$DEVICE_ID" --limit-count 128
```

Cleanup requires the backend's real delete permission and protects unpublished local segments. Minimum retention starts no earlier than local first observation and the latest authenticated segment/event time; a future time delays deletion. Changing file retention never deletes audit history. A successful deletion count means positive backend acknowledgment, not proof that physical provider storage was reclaimed.

The [ADR](adr/006-access-audit.md) records the design and [FORMAT section 11](FORMAT.md#111-independent-access-audit-stream) specifies the container, strict payload and local rows. Access bypassing these instrumented boundaries is outside the log.
