You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 59fdf31
Browse filesBrowse the repository at this point in the historyBrowse files
feat(shadow-infra): the AnalysisTrace — normalize the identifiers, declare the order (#260/#269 cp3)
P-022 step 7a, checkpoint 3. Still INFRASTRUCTURE for shadow mode, no parity
claim, and still not a comparison: the trace is the SHAPE a comparison would
need, and producing it is not performing one.
Checkpoint 2 left two captures side by side. Two things stand between that
pairing and a comparison, and only one should be removed:
* Internal identifiers. The Layer 2 handles are minted from global counters in
document order (BR-L2) — positions wearing the costume of names. Measured on
handles_global_counters, reversing the component list turns
cap_0/sub_1/sub_2/cap_3 into sub_0/cap_1/cap_2/sub_3: four unchanged facts,
eight names, none shared. A raw comparison would report every handle as a
difference and bury the one real one.
* Order, which must NOT be normalized away: document and lowering order is
semantic (BR-D4/BR-L5), and BR-V8 leaves verdict ties in construction order.
So the trace normalizes the identifiers and DECLARES the order.
* Stable ids are `component|file|line|event|handler`, rebuilt from the record
the bridge attached to the handle; every occurrence is rewritten; the rename
is a bijection and total, both asserted. The mint KIND is preserved as
`mint`, so a routing difference stays a comparable value on one step instead
of splitting into a pair of "only in one engine" addresses.
* `order` is `significant` for lowered and verdicts, `canonical` for
summaries — read by a comparison to CLASSIFY an ordering difference, never
to license a sort.
* Both sides project BOTH engines: projecting a capture is not authoring it,
and doing it twice is what cross-checks the normalization itself.
Proven on the property it exists for, over the whole captured corpus: a
permutation that reshuffles the mint counters must not move a stable id, and
must still change the lowered layer's step order.
FINDING: the two implementations disagreed, and the disagreement was in the
schema. For two functions named `Take`, the reference addressed the second as
functions[Take~1] and the port as functions[Take]~1 — both faithful readings,
and the reference was inconsistent with itself (inside the bracket for
functions, outside for every other list). Resolved by making the rule explicit
and uniform: inside the bracket, everywhere, so a nested prefix composes.
A single implementation would have shipped the inconsistency. A second defect
surfaced from this family's own step-id control: the function disambiguator
reset per function, so two `Take`s collided on one address.
Campaign: round 1 was 10 caught, 1 survivor. M51 guards a state the corpus
cannot reach, so it needed a synthetic unit-level control (added both sides) —
and then still survived, because the campaign never RAN the layer that catches
it. A campaign that does not run a layer cannot see it catch. A `rust-unit`
layer was added to all three campaigns and all three re-run: 30/30, 11/11,
11/11, controls clean.
Gates: cargo fmt --check, cargo clippy --workspace --all-targets (zero
warnings), cargo test --workspace --no-fail-fast, ruff check ., mypy,
python tests/run_tests.py.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kh6wpfbBUNLbeCJZ263gYM
@@ -159,8 +183,8 @@ that each recorded result is internally consistent
159
183
| M16 | the port's canonical form stops sorting object keys |`rust::every_committed_artifact_round_trips_and_verifies`, `rust::every_shared_document_hashes_to_the_recorded_digest`, `rust::this_engine_reproduces_its_committed_capture`, `rust::verify_refuses_each_structural_violation`|
160
184
| M17 | the port escapes U+007F, which the reference emits raw |`rust::every_committed_artifact_round_trips_and_verifies`, `rust::every_shared_document_hashes_to_the_recorded_digest`, `rust::this_engine_reproduces_its_committed_capture`|
161
185
| M18 | the port escapes control code points with uppercase hex |`rust::every_committed_artifact_round_trips_and_verifies`, `rust::every_shared_document_hashes_to_the_recorded_digest`, `rust::this_engine_reproduces_its_committed_capture`|
162
-
| M19 | the port renders the artifact with a different indent width |`rust::every_committed_artifact_round_trips_and_verifies`, `rust::the_canonical_form_ignores_only_insignificant_text_formatting`|
163
-
| M20 | the port renders an artifact's objects in sorted rather than document order |`rust::every_committed_artifact_round_trips_and_verifies`, `rust::the_canonical_form_ignores_only_insignificant_text_formatting`|
186
+
| M19 | the port renders the artifact with a different indent width |`rust::every_committed_artifact_round_trips_and_verifies`, `rust::every_trace_golden_is_reproduced_byte_for_byte`, `rust::the_canonical_form_ignores_only_insignificant_text_formatting`|
187
+
| M20 | the port renders an artifact's objects in sorted rather than document order |`rust::every_committed_artifact_round_trips_and_verifies`, `rust::every_trace_golden_is_reproduced_byte_for_byte`, `rust::the_canonical_form_ignores_only_insignificant_text_formatting`|
164
188
| M21 | the port resolves a duplicate key first-wins instead of last-wins |`rust::the_canonical_form_ignores_only_insignificant_text_formatting`|
165
189
| M22 | the port wraps an out-of-i64 integer instead of refusing it |`rust::every_declared_unnameable_document_is_refused`, `rust::values_outside_the_canonical_domain_are_refused_at_parse`|
166
190
| M23 | the port accepts a float as an integer instead of refusing it |`rust::every_declared_unnameable_document_is_refused`, `rust::values_outside_the_canonical_domain_are_refused_at_parse`|
@@ -196,3 +220,28 @@ that each recorded result is internally consistent
196
220
| M39 | the port reports a typed-door refusal on one layer instead of all three |`rust-engine::this_engine_reproduces_its_committed_capture`|
197
221
| M40 | the port's verification stops rejecting a 'full' projection that names members |`rust-repro::verify_refuses_each_structural_violation`|
198
222
| M41 | the port's verification accepts an EMPTY reason on a partial projection |`rust-repro::verify_refuses_each_structural_violation`|
223
+
### checkpoint 3 — AnalysisTrace and stable-ID normalization (`p022-shadow-cp3`)
224
+
225
+
|||
226
+
|---|---|
227
+
| mutations |**11**|
228
+
| caught |**11**|
229
+
| survived |**0**|
230
+
| compile errors (reported as such, never as "caught") | 0 |
231
+
| harness-honesty controls reporting zero failures | 1 |
| M42 | the reference derives a handle's stable id from the counter instead of the record's identity |`python::non-zero exit with no FAIL line`|
238
+
| M43 | the reference drops `line` from a handle's identity, fusing two facts on one line-distinct site |`python::trace-golden`|
239
+
| M44 | the reference stops disambiguating a repeated address |`python::trace-golden`, `python::trace-shape`|
240
+
| M45 | the reference declares the lowered layer's order canonical, licensing a sort |`python::trace-golden`|
241
+
| M46 | the reference declares the verdict layer's order canonical, hiding a tie-order defect |`python::trace-golden`|
242
+
| M47 | the reference narrows the minted-handle pattern so `loc_` names leak through unrewritten |`python::trace-golden`, `python::trace-normalization`|
243
+
| M48 | the reference gives a refused layer an empty step list AND drops its error |`python::trace-golden`|
244
+
| M49 | the port derives a handle's stable id from the counter instead of the record's identity |`rust-trace::a_mint_order_shift_moves_the_order_but_not_the_stable_ids`, `rust-trace::every_trace_golden_is_reproduced_byte_for_byte`, `rust-unit::trace::tests::a_fully_listed_document_normalizes`|
245
+
| M50 | the port declares the lowered layer's order canonical, licensing a sort |`rust-trace::every_trace_golden_is_reproduced_byte_for_byte`|
246
+
| M51 | the port stops asserting that the handle rewrite is total |`rust-unit::trace::tests::a_handle_reference_the_rename_cannot_reach_is_refused`|
247
+
| M52 | the port drops the mint kind from the handle record |`rust-trace::every_trace_golden_is_reproduced_byte_for_byte`, `rust-unit::trace::tests::a_fully_listed_document_normalizes`|
Copy file name to clipboardExpand all lines: docs/notes/p022-shadow-infra-checkpoint1-data/campaign.json
+4-1Lines changed: 4 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,8 @@
4
4
"comment": "A RECORDED run, not a CI gate: mutating the tree and running every layer takes minutes and cannot be part of the per-commit gates. What IS gated is the campaign DEFINITION (every anchor still resolves) and this file's internal consistency — see tests/test_generated_docs.py.",
Copy file name to clipboardExpand all lines: docs/notes/p022-shadow-infra-checkpoint1-data/mutations.json
+14-1Lines changed: 14 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
{
2
2
"campaign": "p022-shadow-cp1",
3
-
"comment": "The mutation campaign for P-022 step 7a checkpoint 1 (shadow-mode infrastructure layer 0: the same-input capture and the reproduction artifact). Every mutation edits a PRODUCTION surface — ownlang/repro.py or rust/crates/own-shadow/src/ — never a test, because P-022 discipline 2 makes a test evidence only once its mutation fails through the surface it claims to protect. Both layers run for every mutation (discipline 3: no fail-fast), so the recorded result names every catching layer, not the first. M00 is the harness-honesty control. Run: python scripts/mutate_campaign.py docs/notes/p022-shadow-infra-checkpoint1-data/mutations.json --out docs/notes/p022-shadow-infra-checkpoint1-data/campaign.json Re-anchored when checkpoint 2 reshaped the layer envelope: M11 and M15 lost their anchors, which the --check gate caught before the recorded result could go on describing a tree that no longer exists.",
3
+
"comment": "The mutation campaign for P-022 step 7a checkpoint 1 (shadow-mode infrastructure layer 0: the same-input capture and the reproduction artifact). Every mutation edits a PRODUCTION surface — ownlang/repro.py or rust/crates/own-shadow/src/ — never a test, because P-022 discipline 2 makes a test evidence only once its mutation fails through the surface it claims to protect. Both layers run for every mutation (discipline 3: no fail-fast), so the recorded result names every catching layer, not the first. M00 is the harness-honesty control. Run: python scripts/mutate_campaign.py docs/notes/p022-shadow-infra-checkpoint1-data/mutations.json --out docs/notes/p022-shadow-infra-checkpoint1-data/campaign.json Re-anchored when checkpoint 2 reshaped the layer envelope: M11 and M15 lost their anchors, which the --check gate caught before the recorded result could go on describing a tree that no longer exists. A `rust-unit` layer runs the crate's own unit tests: without it a rule that is only reachable off the production path (a defensive assertion the corpus cannot trigger) has its catcher invisible to the campaign, and the mutation reads as a survivor when the control exists and works. Found exactly that way on cp3's M51.",
Copy file name to clipboardExpand all lines: docs/notes/p022-shadow-infra-checkpoint2-data/mutations.json
+14-1Lines changed: 14 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
{
2
2
"campaign": "p022-shadow-cp2",
3
-
"comment": "The mutation campaign for P-022 step 7a checkpoint 2 (the engine protocol: how each engine reports its per-layer outputs, and what it declares it could produce). Separate from the cp1 campaign on purpose — each checkpoint's evidence stays frozen at what it measured, so a later checkpoint cannot quietly restate an earlier one's numbers. Every mutation edits a PRODUCTION surface — ownlang/repro.py or rust/crates/own-shadow/src/ — never a test. Three layers run for every mutation (discipline 3: no fail-fast), including the port's own engine suite. M00 is the harness-honesty control. Run: python scripts/mutate_campaign.py docs/notes/p022-shadow-infra-checkpoint2-data/mutations.json --out docs/notes/p022-shadow-infra-checkpoint2-data/campaign.json",
3
+
"comment": "The mutation campaign for P-022 step 7a checkpoint 2 (the engine protocol: how each engine reports its per-layer outputs, and what it declares it could produce). Separate from the cp1 campaign on purpose — each checkpoint's evidence stays frozen at what it measured, so a later checkpoint cannot quietly restate an earlier one's numbers. Every mutation edits a PRODUCTION surface — ownlang/repro.py or rust/crates/own-shadow/src/ — never a test. Three layers run for every mutation (discipline 3: no fail-fast), including the port's own engine suite. M00 is the harness-honesty control. Run: python scripts/mutate_campaign.py docs/notes/p022-shadow-infra-checkpoint2-data/mutations.json --out docs/notes/p022-shadow-infra-checkpoint2-data/campaign.json A `rust-unit` layer runs the crate's own unit tests: without it a rule that is only reachable off the production path (a defensive assertion the corpus cannot trigger) has its catcher invisible to the campaign, and the mutation reads as a survivor when the control exists and works. Found exactly that way on cp3's M51.",
"comment": "A RECORDED run, not a CI gate: mutating the tree and running every layer takes minutes and cannot be part of the per-commit gates. What IS gated is the campaign DEFINITION (every anchor still resolves) and this file's internal consistency — see tests/test_generated_docs.py.",
5
+
"layers": [
6
+
"python",
7
+
"rust-repro",
8
+
"rust-engine",
9
+
"rust-trace",
10
+
"rust-unit"
11
+
],
12
+
"totals": {
13
+
"mutations": 11,
14
+
"caught": 11,
15
+
"survived": 0,
16
+
"compile_errors": 0,
17
+
"control_clean": 1
18
+
},
19
+
"mutations": [
20
+
{
21
+
"id": "M00",
22
+
"what": "harness-honesty control: no mutation at all, which must report zero failing layers",
23
+
"rule": "campaign hygiene",
24
+
"status": "control_clean",
25
+
"caught_by": []
26
+
},
27
+
{
28
+
"id": "M42",
29
+
"what": "the reference derives a handle's stable id from the counter instead of the record's identity",
30
+
"rule": "trace: internal identifiers are normalized away by IDENTITY, never by position",
31
+
"status": "caught",
32
+
"caught_by": [
33
+
"python::non-zero exit with no FAIL line"
34
+
]
35
+
},
36
+
{
37
+
"id": "M43",
38
+
"what": "the reference drops `line` from a handle's identity, fusing two facts on one line-distinct site",
39
+
"rule": "trace: the rename is a BIJECTION — it may not fuse two facts into one address",
40
+
"status": "caught",
41
+
"caught_by": [
42
+
"python::trace-golden"
43
+
]
44
+
},
45
+
{
46
+
"id": "M44",
47
+
"what": "the reference stops disambiguating a repeated address",
48
+
"rule": "trace: a duplicate address takes a ~<n> suffix, inside the bracket",
49
+
"status": "caught",
50
+
"caught_by": [
51
+
"python::trace-golden",
52
+
"python::trace-shape"
53
+
]
54
+
},
55
+
{
56
+
"id": "M45",
57
+
"what": "the reference declares the lowered layer's order canonical, licensing a sort",
58
+
"rule": "trace: order is DECLARED, never normalized away — lowered order is semantic (BR-D4/BR-L5)",
59
+
"status": "caught",
60
+
"caught_by": [
61
+
"python::trace-golden"
62
+
]
63
+
},
64
+
{
65
+
"id": "M46",
66
+
"what": "the reference declares the verdict layer's order canonical, hiding a tie-order defect",
67
+
"rule": "trace: BR-V8 leaves ties in construction order, so position carries information",
68
+
"status": "caught",
69
+
"caught_by": [
70
+
"python::trace-golden"
71
+
]
72
+
},
73
+
{
74
+
"id": "M47",
75
+
"what": "the reference narrows the minted-handle pattern so `loc_` names leak through unrewritten",
76
+
"rule": "trace: the rename is TOTAL — no counter-shaped name survives",
77
+
"status": "caught",
78
+
"caught_by": [
79
+
"python::trace-golden",
80
+
"python::trace-normalization"
81
+
]
82
+
},
83
+
{
84
+
"id": "M48",
85
+
"what": "the reference gives a refused layer an empty step list AND drops its error",
86
+
"rule": "trace: a refused layer carries its error and no steps",
87
+
"status": "caught",
88
+
"caught_by": [
89
+
"python::trace-golden"
90
+
]
91
+
},
92
+
{
93
+
"id": "M49",
94
+
"what": "the port derives a handle's stable id from the counter instead of the record's identity",
95
+
"rule": "trace: internal identifiers are normalized away by IDENTITY, never by position",
0 commit comments