The shared disposition gate now refuses four representations a stored row could carry before: reasons missing or null on an outcome, outcomeId as "" or null on a kind that admits none, triggeredBy as [] or null beside "state": "none", and a retained exception-escalation reason without the requested handoff §8.1 makes it. Every reader of an expected disposition inherits that, and those readers are where a person meets it: packs test and experimental_test_packs (internal/evaluation/corpus.go:454), the graph matrix's headline and expectedNodes rows through experimental graph test and experimental_test_graphs (internal/graph/rows.go:521 and :634), and the coverage and profile derivations that witness probes from those rows (internal/project/coverage.go:106, internal/graph/coverage.go:50). internal/evaluation/corpus_exact_test.go holds the decoder itself, and the CHANGELOG Unreleased note and the experimental_test_packs description already record the tightening; what is missing is a test at those surfaces, so a refactor that gave one reader a decoder of its own re-admits the old shapes with every test green.
Add a project-matrix regression beside TestMatrixRowsAreComparedByCanonicalBytesAndErrorClass (internal/project/project_test.go:626): a stored row whose expectation carries triggeredBy: [] beside "state": "none" is a mismatch whose detail names the rule §8.3 states — "triggeredBy — present if and only if state is requested" — while the same row without that member passes. Add a graph-row regression over the fixture project at internal/graph/testdata/project, whose onboarding graph and onboarding.rows.json the graph tests already use, in the headline position and in expectedNodes. Then extend TestMatrixCoverageRefusesIllegalWitnesses (internal/project/project_test.go:1000) and TestGraphCoverageRefusesIllegalWitnesses (internal/graph/graph_test.go:919), whose present illegal shapes were illegal before this change too, with one newly refused shape, so such a row witnesses nothing and its probe stays missing.
One thing to raise rather than settle here: packs validate reports matrix: passed and exits 0 for exactly such a row, because a matrix loads without decoding its expectations (internal/project/validate.go:305-310), and packs test then reports the same row as a mismatch. Whether the matrix check should decode is a separate decision; pinning today's behaviour is enough for this change.
The shared disposition gate now refuses four representations a stored row could carry before:
reasonsmissing or null on anoutcome,outcomeIdas""or null on a kind that admits none,triggeredByas[]or null beside"state": "none", and a retainedexception-escalationreason without the requested handoff §8.1 makes it. Every reader of an expected disposition inherits that, and those readers are where a person meets it:packs testandexperimental_test_packs(internal/evaluation/corpus.go:454), the graph matrix's headline andexpectedNodesrows throughexperimental graph testandexperimental_test_graphs(internal/graph/rows.go:521and:634), and the coverage and profile derivations that witness probes from those rows (internal/project/coverage.go:106,internal/graph/coverage.go:50).internal/evaluation/corpus_exact_test.goholds the decoder itself, and the CHANGELOGUnreleasednote and theexperimental_test_packsdescription already record the tightening; what is missing is a test at those surfaces, so a refactor that gave one reader a decoder of its own re-admits the old shapes with every test green.Add a project-matrix regression beside
TestMatrixRowsAreComparedByCanonicalBytesAndErrorClass(internal/project/project_test.go:626): a stored row whose expectation carriestriggeredBy: []beside"state": "none"is a mismatch whose detail names the rule §8.3 states — "triggeredBy— present if and only ifstateisrequested" — while the same row without that member passes. Add a graph-row regression over the fixture project atinternal/graph/testdata/project, whoseonboardinggraph andonboarding.rows.jsonthe graph tests already use, in the headline position and inexpectedNodes. Then extendTestMatrixCoverageRefusesIllegalWitnesses(internal/project/project_test.go:1000) andTestGraphCoverageRefusesIllegalWitnesses(internal/graph/graph_test.go:919), whose present illegal shapes were illegal before this change too, with one newly refused shape, so such a row witnesses nothing and its probe stays missing.One thing to raise rather than settle here:
packs validatereportsmatrix: passedand exits 0 for exactly such a row, because a matrix loads without decoding its expectations (internal/project/validate.go:305-310), andpacks testthen reports the same row as a mismatch. Whether the matrix check should decode is a separate decision; pinning today's behaviour is enough for this change.