wai breaking and wai conform: the compatibility gate on every WAI pull request, and conformance runs with signed results #507
Loading…
Reference in a new issue
No description provided.
Delete branch "wai/breaking-conform"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this is
Two operations of the WAI toolchain, and the CI gate WAI's libraries are published behind.
wai breaking --against <git-ref>(operationcompat.check): what the working tree breaks against the merge base of a ref andHEAD, in three cumulative categories, strictest by default (--category wire|record|api,--only/--skip <surface>).registry: the registry export (wai/registry/capabilities.json, held to REGISTRY.md byregistry export --check). A capability, identifier or wire identifier (keyed by kind and name:wai1is both acodecsprefix and a 4CC) that is gone breaks; so does a capability whose kind, media, role, tier, licence, replicate class, edition or operator set changed, one that left a set or joined the floor, one demoted to candidate, and a wire identifier whose status fell back.corpora: everywai/*-conformance/vector andwai-rs/tests/vectors/file that is gone or whose bytes changed; a JSON file is diffed structurally (entries keyed byname/file/id/…, prose members not compared), so a corpus that only gained vectors is compatible.domains: every literal constant named…DOMAIN…,…MAGIC…or…VERSION…inwai/*/src(binaries and test modules left out): a changed value or a removed one breaks (a move to another file with the same name and value does not).corpora: the record corpora (claim, signer, jwp, pq, oer, pin, edge, live).schemas:wai/**/*.schema.json, a document the old schema accepted that the new one refuses.rust: the public API ofwaiandwai-quantum, from rustdoc's JSON (cargo rustdoc -- -Z unstable-options --output-format jsonunderRUSTC_BOOTSTRAP=1), both sides documented with the toolchainrust-toolchain.tomlpins and every feature but those needing system libraries or downloads. The base is extracted withgit archiveto a scratch directory. Every public path is walked from the crate root through modules and re-exports: functions (signature with ids dropped and paths made canonical; parameter names not compared), structs and fields, enums and variants, traits and their items, inherent methods, trait implementations (auto traits in, blanket ones out), constants, statics, aliases, macros; plus the semver rules for exhaustive structs and enums and required trait items. A crate is documented only when something under it or its path dependencies changed.c:wai-rs/include/wai.hparsed (functions by return and parameter types, struct layouts, enums, macro values, preprocessor conditions).openapi: the room service'sopenapi.json(on PR #500's branch; read fromjoule-code/.../joule-moq-room/openapi.jsononce it lands, andwai/**/openapi*.json): routes, operationIds, parameters, required bodies, success responses and their properties.<category>:<surface>:<item>, what changed and why it breaks; additions are compatible. A break passes only when a line this change adds towai/CHANGELOG.md, in an entry that says "breaking", names the id in backticks. Exit 1 otherwise; the run prints the line to add..github/workflows/wai-breaking.yml: on every pull request touchingwai/**(and the room OpenAPI, the toolchain pin, this file): jobbreakingrunswai breaking --against origin/main; jobrunnerruns the tests below and then the corpora's independent Python verifiers (claim_verify.py,jwp_verify.py,signer_verify.py,python3 -I) and the reference packager (wai-pack policy-verdict) throughwai conform, andwai verifyon every record. Concurrency group per ref; therelevantgate reads the paths from the file (scripts/pr-touches-paths.py), as the other WAI workflows do.wai conform <kind> --suite <suite> --impl <command or URL>(operationconformance.run): one of WAI's corpora run against another implementation. Kinds and suites:claimclaim-conformance/vectors.jsonjwpjwp-conformance/vectors.jsonsignersigner-conformance/vectors.jsonscorescore-conformance/WSCRi16lePCM has the declaredpcm_sha256policypack-conformance/policy+refusals/policyok <digest>as declared, or the declared refusal reasonhttphttp-conformance/objects/Adapters: a command (
{corpus}whole-corpusname<TAB>verdict,{case},{in},{out}, else stdin/stdout; per-case--timeout), and HTTP (POSTper case, a JSONverdictmember or the first line; an origin is fetched from). The result is a signed recordconform-result/1(<suite>.wai-conform.json): corpus digest (SHA-256 oversha256 pathlines of every file read), implementation (command + SHA-256 of each file it names, or URL;--name/--version), each case's outcome, summary,conformant, the standard's version (CHANGELOG heading) and toolchain version; Ed25519 overwai:conform-result\x01‖ JCS(record withoutsig), by a run key or--key <seed file>.wai verifynow also checks*.wai-conform.json: the signature (strict), and that the summary andconformantare what the cases say.Both follow the agent rules:
envelope/1JSON with--jsonor off a terminal (--textkeeps text), exit statuses 0/1/2/4/5/6/7,--dry-runfor conform,--confirmbefore replacing a record. Registered as entries ofdx::VERBS(breaking,conform) until the operation catalogue lands;breaking::run(&Options) -> ReportandReport::to_json()are what an MCP toolcompat_checkcalls.conform-result/1in the standard (extensions/conformance-results.md): the record's members and corpus digest (§2, §2.1), its signing input (wai:conform-resultand0x01, then the record withoutsigin RFC 8785 form) and Ed25519 signature under SPEC §7's strict rule (§3), what a verifier checks and in what order (§4: it reads; its version; its members; its signature; its summary; its verdict) and the codes (§5). Registered as a document kind (registry export and REGISTRY.md regenerated,registry export --checkup to date); a SPEC §7 bullet points to it.wai-rsmoduleconform_result(featureconform_result, now inquickstart);wai conformsigns andwai verifychecks through it. It reads records with the crate's strict JSON reader (a member named twice, an unpaired surrogate, a number written other than as a plain integer are refused) and verifies withwai::sig::verify_strict.conform-result-conformance/: accepted records (conformant; not conformant; no cases; HTTP adapter with a supplied key) and refused ones (a case flipped after signing, the corpus digest changed, the summary alone changed, a bad signature,S + L, a small-order key, each summary count wrong alone and two at once, a verdict that disagrees, an unknown version, a member missing, added or named twice, a fractional count, an upper-case digest, bytes that are not JSON). Every outcome is stated in the generator from the text, and the generator asserts the reference reaches it before writing;wai_conform_result_vectors gen|verify|verdicts.conform_result_verify.py(standard library,python3 -I, WAI's strict Ed25519 rule fromclaim-conformance/_ed25519.py, its own JSON reading and RFC 8785 serialisation), which reaches every outcome, reads §5's table, and with--skip <check>fails the corpus for each check of §4 left out. CI step inwai-conformance.yml's independent-verifiers job (feature added to its union and to features-alone; the extension added to its paths).wai conformgains the verifier suiteconform-result, and the runner job runs the Python verifier through it.Measurements are not compared:
wai breakingleaves out an explicit list (breaking/corpora.rsMEASUREMENTS, one entry a file, each with its reason) and reports each such change as a note:wai/format-probe-conformance/provenance.json(the external decodes measured on one build host; no verifier reads it) andwai/live-conformance/metering/relay-record.wmhandrelay-ledger.wml(one live loopback run's recording; the vectors built around it invectors.jsonstay compared). A bin test requires every listed file to exist, once, with a reason. The corpora are otherwise deterministic by design (their READMEs state the same bytes on every machine), so the list is short.A command
wai conformruns is killed alone at its timeout, and its output is read for at most two seconds after it exits, so a child that keeps the output open does not hold the run. (An earlier commit killed the command's process group withkill -KILL -<pgid>; in the CI image that call signals the caller's own group, which killed the runner job with exit 137. Reproduced in that image and removed; the step peaks at 4.6 GB built cold under a 16 GB cap, so memory was not the cause.)tests/int_kernels_vectors.rs(every corpus has a decoded-output sidecar or a stated exclusion) listsconform-result-conformancewith its reason: signed records, no decoded output.Docs:
/docs/breaking/and/docs/conform/on the WAI site'sdocs-quickstartbranch (head0d217b2), each with a.mdtwin and an/llms.txtentry; the conform page links the record's specification. Not deployed.Design decisions
wai breakingreads the file.rustdoc-typesversion pin), both sides from one toolchain; a format-version mismatch refuses to compare.opsdeclaration it never had) are compatible; changed or removed ones break.quickstart).Checks
cargo test --locked --no-default-features --features quickstart,score,key_discovery --bin wai --test cli_breaking --test cli_conformcargo test --locked --no-default-features --features quickstart --bin wai --test floor --test cli_dx;cargo test --no-default-features --bin waicargo clippy --locked --no-default-features [--features quickstart | quickstart,score,key_discovery] --bin wai --testswai.rs,http*.rsare untouched)cargo build --locked --no-default-features --bin waiwai breaking --against <main> --text(all surfaces, wai documented twice)wai-quantum/src/lib.rs,--only rustwai conform verifier --suite {claim,jwp,signer} --impl "python3.11 -I wai/<s>-conformance/<s>_verify.py verdicts {corpus}"wai conform packager --suite policy --impl "wai-pack policy-verdict {in}"cli_conformcargo test --features conform_result --lib conform_result::and--test conform_result;wai_conform_result_vectors verify ../conform-result-conformance;python3 -I conform-result-conformance/conform_result_verify.pycargo test --lib codecs::registry::(wire identifiers in their documents);wai registry export --checkcargo test --no-default-features --features quickstart,score,key_discovery --bin wai --test cli_breaking --test cli_conform --test cli_dx --test conform_resultwai breaking --against <main> --text, every surfacenpm run build;pages.e2e /docs/conform/check-tests-in-ci.py,check-workflow-concurrency.sh,check-spec-refs.py,check-wai-section-refs.py,check-no-vanity-metrics.sh,test-public-text-checks.sh,check-comparative-prose.sh,check-orphan-sources.py, YAML parsecheck-public-names.shon every changed file; commit messages and this body withgrep -Enpm run build(routes, public text with the denylist, names);node e2e/pages.e2e.mjs /docs/breaking/ /docs/conform/Falsifications
On this repository against main (each: exit 1 without an acknowledgement, exit 0 with a
- **Breaking:** \`` line added):wai.audio.flacdropped from the registry export →wire:capability:wai.audio.flac; the magicWCT1dropped →wire:wire-identifier:magic/WCT1.live_events.rsSIGN_DOMAIN\x01→\x02→record:domain:wai/wai-rs/src/live_events.rs::SIGN_DOMAIN. And in code:access_services::signing_bytesappends a byte, the corpus regenerated withwai_access_services_vectors gen→record:corpus:wai/live-conformance/access/vectors.json;access_services_verify.pyrefuses the regenerated corpus (exit 1), and passes again once restored.energy_order::is_learnedmade private →api:rust:wai::energy_order::is_learned("the function fn is_learned(&str) -> bool is gone").wai_envelope_detect_version'slen: usize→u32inffi.rs, the header regenerated bybuild.rs→api:c:wai_envelope_detect_version(uintptr_t→uint32_t).GET /roomremoved →api:openapi:joule-moq-room:GET /room; a required query parameter and a changed operationId → two more findings. Against main, every route of the document reads as compatible.wai conform: a verifier that changes one verdict fails exactly that case (exit 1; Python claim verifier and Rust signer reference); a packager that changes one digest digit fails every admitted policy and nothing else; a renderer that adds one to one output byte fails every score case; an origin serving one object with one byte flipped fails that object ("the body is not the object"). A record with a failed case turned to a pass and its counts made to agree, one with a corpus-digest byte changed, and one with only its summary changed are each refused bywai verify(exit 1).Guards mutated in the code, each caught by its test: an entry acknowledging without the word "breaking"; the record's signature not checked; C parameter types not compared.
conform-result/1's verifiers (on Linux):wai verifyon each vector's record alone: every refused vector exits 1 at its stated step and reason, every accepted one exits 0; no mismatch.wai_conform_result_vectors verifyfails every time. The first run found the case-count check unguarded (every summary vector had two counts wrong); three vectors with one count wrong each were added, and the rerun caught it.conform_result_verify.py --skip <check>for each check of §4 (duplicates, format, members, signature, summary, verdict): the corpus fails each time, naming the vectors that guard it.The measurement list: a changed
provenance.jsonis a note and no finding (exit 0); with the list emptied, the same change iswire:corpus:wai/format-probe-conformance/provenance.json(exit 1).cli_breakingtests a listed file not compared and an unlisted vector beside it still breaking.Not done
relay(the MOQT interop cases live injoule-moq-interop; no runner adapter yet) andplayer(browser sink). Decoder suites other thanscore(audio needs its sources beside the scene; int-payload, neural, film and the others need their output forms named per capability).--format junit|html;--level,--independent,wai conformance gen;breaking.allowinwai.json.wai ops) and the MCP toolscompat_check/conformance_run: registration waits for those branches; both ops are separable modules.🤖 Generated with Claude Code
7e4be9405c4484518afd