2.5 KiB
Internal: Storage
Purpose
Explain the object-store interface and S3 implementation used by Narratio. Remote key layout and lifecycle belong in Operations, while operator-selected storage fields and credential mechanisms belong in Configuration.
Primary Contract
storage.ObjectStore interface:
List(ctx, prefix)Read(ctx, key)returns an object body and the generation observed with itDownload(ctx, key, localPath)Upload(ctx, localPath, key, opts)UploadConditional(ctx, source, key, opts, condition)Exists(ctx, key)
Key invariant:
- callers pass full bucket-relative keys;
- storage implementations do not infer campaign/session/run prefixes.
ReadObjectBounded is the shared mechanism for small control objects. It opens
one object version, returns the metadata observed with that body, rejects an
oversized known size before transfer, and still performs a context-aware
limit-plus-one read. It closes the body on every exit. Callers own the policy
limit and add the control-object category to errors; this helper is not used for
large artifact payloads.
Composition
NewObjectStoreFromConfig constructs the S3-backed implementation from
resolved configuration. The application loads configured filesystem secrets
before calling it. The storage adapter consumes already-resolved values; it does
not own discovery, defaults, or configuration validation.
S3 Backend Behavior
- normalizes object keys.
Listpaginates and returns normalizedObjectInfo.- A truncated S3 listing must supply a new, non-empty continuation token; otherwise listing fails with bucket and prefix context instead of looping.
Downloadwrites local files with parent directory creation.Uploadstreams local file and returns remote metadata.Readbinds a returned body to its S3 ETag.UploadConditionalmaps an ETag match or absence precondition directly to the provider request and reports a failed precondition without performing a local check-then-write replacement.Existsmaps not-found responses tofalse.
Invariants
- storage layer is stateless regarding manifest/stage progression.
- bounded reads never retain more than the caller's limit plus one byte and do not replace owner-specific size policy.
- publish ordering semantics are owned by stage/app code, not storage adapters.
Implementation And Tests
- Contract and S3 adapter:
internal/adapters/storage - Composition:
internal/app/object_store.go - Tests:
internal/adapters/storage/*_test.go,internal/app/object_store_test.go