Bunny write credential for sow-module CI #61

Closed
opened 2026-07-27 18:51:21 +00:00 by archvillainette · 3 comments
Owner

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-manifest already publishes to the depot, so a path exists to copy rather than invent.

Settle:

  • Where the credential comes from — the same secret assets-manifest uses, or a separate one scoped to manifests only. The manifest is a few megabytes into manifests/; blobs are written by other repos entirely, so a write-scoped-to-prefix credential may be possible.
  • Whether Bunny storage supports scoping a key that narrowly, or whether write access is all-or-nothing per zone.
  • What sow-module CI's runner is and whether it can hold the secret at all.
  • The failure posture: what a release does when the upload fails. A module release whose manifest never landed must not present itself as publishable.
## 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-manifest` already publishes to the depot, so a path exists to copy rather than invent. Settle: - Where the credential comes from — the same secret assets-manifest uses, or a separate one scoped to manifests only. The manifest is a few megabytes into `manifests/`; blobs are written by other repos entirely, so a write-scoped-to-prefix credential may be possible. - Whether Bunny storage supports scoping a key that narrowly, or whether write access is all-or-nothing per zone. - What sow-module CI's runner is and whether it can hold the secret at all. - The failure posture: what a release does when the upload fails. A module release whose manifest never landed must not present itself as publishable.
archvillainette added the wayfinder:task label 2026-07-27 18:51:21 +00:00
Author
Owner

Verification sweep — premise correction

"It has no zone credential today" is wrong as written. sow-module/.gitea/workflows/release.yml:45-46 already passes BUNNY_STORAGE_ZONE / BUNNY_STORAGE_PASSWORD from 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_PASSWORD with the host falling back to BUNNY_STORAGE_HOST. So what remains to settle:

  • Does the nwsync zone get its own key, or is one key reused across both zones? (Bunny keys are per-zone, so "reuse" here means one more secret, not one shared secret.)
  • Whether Bunny can scope a write key to a prefix at all — manifests/ only for sow-module, since blobs are written by the two emitter repos. Unchanged, still open.
  • The runner question is answerable now: it is the same runner that already holds BUNNY_STORAGE_PASSWORD, so "can it hold a secret" is yes; what is left is whether it should hold a second, wider-scoped one.
  • Failure posture on upload failure — unchanged, still the substance of this ticket. Note #65 already put the assemble step after publish+promote, so a failed manifest upload lands on a release that is already published; say what that release is allowed to claim.

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.

## Verification sweep — premise correction "It has no zone credential today" is wrong as written. `sow-module/.gitea/workflows/release.yml:45-46` already passes `BUNNY_STORAGE_ZONE` / `BUNNY_STORAGE_PASSWORD` from 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_PASSWORD` with the host falling back to `BUNNY_STORAGE_HOST`. So what remains to settle: - Does the nwsync zone get its own key, or is one key reused across both zones? (Bunny keys are per-zone, so "reuse" here means one more secret, not one shared secret.) - Whether Bunny can scope a write key to a prefix at all — `manifests/` only for sow-module, since blobs are written by the two emitter repos. Unchanged, still open. - The runner question is answerable now: it is the same runner that already holds `BUNNY_STORAGE_PASSWORD`, so "can it hold a secret" is yes; what is left is whether it *should* hold a second, wider-scoped one. - Failure posture on upload failure — unchanged, still the substance of this ticket. Note #65 already put the assemble step **after** publish+promote, so a failed manifest upload lands on a release that is already published; say what that release is allowed to claim. 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.
archvillainette self-assigned this 2026-07-30 19:55:41 +00:00
Author
Owner

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 via tea 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 to manifests/ 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): PUT with AccessKey: <password> and an uppercase Checksum header, existence by GET on the same URL.

Handing sow-module CI 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 holds BUNNY_STORAGE_PASSWORD and 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.sh puts SHA256SUMS and then haks.json (:71-72), and haks.json is 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/ from fetch-upstream-manifests.sh, per #63) and --group-id from the tag shape. So the order is: fetch upstream manifests → build → assemble → publish → promote, with the manifest SHA-1 written into haks.json beside hak_release and topdata_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-manifest and sow-topdata hold only BUNNY_STORAGE_ZONE / BUNNY_STORAGE_PASSWORD. emit cannot upload from either until NWSYNC_STORAGE_* is added to both, and per #65 all three repos must also pass BUNNY_STORAGE_HOST, because Crucible has no default host (internal/depot/config.go:42) while every repo's depot-lib.sh does. Split out as its own ticket.

## 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 via `tea 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 to `manifests/` 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`): `PUT` with `AccessKey: <password>` and an uppercase `Checksum` header, existence by GET on the same URL. Handing `sow-module` CI 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 holds `BUNNY_STORAGE_PASSWORD` and 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.sh` puts `SHA256SUMS` and then `haks.json` (`:71-72`), and `haks.json` **is** 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/` from `fetch-upstream-manifests.sh`, per #63) and `--group-id` from the tag shape. So the order is: fetch upstream manifests → build → **assemble** → publish → promote, with the manifest SHA-1 written into `haks.json` beside `hak_release` and `topdata_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-manifest` and `sow-topdata` hold only `BUNNY_STORAGE_ZONE` / `BUNNY_STORAGE_PASSWORD`. `emit` cannot upload from either until `NWSYNC_STORAGE_*` is added to both, and per #65 all three repos must also pass `BUNNY_STORAGE_HOST`, because Crucible has no default host (`internal/depot/config.go:42`) while every repo's `depot-lib.sh` does. Split out as its own ticket.
Author
Owner

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_KEY is 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.sh treats both an unset and a rejected key as a skip, so a failed purge of the mutable channels.json looks 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.

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_KEY` is 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.sh` treats both an unset *and* a rejected key as a skip, so a failed purge of the mutable `channels.json` looks 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.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: ShadowsOverWestgate/sow-tools#61