Builds the code half of #60, and closes the CLI gap the #53 sweep found: PR #71 shipped #53's original surface rather than the one #56, #62 and #65 settled. ## What lands **`depot.KeyStore`** — `ProbeKey` / `PutReader` / `GetKey`, addressing the zone by object key rather than by depot sha. NWSync cannot use the sha-addressed path: a blob is named after the sha1 of its *uncompressed* bytes while the body uploaded is the compressed form, and Bunny's `Checksum` header is sha256 of the body. Per #55 this reuses `internal/depot`'s `httpBackend` — same IPv4-pinned transport, same retry, same tri-state probe — and the sha-addressed `Backend` is now rewritten on top of it. No second HTTP client, no per-instance hash-function fields: the caller passes the key and the checksum, which turned out simpler than #55 expected. **A sink in `internal/nwsync`** — the zone by default, a local tree under `--out DIR` as the conformance path. Blobs upload as they are produced and the index lands last, so the presence of an index is the publication marker. A blob already in the zone is skipped via #55's probe *without* paying for compression (the body is a thunk) — which matters for the backfill, where compression is the expensive part. **The settled CLI** ``` nwsync emit [--as NAME] [--out DIR] <artifact-key> <file> nwsync assemble --group-id N [--tlk-key KEY] [--out DIR] <artifact-key>... ``` Artifact keys are depot keys; an index lives beside its artifact with the extension replaced (#62), derived in exactly one place so `emit` and `assemble` cannot disagree. Flags may now follow positionals — Go's `flag` stops at the first non-flag argument, which cost a run during #59. **Fail-closed in two places** — an artifact key whose embedded digest does not match the file is refused (publishing an index under the wrong key silently pairs a manifest with the wrong artifact), and `assemble` refuses an artifact with no index rather than publishing a manifest missing a hak. ## Checks `make check` green. Six new tests run against a Bunny-shaped `httptest` zone that verifies the `Checksum` header the way Bunny does: blobs-then-index ordering, skip-if-present, no index after a failed upload, key/file mismatch, and assemble reading indexes back out of the zone. Conformance re-run through the new CLI against upstream `nwn_nwsync_write` 2.1.2 over `sow_vfxs_01.hak` (#59's oracle): the manifest is still **byte-identical**. ## Not in this PR - **Live upload against the real zone.** The nwsync zone and its credential are #61, still open. Everything here is proven against a fake zone only. - **Consumer wiring** — #65, in the three producer repos. - **The mid-hak failure *policy*.** The mechanism is here (fail closed, orphan blobs left, re-run resumes); whether a module release may proceed when an emit failed is a human call, still open on #60. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #73 Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
5.9 KiB
Crucible command surface
internal/dispatch.Registry is the single source of truth for Crucible's
builders, visible commands, descriptions, help, and hidden compatibility
aliases.
Visible commands
| Builder | Command | Purpose |
|---|---|---|
hak |
build |
Build configured HAK archives and their manifest. |
hak |
manifest |
Apply a generated HAK list to module source. |
module |
build |
Build the module and configured project outputs. |
module |
extract |
Extract built archives into configured source trees. |
module |
validate |
Validate project layout, config, and source inventory. |
module |
compare |
Compare source content with built archives. |
module |
manifest |
Apply a generated HAK list to module source. |
topdata |
validate |
Validate topdata source and canonical JSON compatibility. |
topdata |
build |
Compile topdata and refresh the configured package. |
topdata |
package |
Package cached outputs into HAK and TLK files. |
topdata |
compare |
Compare current output with a fresh native build. |
topdata |
convert |
Convert between 2DA and native JSON/module formats. |
wiki |
build |
Render wiki page drafts from compiled topdata. |
wiki |
deploy |
Deploy generated wiki pages to NodeBB. |
assets |
compile |
Compile ASCII .mdl models to binary in place. |
assets |
convert |
Convert textures to/from NWN DDS (flips vertically). |
assets |
upscale |
Upscale textures through an installed backend. |
assets |
check-mdl |
Report uncompiled ASCII .mdl and model-name mismatches. |
assets |
fix-mdl |
Lowercase .mdl names and rewrite model identity to match. |
assets |
check-dupes |
Report runtime-name (basename) collisions across dirs. |
assets |
clean-dupes |
Delete clean-tree files whose basename collides with primary. |
depot |
status |
Report referenced-vs-present drift against a backend. |
depot |
push |
Upload referenced-but-absent blobs from a local depot. |
depot |
verify |
Existence sweep plus sampled download re-hash. |
depot |
get |
Fetch one blob with sha re-verify. |
depot |
pull |
Incremental verified pull of every referenced blob. |
nwsync |
emit |
Explode one artifact into NWSync blobs plus its own NSYM manifest. |
nwsync |
assemble |
Merge per-artifact NSYM manifests into one merged manifest. |
depot status and depot get pick their backend either with --out DIR, a
depot tree on disk, or with --target bunny|cdn, a remote backend. The two
flags are mutually exclusive.
nwsync emit runs where an artifact is born (a .hak/.erf, or a loose file
such as the TLK); nwsync assemble runs at module release and reads only the
small per-artifact indexes. Both take depot keys: an artifact's index lives
beside the artifact itself with the extension replaced, so emit and
assemble agree on where it is without being told.
nwsync emit [--as NAME] [--out DIR] <artifact-key> <file>
nwsync assemble --group-id N [--tlk-key KEY] [--out DIR] <artifact-key>...
Both verbs upload by default; nothing bulky is ever written to the runner's
disk. --out DIR writes a local repository tree instead, which is the
conformance path against upstream nwn_nwsync_write. The zone comes from
NWSYNC_STORAGE_ZONE and NWSYNC_STORAGE_PASSWORD, with the host from
BUNNY_STORAGE_HOST — NWSync data is a separate zone from the asset depot.
assemble's artifact keys are in Mod_HakList order, highest priority first: a
resref in more than one artifact resolves to the earliest one, the way the game
resolves it. --tlk-key has its own slot because the TLK shadows nothing.
--group-id is per channel — 1 is current, 2 is testing, and 0 leaves the field
out of the sidecar.
emit uploads blobs first and the index last, so the presence of an index is
the publication marker: an artifact whose emit died halfway leaves real blobs in
the zone and no index. Blob names are content hashes, so re-running skips
whatever already landed, and assemble fails closed on an artifact with no
index rather than publishing a manifest that is missing a hak.
Hidden compatibility aliases
Existing scripts may continue using these names indefinitely. They are accepted but omitted from routine help and the interactive menu:
| Builder | Hidden alias | Implementation |
|---|---|---|
hak |
build-haks |
build-haks |
hak |
apply-hak-manifest |
apply-hak-manifest |
module |
build-module |
module-only build-module |
module |
apply-hak-manifest |
apply-hak-manifest |
topdata |
validate-topdata |
validate-topdata |
topdata |
build-topdata |
build-topdata |
topdata |
build-top-package |
build-top-package |
topdata |
compare-topdata |
compare-topdata |
topdata |
convert-topdata |
convert-topdata |
depot |
--target local |
--out $DEPOT_DIR on status/get |
wiki |
build-wiki |
build-wiki |
wiki |
deploy-wiki |
deploy-wiki |
module build-module deliberately retains its narrower module-only behavior;
it is not redirected to the broader module build.
Global commands
crucible help | -h usage + builder list
crucible version | -V build version
crucible list name<TAB>binary<TAB>summary
crucible config [args] inspect and validate effective configuration
crucible changelog [args] generate a release changelog
HAK build options
crucible hak build
[--hak <hak-name> ...]
[--archive <archive-name> ...]
[--source-manifest <path>]
[--content-addressed-root <path>]
[--plan-only]
[--quiet|--verbose|--debug]
When a source manifest contains asset_sources, pass
--content-addressed-root <directory>. Crucible resolves each declared SHA-256
under sha256/<first-two>/<next-two>/<full-sha256> and verifies streamed bytes
while writing selected HAK archives. Authored .bmu files are ordinary assets
and require no conversion pipeline.