Immutable audit archival
Nines can deliver each tenant's decision journal to an owner-configured Amazon S3 or S3-compatible bucket with Object Lock. It is disabled by default.
- The writer uses COMPLIANCE retention and verifies the object bytes, version, encryption and retention readback before persisting a delivery receipt.
- A successful upload response alone does not count as verified delivery.
- This protects the delivered object version for its retention period.
- It does not make the Nines database immutable or guarantee that an event reaches S3 during an outage. Pending delivery remains visible.
- Database administrators can alter local records and receipts; independently inspect the S3 versions and preserve separate access controls.
Prepare the destination
- Provision a dedicated bucket with versioning and Object Lock enabled.
- Choose the account, region and retention period deliberately.
- In S3 compliance mode, protected versions cannot be overwritten or deleted during retention, and retention cannot be shortened.
- Nines does not create buckets, change bucket policy, delete objects, enable legal holds or request governance bypass.
- Give the Nines server an IAM role scoped to the bucket and its tenant prefix. The bucket-level permission is
s3:GetBucketObjectLockConfiguration.- Object permissions are
s3:PutObject,s3:PutObjectRetention,s3:GetObjectands3:GetObjectRetention. - An independent verifier may also need
s3:GetObjectVersion. - Do not grant this writer object deletion, bucket policy administration or retention bypass.
- With a customer KMS key, also configure its key policy and the required
kms:GenerateDataKey/kms:Decryptpermissions.
- Object permissions are
- Use separate tenant prefixes or buckets and apply corresponding IAM boundaries.
- Nines puts the tenant ID and a destination-policy digest below the configured prefix.
- A shared process IAM identity can access every prefix permitted to it; Nines configuration does not replace IAM isolation.
Configure the tenant
Add auditArchive to the tenant in the server's TENANTS_FILE:
tenants:
- id: production
name: Production
access:
- tokenEnv: PRODUCTION_API_TOKEN
role: owner
source:
kind: prometheus
url: http://prometheus:9090
auditArchive:
bucket: example-nines-audit
region: eu-west-2
expectedBucketOwner: "123456789012"
prefix: nines/audit
retentionDays: 365
# Optional: full KMS key ARN, not an alias.
# kmsKeyID: arn:aws:kms:eu-west-2:123456789012:key/00000000-0000-0000-0000-000000000000
- Replace the example bucket/account with your own.
retentionDaysis required and accepts 1–3650.- Credentials are obtained by the AWS SDK's default credential provider chain, including workload roles and standard AWS environment/profile configuration.
- Keep access keys out of tenant YAML, manifests, CLI arguments, browser forms and model prompts. Prefer a workload IAM role.
- Archive object destinations are taken from this server-owned configuration.
- SDK S3 endpoint environment overrides do not select another archive destination.
- Set
provider: s3-compatiblewith an explicitendpointfor a deliberately configured non-AWS HTTPS gateway; HTTP is accepted only for loopback acceptance fixtures. - Compatibility tests do not certify that another service provides Amazon S3's retention guarantees.
- Restart the server after configuring the tenant.
- Persistent server deployments require PostgreSQL; configured archival is rejected with production
-memorymode. - An explicit in-memory demo can exercise the API but loses its journal, queue and receipts when the process stops.
- Persistent server deployments require PostgreSQL; configured archival is rejected with production
Open-source object storage
- External storage remains optional.
- The built-in journal and exports work without an object store.
- Use
provider: s3-compatiblefor an owner-configured non-AWS S3 endpoint. - This is a capability-based integration, not certification of every S3 implementation.
- Ceph RGW is the documented open-source target.
- Its maintained Tentacle release documents S3 compatibility, Object Lock and server-side encryption.
- Ceph requires its own cluster administration; Nines does not install it.
- The disposable container acceptance below has passed against Ceph 20.2.4 with OpenBao 2.5.0.
- Your production storage, permissions and key-management setup still require independent acceptance.
auditArchive:
provider: s3-compatible
endpoint: https://rgw.example.com
bucket: nines-audit
region: us-east-1
prefix: nines/audit
retentionDays: 365
- Set the region to the value configured for your gateway.
- Use the HTTPS S3 API endpoint with a trusted certificate. Explicit endpoints use path-style bucket addressing.
- HTTP is allowed only on loopback for acceptance fixtures.
- Loopback in a container refers to that container. Do not disable TLS verification.
- Omit
expectedBucketOwnerandkmsKeyIDin this mode; these fields select AWS account ownership and an AWS KMS key.- The default
s3provider continues to require the 12-digit expected owner. - Configure SSE-S3 on the gateway itself, including its key-management backend.
- Nines requests and verifies
AES256; missing encryption does not produce a verified receipt.
- The default
- Provision the bucket with versioning and Object Lock before configuring delivery.
- Supply a dedicated gateway access key to the Nines server through
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYusing your deployment's secret mechanism. - These names belong to the S3 credential protocol; the explicit endpoint selects your gateway.
- Keep secrets out of tenant YAML, URLs, browser forms and model inputs.
- Limit the credentials to the bucket/prefix and required S3 operations above.
- A process-wide credential chain is shared by configured tenants; use separate deployments if independent storage credentials are required.
- Supply a dedicated gateway access key to the Nines server through
- Before production use, run the provider acceptance check below with an isolated bucket/prefix and
retentionDays: 1.- The gateway must enforce conditional object creation, checksum validation, versioning, encryption and COMPLIANCE retention. An S3-compatible label alone is insufficient.
- No verification requirement is silently relaxed for non-AWS storage.
- Object Lock protects versions through the storage API.
- Storage administrators with disk, encryption-key or backup access remain outside that boundary.
- Keep storage administration separate and retain independent backups.
Inspect delivery
Open Audit log → External archive or run:
nines audit status --context production --agent
nines audit status --record RECORD_UUID --context production --agent
- The same evidence is available through
GET /api/audit/archiveandGET /api/audit/archive?record=RECORD_UUID. Owners/editors can read it; agents and viewers cannot read tenant-wide archive status. The Go client exposesAuditArchiveStatus.
- Disabled: no external destination is configured. Local journal retention and NDJSON exports still work.
- Pending: a journal record has no verified receipt for the current destination policy.
- Retrying: one or more records failed verification/delivery and will retry. Codes distinguish unavailable destinations, missing bucket locking, content mismatch and unverified retention.
- Verified: the record was read back with its exact bytes, object version, encryption and compliance retention. Its receipt contains bucket/key/version, SHA-256, retain-until and verified-at times.
- The portal reports verified objects still within retention separately from elapsed retention periods.
- Status is the last verified readback, not continuous provider monitoring.
- Use provider-side monitoring for later access, lifecycle operations and account-level changes.
- For an empty journal, zero verified records is not proof of an external write.
- The journal itself is the durable outbox.
- Conversation updates and journal insertion commit together; a background worker claims undelivered records.
- A claim expires after 90 seconds, remote work is bounded to 45 seconds, and failed deliveries back off from 10 seconds to one hour. Restarting preserves PostgreSQL work and receipts.
- Conditional object creation plus readback recovers ambiguous upload responses without overwriting an existing object. A content mismatch never authorizes replacement.
- Retention starts at the current delivery generation's first claim. Retries keep that retain-until value.
- If a pending generation expires during a prolonged outage, a later claim creates a fresh retention generation under a new object key.
- Previously verified records are not automatically renewed after their configured retention expires.
- Changing provider, bucket, owner, region, prefix, retention, encryption or explicit endpoint creates a new destination-policy digest and independently queues retained journal records for that policy.
- Previous objects and receipts remain; Nines does not delete or shorten their retention.
- Credential rotation alone does not change the destination identity.
- Archival is asynchronous.
- An archive outage does not undo or automatically block an approved SLO change.
- Inspect pending counts and errors, alert on the
intent.audit_archiveoperation outcome, and define your organization's outage/approval policy. - Configuration rollback still cannot restore consumed error budget.
Disposable Ceph acceptance
Run from the repository root:
make test-ceph
- Requires Docker, Go and OpenSSL with
req -addextsupport.- The runner uses digest-pinned official Ceph 20.2.4 and OpenBao 2.5.0 images.
- It starts a single-node Ceph cluster backed by a 2 GiB container-local file and an ephemeral OpenBao transit engine for SSE-S3.
- Ceph is capped at 1536 MiB/2 CPUs; OpenBao at 256 MiB/1 CPU.
- Allow additional disk space for image downloads.
- The runner generates credentials and a two-day fixture certificate, trusts that certificate explicitly, and publishes only RGW's TLS endpoint on a random host loopback port.
- The key service listens only inside the fixture network namespace.
- It uses no privileged container, host block devices, production credentials or existing playground configuration.
- It creates synthetic locked and unprotected buckets, then checks:
- A Nines archive upload and independent exact-version JSON/encryption readback.
- Conditional retry returning the same checksum/key/version, with exactly one stored version.
- Successful deletion of an unprotected encrypted control object.
- Denial of protected-version deletion and COMPLIANCE retention shortening.
- Nines rejecting an unlocked bucket as
bucket_not_lockedand unchanged retention after the denied mutations. - An exit trap removes only its uniquely named containers and temporary credentials/certificate directory, including on failure or interruption.
- The synthetic retained data is destroyed with the container filesystem; this is an application-protocol test, not disk-level immutability, disaster recovery, cluster HA or production key-management certification. Downloaded image caches remain.
- The fixture uses broad synthetic storage credentials and an ephemeral development key service, neither of which is a production deployment template.
Provider acceptance
- Local tests cover signed SDK requests, checksum/content/version checks, conditional retry recovery, retention rejection, leases, PostgreSQL restart, tenant isolation and backoff. They do not establish a real AWS account's IAM, KMS, region or retention configuration.
- An opt-in acceptance test can write one synthetic object retained for one day into an isolated, pre-provisioned Object Lock bucket/prefix.
- It never runs from ambient AWS credentials alone.
- Create a private YAML file containing the
auditArchivefields above, usingretentionDays: 1and a dedicated acceptance prefix; configure credentials outside that file. Then explicitly run:
TEST_AUDIT_S3_CONFIG=/private/path/acceptance.yaml \
TEST_AUDIT_ARCHIVE_CONFIRM=COMPLIANCE \
go test -v ./internal/auditarchive -run '^TestRealS3ObjectLockReadbackAndIdempotency$' -count=1
- The test does not create a bucket or modify retention policy.
- It verifies content/retention readback and that an exact retry recovers the same version.
- It leaves the synthetic object retained for one day; cleanup cannot shorten compliance retention.
- Independently inspect that version with an authorized verifier.
- A passing S3-compatible test is separate from AWS acceptance.