On this page

Object store

Where does fleet state actually live, and who is allowed to write it? In one canonical object plane, selected by STADO_CONFIG, where every write is authorized by matching its key against a namespace's per-prefix action policies.

What it is

The object store is the single durable plane behind the fleet: queue state, capacity broadcasts, control flags, the registry document, autonomy records, and release artifacts all live under fixed stado:// prefixes. The selected STADO_CONFIG names the primary backend, replication, and release origin; coordinator and agents use the same canonical prefixes regardless of backend. There is no provider-derived storage fallback or direct client bucket path.

Writes cross an authenticated object boundary. The gateway resolves the request's namespace from configuration, checks that the namespace's policy allows this action on this key or list prefix, and then compares the bearer against the namespace's Skarbiec-held token in constant time. A key no policy covers — or an action the prefix does not allow — is refused with 401 {"error":"unauthorized or non-immutable release write"}, a sentence that names neither the namespace, the prefix, nor the grant.

That refusal shape has bitten twice, the same way both times. The whole autonomy layer originally wrote under autonomy/, which no namespace policy declared, so placement, resource reconciliation, cost reports, and service reconciliation had all been refused with that 401 since 2026-08-19 — while reads kept working, so optimize status still printed a forecast. The fix was to root every autonomy object under state/ (state/autonomy/...), the same move state/host_silence/ had already made after the same 401 for the same reason. If you add a new prefix, root it under a namespace some policy declares.

Who declares it

Operators, in the deployment configuration: the backend, the namespaces, and each namespace's prefix/action allowlist. Writers are the coordinator, agents, the autonomy layer, and authenticated submitters — each within its namespace's policy. Reads under stado://releases/ are the one public, bearer-free surface; publication there is authenticated and create-only.

Who observes it

Everything. The scheduler reads the queue window, the reaper reads capacity and heartbeats, resolvers read registry snapshots, stado optimize status reads autonomy records. Ordinary storage clients read the configured primary first and may retry a primary error against the mirrored backup; a successful primary absent answer does not fall through. The object API server and other authority-sensitive paths instead use primary-only readers so stale replica state cannot become an authority answer. In both modes, writes commit to the primary and are then mirrored, and the backup is never promoted automatically. A readable forecast still proves nothing about the write path; stado doctor checks that the configured backend can be constructed, not that a later mutation completed.

Canonical object uploads

The CLI and graphical clients use the same authenticated object API. For a namespace declared in your deployment, upload and download use:

stado storage put stado://example/artifacts/example.bin ./example.bin
stado storage get stado://example/artifacts/example.bin ./downloaded.bin

Replace example with the configured namespace. Larger uploads use the existing chunk-and-compose protocol and SHA-256 verification. The public proxy buffers each incoming upload request before forwarding it through the Node runtime, rather than forwarding the request's live body stream. The release proxy, object proxy and chunk composition share the public-DNS origin connector, including the chunk reads, final write and chunk cleanup. It preserves the configured hostname, TLS verification, bearer and conditional headers; a missing public DNS answer is a request failure, never object absence.

A failed request reports error: "object request failed" together with the HTTP method, object uri, failed operation, and original cause. Failure to read the incoming body returns HTTP 400. Failure while forwarding a request or writing the composed object returns HTTP 503; it does not claim that the host is unreachable merely because an upload failed. The server log retains the same details and the original error.

An HTTP 200 from a read does not prove uploads work. Keep the completed write receipt and compare the downloaded bytes with the uploaded file.

Where it lives

In the backend the selected STADO_CONFIG names — Azure Blob and local storage for the Azure and local outage profiles respectively. The canonical layout (queue/, running/, status/, leases/, capacity/, control/, registry.json, state/..., and the rest) is tabulated in the architecture doc and is identical across backends. system/storage-layout.json is the versioned layout marker.

Commands

stado config show
stado config validate
stado doctor
stado storage
stado artifact list

stado storage moves queue state between backends (the billing-outage migration path). Flags are in cli.

For the specialized source-preserving handoff between the fixed co-located local roots, use stado host storage-root-reconcile; its run, resume, status, rollback, and finalize boundaries are documented in disaster recovery.

Not to be confused with

  • The registry — one versioned document inside the store, not the store itself.
  • The release channel — immutable objects under stado://releases/, publicly readable through https://stado.wisent.com/api/release/object; copying release objects into a second "public" store is a broken release channel.
  • Skarbiec — the credential store. The object gateway reads its verifier tokens from Skarbiec; the object store itself holds no provider or product secret material.

Source: this website