fix(deploy): route /dict through caddy to the gateway (local eval)
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 58s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m30s

The edge caddy @gateway matcher was missing /dict/*, so the client's dictionary
fetch fell to the static landing catch-all: it received a non-dawg blob (200 OK),
which the reader parsed into a bogus finder that reports every word missing — a
valid first move showed as illegal with no network fallback. Add /dict/* to the
gateway route.

Hardening + the cross-test that would have caught it:
- reader: reject a blob whose 32-bit size header != its byte length, so a
  non-dawg page or a truncated download now throws and the loader falls back to
  the network preview instead of silently validating against garbage.
- eval.parity: an adapter conformance suite that drives evaluateLocal through the
  real letter-space path (the server alphabet table, a letter board and letter
  placements) against the engine — the algorithm-level validate.parity did not
  exercise the adapter. validategen now emits the alphabet table for it.
- a CI deploy probe asserts {edge}/dict reaches the gateway (401), not the landing.
- the DebugPanel reports the feature flag, the bad-connection breaker and the
  cached dictionaries with their sizes; its reset also clears the dict cache.
This commit is contained in:
Ilia Denisov
2026-07-01 20:55:20 +02:00
parent d24a127406
commit 2776ef83c2
8 changed files with 233 additions and 22 deletions
+10
View File
@@ -43,6 +43,16 @@ export class Dawg {
constructor(bytes: Uint8Array) {
this.bytes = bytes;
// Reject anything that is not a serialized dawg — e.g. an HTML error page or a
// truncated download routed here by mistake. The 32-bit big-endian header size is
// the total byte length; if it does not match, the blob is not a dawg. Without
// this guard a non-dawg blob would parse into a bogus reader that silently reports
// every word as missing (so the caller must throw here to fall back to the network).
const declaredSize = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
if (declaredSize !== bytes.length) {
throw new Error(`dawg: not a dawg blob (size header ${declaredSize} != ${bytes.length} bytes)`);
}
// Header: 32-bit size (skipped — the whole file is already in memory), then
// cbits, abits, the language code string, and the word/node/edge counts.
this.p = 32;