Catalog normalizer application (#124)
September 21, 2026 ยท View on GitHub
The frozen acceptance contract is CATALOG-NORMALIZER-ORACLE-V1. The independent oracle and its cases under tests/oracle/catalog_normalizer/ are not application source.
The Semaprax project implements the bounded application end to end:
src/batch.spxcounts records under CNORM-001, locates line byte ranges, detects empty lines, and exposes the exact whole-body, record-count, and line-length bounds in CNORM-002..004. Validation carries each current line range forward; the intentional quadratic duplicate-id comparison also advances its prior-record range from the preceding line end, avoiding an extra nested prefix-location scan. A lone LF represents zero records; two LFs represent two empty records. The application test suite checks 512/513-byte lines and 256/257-record batches.src/app.spxdecodes JSON string tokens usingstd.data.json.dec, enforces the distinct decoded id (1..64 bytes) and label (at most 256 bytes) bounds, compares ids by decoded value for the duplicate-id rule, and trims only the four ASCII boundary whitespace bytes from labels (CNORM-010..014). Published-oracle boundary and escaped-duplicate cases are reproduced in named tests.src/record.spxcomposesstd.data.json.doc, the decoder, and a compact exact nonnegative-decimal scanner into a bounded per-line parser. It narrows a document to a depth-8 object, requires the exact rawid/label/quantitykey set, validates scalar value classes and bounds, and carries the first local failure as a deterministic category-plus-byte-offset scalar. Its duplicate-id comparison advances both decoder cursors once, checking validity, the 1..64-byte bound, and decoded equality without materializing an id or restarting either token.src/batch.spxthen identifies the first later decoded-equivalent id without an owned map.src/enrichment.spxis the CNORM-060 fixture-provider seam. Its table is frozen and source-local: no network capability, retry loop, or ambient provider selection exists. An enabled run looks up every already-accepted decoded id and either appends its category, appendsnull, or selects one terminal provider envelope before a response buffer is allocated.
src/tests.spx has fourteen named cases. The owning Rust application gate in tests/useful_data/catalog_normalizer_application.rs is registered exactly once by the useful_data harness and runs that same test closure serially through the authenticated interpreter, C11 at -O0 and -O2, and Core Wasm. Native and Wasm artifacts have disjoint lifetimes, and scratch artifacts are removed even when an assertion unwinds, keeping this capacity-sensitive gate bounded without dropping a lane. Its one authenticated snapshot first byte-compares all 36 frozen published cases with a live independent-oracle run, then invokes the real normalize or normalize-enriched entrypoint for every case and compares that output byte for byte; this is intentionally inside the existing snapshot so it cannot construct a second capacity-sensitive graph alongside a backend lane. It also mutates terminal-LF counting, UTF-8 admission, and checked-total overflow and requires the complete project test closure to reject each otherwise-valid candidate. The same snapshot executes the exact 69,710-byte maximal enriched response implied by the request, record, escaping, total, and fixture bounds.
All admitted parser, first-error, duplicate, checked-total, fixture-enrichment, and canonical success/error projection logic is connected for the source-tested bounded path. The writer validates the complete batch before allocating its response and emits through one scalar byte mapper, so failure cannot publish a successful prefix. During projection it carries the current record range plus the source-byte, decoded-UTF-8-part, and canonical-escape-part cursor for each active string field. Advancing a record starts at the preceding line end instead of rescanning every earlier line; advancing an emitted response byte does constant work instead of restarting decoding at the token opening. Total, length, enrichment, and boundary-trimming passes likewise walk records and tokens only forward, and the writer reuses its already-computed count and total when sizing output. The capacity proof uses the frozen maxima rather than sampling body shapes: canonical record bytes cannot exceed their admitted input-line bytes, the success envelope is at most 78 bytes, and enrichment adds at most 16 bytes to each of at most 256 records. The manifest's public smoke export is a contract-free probe, not the application API.
The fourteen-case source suite needs the documented bounded interpreter envelope:
semaprax test . --max-steps 1000000 --max-bytes 69710
Capacity remains a development constraint. The complete source-test projection forecast 46,202,120 builder bytes but exceeded the intermediate 48 MiB ceiling during live construction, motivating the 64 MiB graph ceiling. The fully scalar writer and source suite separately motivated the 32,000,000-unit global cleanup-replay work ceiling. The per-function 65,536-path refusal remains unchanged. The focused application gate is the executable evidence for this capacity-sensitive shape.