joulecontract: the published negative vectors were never actually run #94
Merged
dcharlot
merged 2 commits from 2026-09-18 11:35:25 -04:00
joulecontract/run-negative-vectors into main 2026-09-17
joulecontract: the negative replay could pass with the pack missing
All checks were successful
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m56s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m19s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 3m37s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / aac-rs (pull_request) Successful in 52s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m7s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 2m54s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 2m34s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 2m3s
standards workspaces build + test / map-rs (pull_request) Successful in 2m24s
standards workspaces build + test / mesh-rs (pull_request) Successful in 1m52s
standards workspaces build + test / arl-rs (pull_request) Successful in 18m13s
standards workspaces build + test / openpay (pull_request) Successful in 6m30s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 24m22s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 40m4s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 12m31s
standards workspaces build + test / sense-rs (pull_request) Successful in 18m28s
The replay test opened with the same `if !dir.exists() { return; }` the other
two vector-pack tests had — and that early return fires *before* the
`replayed >= 11` guard at the end of the function. So a missing or moved pack
reported green, which is precisely the case that guard was written to catch. I
had described the test as anti-vacuous; with that return in place it was not.
`main` has since replaced the same pattern in `vector_pack_tests` with an
assertion, on the grounds that this crate is not published and so only ever runs
in-repo where the pack is always present. That reasoning applies unchanged here,
so the replay now asserts too, and the three pack tests in this file agree.
Verified in both directions rather than one:
- pack present: 10 passed
- pack moved aside: the replay FAILS, naming the path it could not find
Before this change the second case passed. The pack was restored and confirmed
byte-identical with `git status`, not by assumption.
Co-Authored-By: Claude Opus 5 <[email protected]>
joulecontract: the published negative vectors were never actually run
The pack had two tests: every file's content address against `MANIFEST.json`, and the positive DSSE envelope. Neither *executed* a negative vector. All eleven `negative-*.json` were published, digest-pinned, and never read by a verifier — so the vectors written to catch a verifier that stops demoting were exactly the ones that could not catch it. This replays each through `validate_sealed` under the context they were generated in, and compares all four recorded fields: the demotion list, the maximum profile, the rendered §15 claim string, and whether the claimed profile is permitted. **Equality, not "it demoted".** These are degradation vectors, not accept/reject ones. Each records the verdict a conformant verifier must produce — `metered_unattested` demotes Energy to `ModelBased`, `generator_unattested` caps Governance at `declared`. Asserting merely that something was demoted would pass a verifier that demoted the wrong axis, or by the wrong amount, or that refused the receipt outright — and refusing is not the specified behaviour either, so the replay also asserts each vector still validates structurally. **A wrong assumption the vectors corrected.** My first version asserted every negative vector must leave its claimed profile impermissible. Two do not: `recourse_undeclared` and `static_only_frame` record a *disclosure* demotion rather than a cap, and `static_only` is prohibited only at JR-4 while these vectors claim JR-3. The honest verdict there is "permitted, and here is what you are trusting". Asserting `!permits` would have pressured two published vectors toward a stricter reading than the spec takes, so the invariant is the demotion list instead. **Falsified, not assumed.** Removing the `EngagementUndisclosed` demotion rule from the verifier leaves the *entire* pre-existing workspace suite green, and fails this replay naming the vector and the axis. `StaticOnly`, `EngagementUndisclosed` and `IndependentKeyOnly` appear in no unit test in `joulecontract-receipt` at all — the published pack was their only coverage, and it was not running. (A probe against `MeteredUnattested` is *not* silent: two unit tests catch that one. The gap is uneven, which is itself worth knowing.) The replay asserts it saw at least eleven vectors, so a wrong path, a renamed prefix or an empty glob fails rather than passing having checked nothing — the same vacuity that let the original gap hide. It also pins the three context scalars it replays under, so a regenerated pack under a different context is a failure rather than a silent comparison between two different questions. `joulecontract-rs` is already in the CI matrix, so these vectors now run on every change. Verified: 15 test binaries green across the workspace, 1 new. The verifier crate is byte-identical to `main` — the falsification probes were reverted and the revert checked with `git diff --quiet`. Co-Authored-By: Claude Opus 5 <[email protected]>