Builds sow-tools#53. Format spec followed is the resolution comment of sow-platform#94, checked line by line against niv/neverwinter.nim at HEAD (`nwsync.nim`, `compressedbuf.nim`, `nwsync/private/libupdate.nim`).
## What lands
`crucible nwsync emit <artifact> --out DIR` — explodes one `.hak`/`.erf`, or one loose file such as the TLK, into NWSync blobs plus a NSYM v3 manifest covering only that artifact, with the same `.json` sidecar upstream writes. Blob path `data/sha1/<h0h1>/<h2h3>/<sha1>`, body in NWCompressedBuffer framing (magic `NSYC`, version 3, algorithm 2, uncompressed size, zstd header version 1, dictionary 0, raw zstd frame). The sha1 that names a blob is over the uncompressed bytes.
`crucible nwsync assemble --order NAMES --entries DIR --out DIR [--group-id N]` — merges the per-artifact manifests into one, reading no bulk data at all. Merge rule is resref shadowing, not concatenation: a resref in more than one artifact resolves to the earliest artifact in `--order`, which is how the game resolves it. `--group-id` stays caller-supplied (1 current, 2 testing; 0 is absent, matching upstream omitting a zero integer meta field).
Rules taken from upstream and not re-invented: `nss`/`ndb`/`gic` always skipped; an unresolvable restype is a hard error, not a skip; a resource over 15 MB fails closed; no `latest` file and no `.origin` file, ever. A `.mod` is refused outright — a persistent world publishes no module contents, so the module contributes no bytes.
## Two deliberate departures
- **Emitter version is its own field, not the build revision.** `emitter_version` is a constant bumped only when emitted bytes change. Keying the refuse-to-merge check on `created_with` would invalidate every published index on every unrelated crucible commit and force a re-emit of the whole 15 GB corpus — the opposite of "nothing downstream ever needs the hak again".
- **`SOURCE_DATE_EPOCH` pins the sidecar timestamp.** The manifest itself was already deterministic; the sidecar's `created` was not, against the determinism rule in `docs/consumer-contract.md`.
## Not in this PR, and why
- **Direct upload.** Only the local `--out` sink exists, which is the conformance path. The upload sink and the mid-hak-failure question are sow-tools#60, and the consumer wiring is #65.
- **The conformance run against upstream.** sow-tools#59 owns getting `nwn_nwsync_write` running and capturing reference output. The format here was read from upstream source rather than from its output, so the byte-for-byte manifest comparison and the after-decompression blob comparison still have to happen — that is what #59 is for. The tests in this PR check the layout against the spec, so a shared misreading would pass them.
- **The `artifacts/haks/sha256/<a>/<b>/<sha256>.nsym` location.** emit writes `<out>/<name>.nsym`; where a publisher puts it is the publisher's business (#65).
- **The acceptance gate** — a real client syncing from an assembled manifest — is unchanged and still open.
## Checks
`make check` green (vet, unit tests, shellcheck, yamllint, workflow contract), `make smoke` green with the new builder, `nix build .#crucible` produces `crucible-nwsync`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #71
Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
Closes#52.
Adds `scripts/prune-release-assets.sh` and a best-effort step at the end of `build-binaries.yml`.
- Lists every release page, sorts by creation date, skips the newest two published releases, deletes the rest's attachments via `DELETE /releases/{id}/assets/{asset_id}`.
- Drafts never consume a keep slot, so a draft cannot strip the newest real release.
- Releases, tags, source archives and release notes are never touched.
- Nothing is excluded: SHA256SUMS and the wrappers go with the binaries. Checksums are only meaningful next to the files they cover, and the consumer drift-check reads the wrappers from the latest release, which stays complete.
- Failures warn and the step has `continue-on-error: true` — a stale asset is cheaper than a blocked publish.
`tests/prune-release-assets.sh` drives the script with a stub `curl`: keep window, out-of-order responses, drafts, no non-asset DELETE, short-page pagination stop, and both failure paths. Wired into `make check`.
v0.2.0 still needs its 9 assets cleared by hand in the web UI, as the issue notes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #70
Co-authored-by: vickydotbat <vickydotbat@tutamail.com>