Two correctness fixes surfaced in review of the direct-depot HAK artifact work.
Reject resref+type collisions in chunksFromManifest
A manifest can name two distinct source paths that collapse to the same resref+type (e.g. creature/foo.tga and placeable/foo.tga — resref is the
lowercase basename minus extension). The ERF writer keys resources on Name:Type, so the second silently shadowed the first: an asset would vanish
from the packed HAK with no error.
chunksFromManifest now runs ensureUniqueChunkResources per chunk and fails
the build on a duplicate. This guards both build paths — the legacy --source-manifest flow and the direct content-addressed flow
(chunksFromSourceManifest → chunksFromManifest).
Document erf post-write hash coupling
writeResourceData streams the source into the output while hashing, so
size/SHA mismatches are only detected after the bytes are written. A non-nil
return therefore means the writer holds partial, unverified output and the
caller must discard it. Added a comment making that contract explicit; writeHAKArchive already honours it (writes to a temp file, removes on any
Write error, never renames a bad archive into place).
Tests
TestChunksFromManifestRejectsResrefCollision: collision → error, distinct
resrefs → clean. Asserts only error presence/absence — silent asset loss is
the contract, not any specific wording.
go vet ./internal/erf/ ./internal/pipeline/ clean.
go test ./internal/erf/ ./internal/pipeline/ green.
Follow-up
A new crucible release must be cut after this merges so the guard ships in the
binary sow-assets-manifest pins.
## What
Two correctness fixes surfaced in review of the direct-depot HAK artifact work.
### Reject resref+type collisions in `chunksFromManifest`
A manifest can name two distinct source paths that collapse to the same
`resref+type` (e.g. `creature/foo.tga` and `placeable/foo.tga` — resref is the
lowercase basename minus extension). The ERF writer keys resources on
`Name:Type`, so the second silently shadowed the first: an asset would vanish
from the packed HAK with no error.
`chunksFromManifest` now runs `ensureUniqueChunkResources` per chunk and fails
the build on a duplicate. This guards **both** build paths — the legacy
`--source-manifest` flow and the direct content-addressed flow
(`chunksFromSourceManifest` → `chunksFromManifest`).
### Document erf post-write hash coupling
`writeResourceData` streams the source into the output while hashing, so
size/SHA mismatches are only detected *after* the bytes are written. A non-nil
return therefore means the writer holds partial, unverified output and the
caller must discard it. Added a comment making that contract explicit;
`writeHAKArchive` already honours it (writes to a temp file, removes on any
Write error, never renames a bad archive into place).
## Tests
- `TestChunksFromManifestRejectsResrefCollision`: collision → error, distinct
resrefs → clean. Asserts only error presence/absence — silent asset loss is
the contract, not any specific wording.
- `go vet ./internal/erf/ ./internal/pipeline/` clean.
- `go test ./internal/erf/ ./internal/pipeline/` green.
## Follow-up
A new crucible release must be cut after this merges so the guard ships in the
binary `sow-assets-manifest` pins.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
What
Two correctness fixes surfaced in review of the direct-depot HAK artifact work.
Reject resref+type collisions in
chunksFromManifestA manifest can name two distinct source paths that collapse to the same
resref+type(e.g.creature/foo.tgaandplaceable/foo.tga— resref is thelowercase basename minus extension). The ERF writer keys resources on
Name:Type, so the second silently shadowed the first: an asset would vanishfrom the packed HAK with no error.
chunksFromManifestnow runsensureUniqueChunkResourcesper chunk and failsthe build on a duplicate. This guards both build paths — the legacy
--source-manifestflow and the direct content-addressed flow(
chunksFromSourceManifest→chunksFromManifest).Document erf post-write hash coupling
writeResourceDatastreams the source into the output while hashing, sosize/SHA mismatches are only detected after the bytes are written. A non-nil
return therefore means the writer holds partial, unverified output and the
caller must discard it. Added a comment making that contract explicit;
writeHAKArchivealready honours it (writes to a temp file, removes on anyWrite error, never renames a bad archive into place).
Tests
TestChunksFromManifestRejectsResrefCollision: collision → error, distinctresrefs → clean. Asserts only error presence/absence — silent asset loss is
the contract, not any specific wording.
go vet ./internal/erf/ ./internal/pipeline/clean.go test ./internal/erf/ ./internal/pipeline/green.Follow-up
A new crucible release must be cut after this merges so the guard ships in the
binary
sow-assets-manifestpins.Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com