Acceptance gate: a real NWN client syncs from an assembled manifest on the Bunny zone #75
Notifications
Due Date
No due date set.
Depends on
#88 Run the verifying backfill: every blob published by emitter 1 is still unreadable by a client
ShadowsOverWestgate/sow-tools
#89 Replacing a blob does not purge the pull-zone cache, so verify can pass while clients still get the bad bytes
ShadowsOverWestgate/sow-tools
You do not have permission to read 3 dependencies
Reference: ShadowsOverWestgate/sow-tools#75
Reference in New Issue
Block a user
Part of #54.
Split out of #53, which is otherwise complete.
Everything upstream of this gate has landed and is verified:
crucible nwsync emit/assembleshipped in PR #71 (a131b25), key-addressed CLI in PR #73 (f9051a2).nwn_nwsync_write2.1.2 passed in #59 — manifest byte-identical, 2,296 blobs with matching names and paths, payloads identical after decompression.What is still unproven is the only thing conformance cannot prove: that a real
NWN:EE client can actually sync from what we publish. Upstream never runs in
production for us, and a shared misreading of the serving side — zone layout,
content types, the URL the client is handed — would pass every check we have.
Done means
assemblerun against areal channel group id (current=1 or testing=2), published to the Bunny zone.
the module with a character, with no missing or wrong assets.
bloodstain1.tgaand friends, insow_core_01andsow_over_01) andsow_top's 2DAs are spot-checked in thesynced client — earliest-hak-wins is the rule that ships the wrong texture
silently if it is backwards.
recorded, since none of it is in Crucible.
Notes
toolchain repo's test suite. It stays a manual run.
internal/nwsync— the bytes are already proven against upstream.The zone side is ready; the gate is now blocked on infrastructure, not on tooling
Every hak named by a channel pointer now has its blobs and its
.nsymindex in thesow-synczone, backfilled without re-releasing anything. Emit streams and runs in parallel, and the size guard that used to skip the big haks is gone (see the #54 update for the detail). So "a full corpus emit published to the Bunny zone" — the first half of Done means — is satisfied for the haks. What remains is the TLK, oneassemblerun against a real channel group id, and the client run itself.Three prerequisites, none of them in a repo:
1. netcup-game must be hosting the testing server. The gate is "enters the module with a character", which needs a running server, not just a published manifest. No server, no gate.
2. A testing tag cut for every repo that has not had one. All three testing pointers are stale right now:
v0.2.0v0.1.7v0.2.0v0.1.3v0.2.2v0.1.11Every one of those
testingvalues is an autoseed, not a deliberate release candidate — this is exactly the failure sow-assets-manifest#47 describes for the haks stream, and the module and topdata streams have it too. It matters specifically for this gate: Done means says the assemble run targets "a real channel group id (current=1 or testing=2)". Assembling againsttestingtoday would assemblev0.1.7haks — content nobody chose, two releases behind, and not what the acceptance run is meant to prove. A real-rcNtag has to move each pointer first.3. A live, joinable session. The spot-checks in Done means are visual and in-world: the 13 cross-hak duplicate resrefs shared by
sow_core_01andsow_over_01(bloodstain1.tgaand friends) andsow_top's 2DAs. Earliest-hak-wins ships the wrong texture silently if the rule is backwards, so it cannot be checked from the zone — someone has to log in and look.Suggested order
testingnames something deliberate on all three streams. sow-assets-manifest#47 covers the haks stream and names two defects a first real RC would walk into; module and topdata need the same treatment.assembleagainst channel group 2 (testing).Steps 1 to 3 are prerequisites owned outside this repo. Step 4 is the gate. If the sync fails, the bytes are already proven byte-identical against upstream
nwn_nwsync_write2.1.2, so look at the serving side first.Two corrections to the comment above.
The testing-channel work is already ticketed on all three streams, so prerequisite 2 needs no new issues — just the tags:
All three are open and labeled
ready-for-agent.The TLK is already emitted. I listed it as remaining; it is not. sow-topdata#178 emitted blobs and a NSYM index for
sow_top.hakandsow_tlk.tlk, and sow-topdata#182 backfilled the already-published topdata artifacts. Both closed.So the corpus side of Done means is complete — every hak plus the TLK has blobs and an index in the zone. What is left on the toolchain side is only the
assemblerun against a real channel group id, and that wants the testing pointers moved first.Prerequisite 1 has moved, and this ticket now carries its blockers as native dependencies rather than as prose.
The three testing-channel PRs are merged — sow-assets-manifest#60 (ShadowsOverWestgate/sow-assets-manifest#60 (comment)), sow-module#55 (ShadowsOverWestgate/sow-module#55 (comment)), sow-topdata#187 (ShadowsOverWestgate/sow-topdata#187 (comment)) — so each stream's release-candidate path is ready. The tags themselves are still not cut, and the ordering is load-bearing: haks first (sow-assets-manifest#47, which I reopened after it was auto-closed by a
Closesline although the tag is its actual deliverable), then topdata (sow-topdata#151), then module (sow-module#49).Two blockers were not written down anywhere before today, and both are now dependencies of this issue:
grep -rn nwsync sow-module/scripts sow-module/.giteareturns nothing; sow-tools#65 closed reading "all three repos wired" but only the two emitters landed. Without this there is no manifest to sync from at all.runtime/nwn.nix:27-52runs a Caddy file-server over${s.stateRoot}/nwn/nwsyncand hands the serverNWN_NWSYNCURL = https://${s.nwsyncHost}, routed to it. Onnetcup-gamethat directory is empty. This is the failure this ticket predicted in as many words, and it contradicts ADR-0020.So Done means now reads: corpus emitted (done), the three
-rcNtags cut, sow-module#51 built, sow-platform#128 built, then assemble against group 2 and the client run. The client run is still the gate and still manual.Platform's blocker is built; the serving side is now written down
sow-platform#128 (PR ShadowsOverWestgate/sow-platform#129) is the platform third of what this gate was waiting on.
"The URL the client is handed" is now real. Until this change the game server was still configured to sync from a
caddyfile-server over a directory on the host — a directory that has been empty since every NWSync byte moved to thesow-synczone. That is exactly the "shared misreading of the serving side" this issue predicted: it passes every check we have and fails only in front of a tester.NWN_NWSYNCURLnow names the Bunny pull zone,NWN_NWSYNCHASHcarries the assembled manifest's SHA-1, and the build refuses an empty hash."Whatever the zone needed to serve this ... is recorded" — that bullet is answered by
docs/runbooks/nwsync.mdin sow-platform, written from what the zone actually returns today rather than from intent:data/sha1/<aa>/<bb>/<sha1>blobs,artifacts/…/<sha256>.nsym+.jsonper-artifact indexes beside their depot key,manifests/<sha1>+.jsonfor the assembled manifest.application/octet-streamfor blobs, indexes and manifests;application/jsonfor sidecars. Nothing in the path depends on them being anything else.cache-control: public, max-age=2592000,accept-ranges: bytes. Safe because every name in the zone is a content hash, so nothing to purge on release — and a warning never to add a mutable pointer file there./answers 403 on a healthy zone), POST blocking, Force SSL.6046370) frontssow-syncand not the depot — a depot key 404s there, an NWSync index serves 200.What this gate still waits on: sow-module#51, so something actually assembles a manifest, and a real
-rcNtag on each of the three streams. Platform's side no longer blocks it. When the manual run happens, anything the client needed that the runbook does not say is a bug in the runbook — please add it there rather than in a comment.One of the two blockers is gone. sow-module#51 merged (PR #57) and sow-module#49 cut
v0.2.1-rc1, so a manifest assembled by sow-module release CI now exists on the zone:Reachable at
https://nwsync.westgate.pw/manifests/061d23b9…(200) with its sidecar at….json(200). Sampled blobs across the sorted hash range all resolve underdata/sha1/<xx>/<yy>/<sha1>. Recorded for consumers atreleases/module/v0.2.1-rc1/nwsync.jsonon the depot.That is the map's destination minus the client: assembled by module CI, served from Bunny, no bulk data on ovh-main.
Remaining blocker is sow-platform#79 alone — nothing sets
sow.services.nwn.nwsyncManifestyet, sosow.services.nwn.enablestill hard-fails the NixOS build per ADR-0029. Once #79's resolver reads the sidecar above, this gate can run.Content pairing for the acceptance run, if it helps whoever picks it up: module
v0.2.1-rc1← haksv0.2.1-rc1+ topdatav0.2.3-rc2, all threetesting.First real client sync against the live zone — most of the way, then a transient failure
Ran this gate today from the
testingtarget onnetcup-game(sow-platform#79). Recording it here because this ticket is the gate.What worked, end to end and unassisted:
NWN_NWSYNCHASH→nwsync.westgate.pw.What failed:
It is not a repository issue. Verified that exact object by hand:
017f6802…, size 230, resrefblood_fore, restype 2072NSYCv3, algorithm 2, declared size 230, 203 bytes on the wiresha1(uncompressed)== the filenameThen audited the zone more broadly: 68 blobs verified end to end, 0 corrupt, 0 missing — 60 sampled at random from the manifest plus the 8 largest entries (14 MB uncompressed each, ~1.8 MB compressed). Every one decompressed to its declared size with a matching SHA-1.
Also confirmed the edge is not mangling anything: identical bytes with
Accept-Encoding: gzip(nocontent-encodingapplied), with a NWN-ish User-Agent, and a correct206with a properContent-Rangefor a range request. No rate limiting is configured on the zone.Conclusion: a transient truncated transfer, not stored corruption. The tell is in the screenshot — 58 KB/s, far below what the CDN serves, with many parallel fetches in flight. A stalled connection yields a short body, and a short zstd frame is exactly "potential compression error".
So the gate is nearly met: the manifest, the delta calculation, the URL handshake and the blob format all work against a real client. What is unproven is a sync running to completion. Retrying.
Filed separately: the client's checksum is currently the only thing that ever verifies a published blob, which is a thin place to find out.
Correction: not transient, and my earlier read was wrong
I said above this looked like a truncated transfer and to retry. That was wrong, and the retry disproved it: same file,
017f6802…, from 8593 files remaining to 8590. Deterministic.It is also not the client's cache — that blob is absent from
nwsyncdata_0.sqlite3(64,715 rows present), so it is fetched fresh and rejected every time.The cause is our zstd framing:
klauspost/compressomits Frame_Content_Size for inputs under 256 bytes, reference libzstd does not, and the client needs it. Full diagnosis and reproduction in #86.Why my audit said the zone was healthy. I verified 68 blobs by decompressing and hashing them, and they all passed — because the
zstdCLI streams fine without a declared content size. I used a more capable decoder than the client's and concluded the thing it tripped on did not exist. Round-trip success is not the property that matters here; the frame header is. #85's verifier has to assert frame properties, not just that bytes come back.So this gate stays open, and it has earned its keep: the manifest, the delta calculation, the URL handshake and ~64k blobs all work, and one 6%-of-files framing detail still stops every sync.
Gate met
The testing target on netcup-game is live, a retail NWN:EE client synced to completion against the published manifest, and a character is in the module.
Against Done means:
v0.2.1-rc1against haksv0.2.1-rc1and topdatav0.2.3-rc2, all three testing, group 2.--verify(2,603 blobs rewritten), 8 stale edge keys purged, then a full sweep —verified 69177 of 69177 blobs behind 72544 resources: 0 failures. Detail in #88.internal/nwsync— the bytes are proven against upstream.Closing on the sync and the character.
The outstanding in-world spot-check is now #92, so it does not get lost with this ticket closed.