Files
sow-tools/docs/command-surface.md
T
archvillainette f9051a2c01
build-binaries / build-binaries (push) Successful in 2m18s
nwsync: upload sink, key-addressed CLI, fail-closed publication marker (#73)
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>
2026-07-30 21:56:06 +00:00

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.