Skip to content

measurement: does the suite notice when a conformance check breaks - #57

Merged
imran-siddique merged 3 commits into
mainfrom
contrib/regression-guard-measurement
Aug 10, 2026
Merged

measurement: does the suite notice when a conformance check breaks#57
imran-siddique merged 3 commits into
mainfrom
contrib/regression-guard-measurement

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

Refs #53. Authored by @lywinged, from their branch lywinged/trace-tests:measurement, rebased onto current main. Their commit and authorship are intact. Opened as a maintainer because the branch had no PR.

What it measures. Not whether the checks are right, they are. Whether a regression inside a conformance module would be caught. That distinction matters more here than in ordinary code: this repository's output is a conformance stamp, so a broken check does not produce a wrong answer, it produces a missing question, and implementations keep getting stamped without the property being checked.

mutate_modules.py rewrites one Finding(..., Status.FAIL, ...) site at a time so it can never fail, runs the suite, restores, and verifies the restore before continuing. It refuses to start on a non-green baseline, refuses a rewrite that did not change the file, and refuses an empty site list.

I reproduced the numbers on current main rather than taking them from the report:

checks measured : 18
verified        : 15
unverified      :  3  ['TR-SIG-002', 'TR-TXN-001', 'TR-TXN-002']
margin 1 only   :  7
sites           : 23 of 33 guarded, 10 unguarded

All three TR-SIG-002 and TR-TXN-001 sites are unguarded. TR-TXN-001 is the only place the Level 2 tool-transcript requirement is enforced, so today that requirement could stop being checked and every test would still pass.

Why mutation rather than reading the tests, which is the part worth keeping: grepping test files for TR-xxx-nnn codes and diffing against emissions reports 12 unguarded checks, mutation reports 3. Tests exercise modules through check(...) and assert on findings without naming codes, so naming is a convention and changing outcome is the property. It cuts the other way too: five files mention tool_transcript, which reads as coverage, but one re-implements the digest check inline, two assert on the JSON schema, and one only asserts the module ran.

Scope. This lands the instrument and its report under measurement/, not the ten missing tests and not a CI gate. Wiring it into CI is a separate decision: it takes about a minute to run, and it should probably ratchet rather than block on first failure.

🤖 Generated with Claude Code

Fork-only. Nothing under the existing tree is modified; this adds one directory.

For every Finding(..., Status.FAIL, ...) site in the conformance modules, rewrite
it so the check can never fail, run the suite, count how many tests notice, restore.
23 of 33 failure paths are guarded. Ten are not, including all three of TR-TXN-001,
which is the only enforcement of the Level 2 tool-transcript requirement since
tool_transcript is absent from the schema's required array.

None of the checks is wrong. This measures whether a regression would be caught.

Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
@lywinged

Copy link
Copy Markdown
Contributor

Thank you for opening it, and the stale-install finding is a real hole rather than an environment quirk. It belongs in the same list as the three guards the docstring already claims, and it is the one that would have embarrassed the measurement rather than merely broken it.

The three existing guards all defend against a mutation that did not take effect: a red baseline, a rewrite that changed nothing, an empty site list. Yours is the same failure one layer down — the mutation takes effect on a tree the suite never imports. The symptom is identical to the strongest possible result, every site reporting that nothing noticed, and 0 of 18 verified is exactly what a suite with no guarded checks at all would print. An instrument that cannot separate I measured nothing from there is nothing to measure has to refuse rather than report.

The guard, in the same shape as the others, checked before the baseline rather than inferred after:

def assert_suite_imports_this_checkout() -> None:
    """The suite must import the tree being mutated, not another copy of it.

    The guards above defend against a mutation that did not take effect. This one
    defends against it taking effect on a tree the suite never imports: an editable
    install pointing elsewhere, a wheel earlier on sys.path, a stale .pth. The symptom
    is indistinguishable from a perfect result at the other end, so it is a precondition
    and not a diagnosis.
    """
    probe = subprocess.run(
        [sys.executable, "-c",
         "import pathlib, trace_tests; print(pathlib.Path(trace_tests.__file__).resolve())"],
        cwd=TRACE_TESTS, capture_output=True, text=True,
    )
    if probe.returncode != 0:
        sys.exit(
            "could not import trace_tests with the interpreter that runs the suite:\n"
            + probe.stderr.strip()
        )
    imported = Path(probe.stdout.strip())
    expected = (TRACE_TESTS / "src" / "trace_tests").resolve()
    if not (expected == imported.parent or expected in imported.parents):
        sys.exit(
            f"the suite imports trace_tests from {imported.parent}\n"
            f"but this run mutates            {expected}\n"
            "Every site would report that nothing noticed, which is also what a suite "
            "with no guarded checks looks like. Reinstall with `pip install -e .` from "
            "this checkout, or run with PYTHONPATH=src."
        )

It uses sys.executable and cwd=TRACE_TESTS deliberately, so the probe resolves the import the same way run_suite() does rather than the way this script happens to have been launched. I have tested the path comparison against a correct checkout, a submodule inside it, and an install elsewhere; I have not been able to run it against a real stale install, so if your reproduction is easy to recreate it is worth pointing at it once.

enum_drift.py has the same exposure and no equivalent check, so the guard should be called from both rather than living in one.

On delivery: contrib/regression-guard-measurement is on this repository and I have read access only, so I cannot push to it. Say which you prefer and I will do that one — you take the patch above into your branch, or I open a small separate PR against main after this merges. I am not opening one unasked while your branch is live, since that would only give you a conflict to resolve.

imran-siddique and others added 2 commits August 9, 2026 21:26
…nverified

The harness in measurement/ reported ten of thirty-three Finding(..., FAIL, ...)
sites at margin zero: rewriting any of them so it could never fail left the whole
suite green. Landing the instrument without the tests would have published a gap
list against our own conformance suite and fixed nothing.

tr_txn and tr_sca had no unit tests at all, which is why every one of their
failure paths measured zero. TR-TXN-001 carried the most weight: it is the only
place the Level 2 tool-transcript requirement is enforced anywhere in the suite.

New: tests/unit/test_tr_txn.py, tests/unit/test_tr_sca.py. Extended:
test_tr_sig.py with the two check_cmcp_runtime key-shape paths, the unsupported
kty path, and the signature-present-but-uncheckable path; test_tr_anc.py with a
URI that raises during parsing rather than parsing to a wrong scheme.

The two TR-SIG-002 vectors are deliberately separable: an implementation that
checks kty and crv without checking x passes one and fails the other, which is
what makes them two vectors rather than one written twice.

Re-measured: 18 of 18 checks and 33 of 33 sites verified, none unguarded. Eight
checks sit at margin 1, which the report now names as the number to watch.

REPORT.md and README.md updated to describe the state they measured rather than
the state they found.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bool is an int subclass, so call_count: true passed the non-negative integer
check and reported as a call count of one, false as zero. A record that says
true there is malformed, and a conformance suite that silently reads it as a
number is answering a question nobody asked.

Found by writing the TR-TXN-002 guard test, so the test and the fix land
together. Same class as the issued_at case in agentrust-io/trace-spec#146.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@imran-siddique
imran-siddique merged commit 42e7c6a into main Aug 10, 2026
6 checks passed
@imran-siddique
imran-siddique deleted the contrib/regression-guard-measurement branch August 10, 2026 22:15
imran-siddique pushed a commit that referenced this pull request Aug 12, 2026
…#59)

The three guards this script already documents defend against a mutation that
did not take effect: a red baseline, a rewrite that changed nothing, an empty
site list. This is the same failure one layer down. The mutation takes effect,
and the suite runs against a tree that never sees it.

Reported on #57: run against a stale editable install, the harness returned 0
of 18 verified while rewriting the checkout's modules correctly the whole time,
because pytest was importing `trace_tests` from somewhere else. Nothing fails,
every site reports that nothing noticed, and that reads exactly like a suite
with no guarded checks at all. An instrument that cannot separate "I measured
nothing" from "there is nothing to measure" has to decline rather than report.

`assert_suite_imports_this_checkout` probes through the same interpreter and
working directory `run_suite` uses, so it resolves the import the way the
measurement will rather than the way the script happened to be launched, and it
runs before the baseline rather than diagnosing afterwards.

Verified both directions: against this checkout it passes and exits 0; against a
second checkout while the install still points at the first it exits 1 with both
paths named. The full run is unchanged at 18 of 18 verified, 0 unverified, 8 at
margin 1.

Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
imran-siddique pushed a commit that referenced this pull request Aug 14, 2026
…62)

Closes #53.

#57 closed the first half of that issue: the ten unguarded conformance
failure paths are now guarded, 33 of 33 sites verified. The second half was
still open - tests/test_level0.py:7 restates the runtime.platform enum and
holds nine of the schema's ten values, missing software-only.

That is the value docs/trust-levels.md requires for Level 0. A Level 0 record
as documented would fail test_runtime_platform_registered; nothing noticed
because valid_level0.json uses intel-tdx.

The value is added, and so is the general guard. Five set literals in this
repository restate a schema enum:

    tr_rte._VALID_PLATFORMS         runtime.platform
    tr_pol._VALID_ENFORCEMENT       policy.enforcement_mode
    test_level0.VALID_PLATFORMS     runtime.platform
    test_level0.VALID_ENFORCEMENT   policy.enforcement_mode
    test_level0.VALID_APPRAISAL     appraisal.status

tests/test_enum_parity.py asserts each against schemas/trace-claim.json on
every run, by equality rather than containment: a superset means the copy
accepts a value the schema does not define, which is the same drift pointing
the other way. Load-bearing, checked rather than assumed - removing
software-only again fails exactly that parametrized case.

measurement/scripts/enum_drift.py already reported this and exits 1. It walks
the AST, which is what finds a copy nobody has listed yet; this file is the
other half, so a known copy fails in the suite rather than waiting for someone
to run the script.

167 pass. ruff reports the same 68 pre-existing errors either side of this
change and none in the new file.

Signed-off-by: lywinged <48041247+lywinged@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants