Bunny write credential for sow-module CI #61
Notifications
Due Date
No due date set.
Depends on
#58 Does sow-module CI know the full artifact set at release time?
ShadowsOverWestgate/sow-tools
Reference: ShadowsOverWestgate/sow-tools#61
Reference in New Issue
Block a user
Question
How does sow-module CI get a Bunny write credential, and is handing it one acceptable?
Part of #54. sow-module CI now assembles the manifest and uploads it, which it has never done — it has no zone credential today.
sow-assets-manifestalready publishes to the depot, so a path exists to copy rather than invent.Settle:
manifests/; blobs are written by other repos entirely, so a write-scoped-to-prefix credential may be possible.Verification sweep — premise correction
"It has no zone credential today" is wrong as written.
sow-module/.gitea/workflows/release.yml:45-46already passesBUNNY_STORAGE_ZONE/BUNNY_STORAGE_PASSWORDfrom Actions secrets, and fails closed at line 51 if the password is unset — the release job already writes to the depot zone. #58's "sow-module CI already holds Bunny write credentials" refers to that.The live question is narrower, and #56 already fixes its shape: NWSync data lives in a separate zone from the assets depot, read from
NWSYNC_STORAGE_ZONE/NWSYNC_STORAGE_PASSWORDwith the host falling back toBUNNY_STORAGE_HOST. So what remains to settle:manifests/only for sow-module, since blobs are written by the two emitter repos. Unchanged, still open.BUNNY_STORAGE_PASSWORD, so "can it hold a secret" is yes; what is left is whether it should hold a second, wider-scoped one.Same secret is needed by the two emitter repos (
sow-assets-manifest,sow-topdata) per #65, so decide the scoping once here for all three.Resolved: the credential already exists on sow-module; the real question was the ordering
Where it comes from — already provisioned, separate from the depot
sow-module's Actions secrets today (names only, read viatea api repos/ShadowsOverWestgate/sow-module/actions/secrets):BUNNY_STORAGE_ZONE,BUNNY_STORAGE_PASSWORD— the depot, in use since the repo started publishing.NWSYNC_STORAGE_ZONE,NWSYNC_STORAGE_PASSWORD— the NWSync zone. Already present. Nothing to invent, nothing to request.This matches #56: nwsync data is a separate Bunny zone from the assets depot, so the depot key is not reused and a compromise of one does not reach the other. Only the write key is ever used; there is no read key anywhere in this path, because reads go through the public CDN base.
Prefix scoping is not possible, and does not need to be
Bunny Storage authenticates with a single per-zone
AccessKey— the zone's full password, optionally plus a read-only password. There is no path or prefix ACL, so "write scoped tomanifests/only" cannot be expressed. Recorded consistently across the repo's own Bunny work (sow-tools/docs/superpowers/plans/2026-07-04-crucible-depot-core.md:23,sow-platform/docs/superpowers/plans/2026-06-18-gitea-auth-depot-proxy.md:19):PUTwithAccessKey: <password>and an uppercaseChecksumheader, existence by GET on the same URL.Handing
sow-moduleCI a zone-wide write key is acceptable anyway, because the separate zone is the scope. The blast radius is NWSync data only: content-addressed, regenerable, no secrets and no player data in it. Worst case is re-emit and re-assemble. That is a materially smaller grant than the depot key the same workflow already carries.The runner can hold it
runs-on: nix-docker(.gitea/workflows/release.yml:15) — the same runner that already holdsBUNNY_STORAGE_PASSWORDand publishes to the depot. No new trust boundary, no new host.Failure posture — assemble runs before publish, not after
This overrides #65's "a new sow-module step after publish+promote".
The bar this ticket set is that a module release whose manifest never landed must not present itself as publishable. Placing assemble after publish cannot meet it:
publish-module-release.shputsSHA256SUMSand thenhaks.json(:71-72), andhaks.jsonis the publication marker — the release is already visible and pinnable before assemble has run, so an upload failure leaves a published release with no manifest and nothing to roll back.Assemble needs nothing that publish produces. Its inputs are the hak artifact keys and the TLK key (already in
.cache/fromfetch-upstream-manifests.sh, per #63) and--group-idfrom the tag shape. So the order is: fetch upstream manifests → build → assemble → publish → promote, with the manifest SHA-1 written intohaks.jsonbesidehak_releaseandtopdata_release(schema stays 1; the proxy ignores unknown fields). One catalog write, no window, and a failed assemble aborts the release before anything is publishable.A failed assemble may leave an uploaded manifest orphaned in the zone. Harmless and uncollected, exactly as #60 settled for orphan blobs — nothing names it, and the next run rewrites the same content-addressed path.
Still outstanding, and it is not here
The two emitter repos have no NWSync credential:
sow-assets-manifestandsow-topdatahold onlyBUNNY_STORAGE_ZONE/BUNNY_STORAGE_PASSWORD.emitcannot upload from either untilNWSYNC_STORAGE_*is added to both, and per #65 all three repos must also passBUNNY_STORAGE_HOST, because Crucible has no default host (internal/depot/config.go:42) while every repo'sdepot-lib.shdoes. Split out as its own ticket.Back-reference from the Bunny credential audit this surfaced: sow-module#52 is fixed in sow-module#53.
One correction to that ticket's premise, relevant to the rest of the audit:
BUNNY_API_KEYis set org-wide, not per-repo, so it resolves in every repo's workflow and there was no missing secret. The real gap was posture —depot-lib.shtreats both an unset and a rejected key as a skip, so a failed purge of the mutablechannels.jsonlooks like a green release. Since the key is org-wide rather than repo-scoped, a wrong-account key is a live failure mode, not a hypothetical.That half is upstream in the vendored file, filed as ShadowsOverWestgate/sow-topdata#180, covering sow-topdata, sow-module, and sow-assets-manifest.