Where do the nwsync verbs live in Crucible's command surface — a new crucible-nwsync binary, or folded into crucible-hak?
Part of #54. docs/command-surface.md and internal/dispatch are the authority; the dispatch registry maps a builder group to a crucible-<name> binary.
The tension: the repo's pattern says one group, one binary, and nwsync is a distinct concern with its own two verbs. But crucible-hak already reads ERFs, which is exactly what emit needs, and a new binary is a new cmd/ shim, a new dispatch group, a new release asset and a new thing for every consumer wrapper to resolve.
Settle: the group name, the binary, the verb names and their flags as they will actually appear in docs/command-surface.md, and whether assemble — which reads no ERF at all and is pure index merging — belongs in the same place as emit.
## Question
Where do the `nwsync` verbs live in Crucible's command surface — a new `crucible-nwsync` binary, or folded into `crucible-hak`?
Part of #54. `docs/command-surface.md` and `internal/dispatch` are the authority; the dispatch registry maps a builder group to a `crucible-<name>` binary.
The tension: the repo's pattern says one group, one binary, and `nwsync` is a distinct concern with its own two verbs. But `crucible-hak` already reads ERFs, which is exactly what `emit` needs, and a new binary is a new `cmd/` shim, a new dispatch group, a new release asset and a new thing for every consumer wrapper to resolve.
Settle: the group name, the binary, the verb names and their flags as they will actually appear in `docs/command-surface.md`, and whether `assemble` — which reads no ERF at all and is pure index merging — belongs in the same place as `emit`.
--out DIR — write locally instead of uploading. Absent means upload to the Bunny zone; present means the conformance path on a developer machine, and nothing touches the network. Same meaning on both verbs.
--as <name> — override the manifest entry name; --as sow_tlk.tlk for the TLK, per #62.
--group-id <n> — caller-supplied, per the map's settled decision (1 = current, 2 = testing). Crucible never derives it.
--tlk-key <key> — the TLK is not a hak and has no place in the precedence order, so it gets its own slot rather than a position in the list.
Keys are positional, in both verbs.emit takes exactly one, assemble takes N in Mod_HakList order. This overrides #62's --artifact-key spelling in favour of one uniform rule across the group; its meaning there is unchanged, and emit still derives the sibling NSYM key itself with the fail-closed digest check.
Why its own group and not folded into hak
The stated cost of a new binary does not survive contact with the build:
.gitea/workflows/build-binaries.yml and ci.yml build only./cmd/crucible. A new cmd/ shim adds no release asset.
wrappers/crucible.sh resolves a single asset (crucible-${os}-${arch}). The three consumer repos in wrappers/consumers.txt need no wrapper change to reach a new group.
Real cost: one dispatch.Registry entry, a one-line cmd/crucible-nwsync/main.go, one line in flake.nixsubPackages, one docs row.
Against that, hak is the wrong home on the merits. assemble reads no ERF at all — it is pure index merging over JSON — so folding it into hak would put a verb in a builder whose entire subject it never touches. Splitting the two verbs across builders is worse still: they share the artifact-key-to-NSYM-key rule that #62 deliberately kept in one place.
depot is the precedent, not the alternative: it is a separate group that bypasses internal/app entirely (dispatch.RunBuilder("depot", ...) straight into internal/depot.Run). nwsync takes the same shape — Wired: true, delegating to internal/nwsync.Run — while reusing internal/depot's httpBackend per #55.
Zone and credentials
Two storage zones exist. NWSync data does not live in the assets depot zone, so there is no --zone flag and no reuse of BUNNY_STORAGE_ZONE. sow-module already carries the secrets (created 2026-07-25): NWSYNC_STORAGE_ZONE and NWSYNC_STORAGE_PASSWORD.
internal/nwsync mirrors internal/depot/config.go's env resolution under the NWSYNC_ prefix, falling back to BUNNY_STORAGE_HOST for the host — it is the same Bunny storage endpoint, only a different zone. Credentials come from the environment; no flag carries them.
Follow-on, outside this map: --out DIR reads better than depot's --target local, so depot should adopt the same convention.
## Resolution
`nwsync` is its own builder group with its own binary, `crucible-nwsync`, holding both verbs.
### The surface, as it will read in `docs/command-surface.md`
| Builder | Command | Purpose |
| --- | --- | --- |
| `nwsync` | `emit` | Emit an artifact's blobs and its per-artifact NSYM index. |
| `nwsync` | `assemble` | Merge per-artifact NSYM indices into one NWSync manifest. |
```text
crucible nwsync emit [--as <name>] [--out DIR] <artifact-key>
crucible nwsync assemble --group-id <n> [--tlk-key <key>] [--out DIR] <artifact-key>...
```
- `--out DIR` — write locally instead of uploading. Absent means upload to the Bunny zone; present means the conformance path on a developer machine, and nothing touches the network. Same meaning on both verbs.
- `--as <name>` — override the manifest entry name; `--as sow_tlk.tlk` for the TLK, per #62.
- `--group-id <n>` — caller-supplied, per the map's settled decision (1 = current, 2 = testing). Crucible never derives it.
- `--tlk-key <key>` — the TLK is not a hak and has no place in the precedence order, so it gets its own slot rather than a position in the list.
**Keys are positional, in both verbs.** `emit` takes exactly one, `assemble` takes N in `Mod_HakList` order. This overrides #62's `--artifact-key` spelling in favour of one uniform rule across the group; its *meaning* there is unchanged, and `emit` still derives the sibling NSYM key itself with the fail-closed digest check.
### Why its own group and not folded into `hak`
The stated cost of a new binary does not survive contact with the build:
- `.gitea/workflows/build-binaries.yml` and `ci.yml` build **only** `./cmd/crucible`. A new `cmd/` shim adds **no** release asset.
- `wrappers/crucible.sh` resolves a single asset (`crucible-${os}-${arch}`). The three consumer repos in `wrappers/consumers.txt` need **no** wrapper change to reach a new group.
- Real cost: one `dispatch.Registry` entry, a one-line `cmd/crucible-nwsync/main.go`, one line in `flake.nix` `subPackages`, one docs row.
Against that, `hak` is the wrong home on the merits. `assemble` reads no ERF at all — it is pure index merging over JSON — so folding it into `hak` would put a verb in a builder whose entire subject it never touches. Splitting the two verbs across builders is worse still: they share the artifact-key-to-NSYM-key rule that #62 deliberately kept in one place.
`depot` is the precedent, not the alternative: it is a separate group that bypasses `internal/app` entirely (`dispatch.RunBuilder("depot", ...)` straight into `internal/depot.Run`). `nwsync` takes the same shape — `Wired: true`, delegating to `internal/nwsync.Run` — while reusing `internal/depot`'s `httpBackend` per #55.
### Zone and credentials
Two storage zones exist. NWSync data does **not** live in the assets depot zone, so there is no `--zone` flag and no reuse of `BUNNY_STORAGE_ZONE`. `sow-module` already carries the secrets (created 2026-07-25): `NWSYNC_STORAGE_ZONE` and `NWSYNC_STORAGE_PASSWORD`.
`internal/nwsync` mirrors `internal/depot/config.go`'s env resolution under the `NWSYNC_` prefix, falling back to `BUNNY_STORAGE_HOST` for the host — it is the same Bunny storage endpoint, only a different zone. Credentials come from the environment; no flag carries them.
Follow-on, outside this map: `--out DIR` reads better than `depot`'s `--target local`, so `depot` should adopt the same convention.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Question
Where do the
nwsyncverbs live in Crucible's command surface — a newcrucible-nwsyncbinary, or folded intocrucible-hak?Part of #54.
docs/command-surface.mdandinternal/dispatchare the authority; the dispatch registry maps a builder group to acrucible-<name>binary.The tension: the repo's pattern says one group, one binary, and
nwsyncis a distinct concern with its own two verbs. Butcrucible-hakalready reads ERFs, which is exactly whatemitneeds, and a new binary is a newcmd/shim, a new dispatch group, a new release asset and a new thing for every consumer wrapper to resolve.Settle: the group name, the binary, the verb names and their flags as they will actually appear in
docs/command-surface.md, and whetherassemble— which reads no ERF at all and is pure index merging — belongs in the same place asemit.Resolution
nwsyncis its own builder group with its own binary,crucible-nwsync, holding both verbs.The surface, as it will read in
docs/command-surface.mdnwsyncemitnwsyncassemble--out DIR— write locally instead of uploading. Absent means upload to the Bunny zone; present means the conformance path on a developer machine, and nothing touches the network. Same meaning on both verbs.--as <name>— override the manifest entry name;--as sow_tlk.tlkfor the TLK, per #62.--group-id <n>— caller-supplied, per the map's settled decision (1 = current, 2 = testing). Crucible never derives it.--tlk-key <key>— the TLK is not a hak and has no place in the precedence order, so it gets its own slot rather than a position in the list.Keys are positional, in both verbs.
emittakes exactly one,assembletakes N inMod_HakListorder. This overrides #62's--artifact-keyspelling in favour of one uniform rule across the group; its meaning there is unchanged, andemitstill derives the sibling NSYM key itself with the fail-closed digest check.Why its own group and not folded into
hakThe stated cost of a new binary does not survive contact with the build:
.gitea/workflows/build-binaries.ymlandci.ymlbuild only./cmd/crucible. A newcmd/shim adds no release asset.wrappers/crucible.shresolves a single asset (crucible-${os}-${arch}). The three consumer repos inwrappers/consumers.txtneed no wrapper change to reach a new group.dispatch.Registryentry, a one-linecmd/crucible-nwsync/main.go, one line inflake.nixsubPackages, one docs row.Against that,
hakis the wrong home on the merits.assemblereads no ERF at all — it is pure index merging over JSON — so folding it intohakwould put a verb in a builder whose entire subject it never touches. Splitting the two verbs across builders is worse still: they share the artifact-key-to-NSYM-key rule that #62 deliberately kept in one place.depotis the precedent, not the alternative: it is a separate group that bypassesinternal/appentirely (dispatch.RunBuilder("depot", ...)straight intointernal/depot.Run).nwsynctakes the same shape —Wired: true, delegating tointernal/nwsync.Run— while reusinginternal/depot'shttpBackendper #55.Zone and credentials
Two storage zones exist. NWSync data does not live in the assets depot zone, so there is no
--zoneflag and no reuse ofBUNNY_STORAGE_ZONE.sow-modulealready carries the secrets (created 2026-07-25):NWSYNC_STORAGE_ZONEandNWSYNC_STORAGE_PASSWORD.internal/nwsyncmirrorsinternal/depot/config.go's env resolution under theNWSYNC_prefix, falling back toBUNNY_STORAGE_HOSTfor the host — it is the same Bunny storage endpoint, only a different zone. Credentials come from the environment; no flag carries them.Follow-on, outside this map:
--out DIRreads better thandepot's--target local, sodepotshould adopt the same convention.