Skip to content

Commit 2cb3822

Browse files
committed
docs(P-022): parity-work discipline + status reconciliation at fdcb222
Two halves of one change, deliberately together: the rules that say how to stop status drift, and the reconciliation that clears the drift those rules were learned from. ## Parity-work discipline (new section) Four rules, each paid for by a real defect during step 5a (#255, PRs #319/#320/#321), written wider than this port so they outlive P-022: 1. Oracle over reviewer prose — a finding is a hypothesis until reproduced against the reference; keep the observed behaviour, not the reviewer's explanation. (A review argued the word-boundary rule from single-ended probes; the reference builds a both-ended pattern. Right conclusion, wrong reason — implementing the reason would have been wrong.) 2. Mutation over plausible tests — a regression test is not evidence until the matching mutation fails it THROUGH the production surface it claims to protect. (The ordering replay sorted a parallel vector with a copy of the key; it would have passed against sort_unstable_by, the exact defect it existed for.) 3. No fail-fast during mutation campaigns — expose all catching layers, not the first failing target. (cargo test halts on the first target; with --no-fail-fast the same mutation showed three catchers, and a second mutation was caught only at the replay layer.) 4. Insertion-stable generated goldens — vocabulary-derived shape must depend on stable item identity, never ordinal position. Rule 4's law is the INVARIANT, not the mechanism: insert one synthetic vocabulary member existing-record churn == 0 new-record delta == 1 A content hash is today's way of satisfying it, not the requirement; any stable mapping conforms and swapping it is not a violation. Writing the hash into the norm would turn an implementation detail into scripture. Single home by design — not duplicated into AGENTS.execution-surfaces.md, because two copies of one law drift, which is what the status-drift rule exists to prevent. ## Status reconciliation Written fresh against the tree at fdcb222, NOT carried over from the earlier unmerged reconciliation: a stale block patched with a stale fix stays stale. The old branch is abandoned rather than cherry-picked. What was wrong on main: - #258 still described as "land with PR #297, in independent review; not on main yet" — closed completed, both spec documents on main. - own-lowered and own-bridge absent from the workspace list; both are members. - 5a listed as a future step — #255 closed completed today. - own-diagnostics described as the data-only layer — it now carries the full normalized contract. What it says now: checkpoint-level status with each open step separating its normative blocker from #250's preferred sequencing, #259 broken out per its own five checkpoints (2 and 3 complete with counts, 1 partial, 4 not started, 5 unblocked-but-not-done), and #260/#269 keeping the sliceable-vs-declarable distinction. Preferred queue recorded: #256 → #259 remaining → #260/#269. The proposals index row is corrected in the same change — a third status surface for the same fact, and leaving it stale would repeat the defect. #250 is a GitHub issue and cannot ride in a git commit; its body is synchronised in the same move, per its own rule that both surfaces change together. Docs only. No code, fixtures or acceptance touched. The executable guard for rule 4 currently implements `churn == 0`; extending it to `delta == 1`, plus three review nitpicks from #321, lands in the follow-up test PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CJF7MBi1ijU5m9cJVWgQsM
1 parent fdcb222 commit 2cb3822

2 files changed

Lines changed: 138 additions & 18 deletions

File tree

‎docs/proposals/P-022-rust-core-migration.md‎

Lines changed: 137 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,21 @@ rationale below is historical and unchanged; the live sequencing is the #250
66
child-issue DAG. Revised per the post-merge review in
77
[`docs/notes/p022-review-notes.md`](../notes/p022-review-notes.md).
88

9-
### Implementation status (reconciled after #214/#249 — see #250/#251)
10-
11-
**Implemented** (workspace members in `rust/Cargo.toml`, parity-gated by
9+
### Implementation status
10+
11+
> **Reconciled at `fdcb222`** against `rust/Cargo.toml`, the crate sources and
12+
> the child-issue states under #250. Written fresh from the tree, not carried
13+
> over from an earlier reconciliation — a stale status block patched with a
14+
> stale fix stays stale. Statuses are **checkpoint-level**, and each open step
15+
> separates its *normative* blocker (what its acceptance actually requires) from
16+
> #250's *preferred* sequencing (what order is cheapest); conflating those is
17+
> what let this table drift twice already.
18+
>
19+
> Keeping it true is a rule, not a habit: see
20+
> [Parity-work discipline](#parity-work-discipline) below, and the status-drift
21+
> rule in #250.
22+
23+
**Complete** (workspace members in `rust/Cargo.toml`, parity-gated by
1224
`scripts/oracle_exact.py` and the shared fixtures in `tests/fixtures/`):
1325

1426
- `own-ir` — OwnIR serde + schema round-trip (step 1);
@@ -17,24 +29,47 @@ child-issue DAG. Revised per the post-merge review in
1729
`tests/fixtures/cfg_parity.json` (steps 0/3; the seam the strategy below
1830
said "still needs building" **is built** — `python -m ownlang cfg --format
1931
json` + the `--write`-regenerated parity fixtures);
20-
- `own-diagnostics` — the data-only diagnostics layer;
2132
- `own-analysis` — the worklist solver + ownership/lifetime/buffer/effect/DI
2233
analyses (step 4; the **analysis-heart milestone**, completed in #214 /
2334
PR #249, replaying `diag_parity.json` and the DI/effect fact-parity
24-
fixtures).
25-
26-
**Next steps — each owned by exactly one child issue under #250:**
27-
28-
| Step | Deliverable | Issue |
35+
fixtures);
36+
- `own-diagnostics` — the verdict model **and** the full normalized diagnostic
37+
contract (step 5a, #255 **closed completed**, PRs #319/#320/#321): structural
38+
identity with a non-collapsing comparison key, canonical `render` /
39+
`render_pretty` text, the emission ordering contract, and a self-policing
40+
ledger covering all 47 `TITLES` codes;
41+
- `own-lowered` — the normalized Layer 2 document the bridge lowers into (the
42+
differential-testing representation named by #259 checkpoint 2);
43+
- `spec/Bridge.md` + `spec/BridgeBehaviorMatrix.md` — the normative bridge
44+
contract and its completeness ledger (step 6a, #258 **closed completed**;
45+
PR #297 merged, both documents on `main`).
46+
47+
**In progress — step 6b, `own-bridge` (#259).** It landed ahead of #250's
48+
*preferred* order ("preferably after #255/#256"); its *normative* blocker was
49+
#258 alone, which is satisfied. Per the checkpoints #259 itself defines:
50+
51+
| #259 checkpoint | Status | Evidence / what remains |
2952
|---|---|---|
30-
| 5a | diagnostic messages + ordered Evidence parity | #255 |
31-
| 5b | `.ownreport.json` + SARIF projection, canonical parity | #256 |
32-
| 5c | `own-codegen` (analysis-independent sibling) | #257 |
33-
| 6a | OwnIR **bridge semantics formalized** before the port | #258 (deliverable written — `spec/Bridge.md` + `spec/BridgeBehaviorMatrix.md` **land with PR #297**, in independent review; not on `main` yet) |
34-
| 6b | Rust `own-bridge`, layered OwnIR parity | #259 |
35-
| 7a | dual-engine shadow mode + zero-diff reproduction artifacts | #260 (supported by #269 — normalized `AnalysisTrace` + first-divergence minimizer) |
36-
| 7b | Rust `own-cli`: command/output/exit-code parity | #261 |
37-
| 8 | Rust-default **cutover**, rollback gate, Python distribution removal | #262 |
53+
| 1 — typed OwnIR validation | **partial** | `OwnIr::from_json` + the #294 OD-2 fail-loud unknown-kind rule. Full validation acceptance/rejection parity (fixture layer 1) is out of the current slice |
54+
| 2 — fact lowering | **complete** | `lower()` → `own_lowered`; **27/27** `rust_replay` cases in `tests/fixtures/lowered/manifest.json` byte-exact |
55+
| 3 — interprocedural MOS | **complete for the stage-1 domain** | `dump_summaries()` byte-identical to `python -m ownlang summaries` across **35** `*.summaries.json` goldens. Container-valued metadata is **outside** the declared scalar-metadata parity domain — a separate #294-class door decision, not a silent gap |
56+
| 4 — analysis wiring | **not started** | the crate states its own boundary: "no diagnostics, no analysis" |
57+
| 5 — full fact-to-verdict parity | **unblocked, not done** | its comparison set (message, severity, subject, resource kind, ordered Evidence) is now delivered by #255 — but the checkpoint still needs checkpoint 4's wiring to produce verdicts to compare |
58+
59+
**Open steps — each owned by exactly one child issue under #250:**
60+
61+
| Step | Deliverable | Issue | Status |
62+
|---|---|---|---|
63+
| 5a | diagnostic messages + ordered Evidence parity | #255 | **complete** — see above |
64+
| 5b | `.ownreport.json` + SARIF projection, canonical parity | #256 | **ready** — its normative blocker (#255) is satisfied. The **preferred next step**: it stays on the diagnostic/output seam #255 just finished, and completing it widens #259 checkpoint 5 to its full Layer-5 surface in one pass instead of two visits |
65+
| 5c | `own-codegen` (analysis-independent sibling) | #257 | **ready**, independent of the analysis path — parallelizable |
66+
| 6a | OwnIR **bridge semantics formalized** before the port | #258 | **complete** — see above |
67+
| 6b | Rust `own-bridge`, layered OwnIR parity | #259 | **in progress** — checkpoint table above |
68+
| 7a | dual-engine shadow mode + zero-diff reproduction artifacts | #260 (supported by #269) | **infrastructure sliceable now**, final acceptance **blocked by #259**. Buildable against the landed checkpoints: same-input OwnIR capture + hash, reproduction-artifact format, engine protocol, trace schema, stable-ID normalization, first-divergence reduction over the *lowered*/MOS layers. Not yet declarable as shadow mode: acceptance compares end diagnostics |
69+
| 7b | Rust `own-cli`: command/output/exit-code parity | #261 | blocked — needs the production bridge and the output surfaces |
70+
| 8 | Rust-default **cutover**, rollback gate, Python distribution removal | #262 | blocked by #260/#261 and final parity |
71+
72+
**Preferred queue:** #256 → #259 remaining (cp1 → cp4 → cp5) → #260/#269.
3873

3974
The Datalog/Ascent rule layer stays strictly **post-cutover** (strategy step 8
4075
below) and deliberately has no issue yet. Throughout: Python remains
@@ -561,6 +596,91 @@ hardening is what made the verdict seam cheap — and the CFG seam has since bee
561596

562597
Throughout, Python stays authoritative; the Rust crates light up behind the ratchet.
563598

599+
## Parity-work discipline
600+
601+
Four rules, each paid for by a real defect during step 5a (#255, PRs
602+
#319/#320/#321). They are written **wider than this port on purpose**: nothing
603+
below depends on Rust, on Python, or on the diagnostics layer, so they outlive
604+
P-022 and apply to the next migration that pins one implementation against
605+
another.
606+
607+
Scope: parity/migration work. This is the only home — the rules are not
608+
duplicated into `AGENTS.execution-surfaces.md`, because two copies of one law
609+
drift, which is the failure mode the status-drift rule already exists to stop.
610+
611+
### 1. Oracle over reviewer prose
612+
613+
**Rule.** A reviewer finding is a *hypothesis* until reproduced against the
614+
reference implementation. Preserve the observed oracle behaviour, not the
615+
reviewer's explanation of it.
616+
617+
**Why.** A reviewer — human or bot — can be right about *what* is wrong and
618+
wrong about *why*, and the explanation is what you would otherwise encode.
619+
620+
**Failure mode paid for.** A review argued the word-boundary rule from probes
621+
using single-ended patterns (`\b-foo`). The reference builds a **both-ended**
622+
pattern (`\b…\b`), which gives a different answer at the right edge. The
623+
conclusion was correct; the stated reason was not, and implementing the reason
624+
would have been wrong. Re-probing with the real pattern shape settled it.
625+
626+
### 2. Mutation over plausible tests
627+
628+
**Rule.** A test written to catch a specific regression is not evidence until
629+
the corresponding mutation makes it fail **through the production surface it
630+
claims to protect**.
631+
632+
**Why.** A test that has never failed has never been shown to test anything.
633+
"Through the production surface" is the load-bearing half: a test can exercise a
634+
private copy of the logic and pass while the public path rots.
635+
636+
**Failure mode paid for.** The ordering replay sorted a parallel vector with a
637+
*copy* of the sort key and then compared only `(line, code)` — which tied
638+
records share by construction. `ties_keep_emission_order` would have passed
639+
against `sort_unstable_by`, the exact defect it was written for. Driving the
640+
label order through the public helper made it real.
641+
642+
### 3. No fail-fast during mutation campaigns
643+
644+
**Rule.** Mutation validation must expose **all** independent catching layers,
645+
not merely the first failing test target.
646+
647+
**Why.** Stopping at the first failure tells you *a* test caught the mutation.
648+
It does not tell you which layers did and which silently would not have.
649+
650+
**Failure mode paid for.** `cargo test` halts after the first failing target. A
651+
mutation appeared to be caught by one unit test only; with `--no-fail-fast` the
652+
same mutation showed three catchers across unit and replay layers — and a second
653+
mutation, invisible in the first run, was caught only at the replay layer.
654+
655+
### 4. Insertion-stable generated goldens
656+
657+
**Rule.** When a fixture's shape is derived from a vocabulary, it must depend on
658+
**stable item identity**, never on ordinal position.
659+
660+
**Normative acceptance:**
661+
662+
```text
663+
insert one synthetic vocabulary member
664+
existing-record churn == 0
665+
new-record delta == 1
666+
```
667+
668+
The **invariant is the law**. A content hash is today's way of satisfying it in
669+
`tests/test_diag_ledger_fixtures.py`, not the requirement — any stable mapping
670+
that holds the two lines above conforms, and swapping the mapping is not a
671+
violation. (Whatever is chosen must be reproducible across processes: Python's
672+
`hash()` randomises string hashing per run and cannot be used.)
673+
674+
**Why.** A vocabulary-derived golden exists to make *adding a member* legible.
675+
If insertion rewrites unrelated records, the one diff a reviewer needs is buried
676+
in churn, and a genuine change to the generator hides inside it.
677+
678+
**Failure mode paid for.** The ledger rotated case shapes by sorted index.
679+
Inserting one code (`DI006`) rewrote **42 of 47** existing records — measured,
680+
not estimated. The docstring meanwhile claimed the shapes were "deterministic,
681+
so the golden stays stable", true only while the vocabulary never changed, which
682+
is the single event the ledger exists to expose.
683+
564684
## Open questions (to resolve as we go)
565685

566686
- **Parser**: hand-roll vs `chumsky`/`winnow`. Leaning hand-roll for golden-parity of

‎docs/proposals/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ proposal is marked `done` with a pointer.
4141
| [P-017](P-017-multi-stack-frontends.md) | Multi-stack frontends (OwnTS / OwnJVM: OwnJava + OwnKotlin) | draft |
4242
| [P-020](P-020-ownts-react-effects.md) | OwnTS React effects profile (`Own.React`) — the effect-storm angle | draft |
4343
| [P-021](P-021-async-audit-pack.md) | Async audit pack (`Own.Async`) | draft |
44-
| [P-022](P-022-rust-core-migration.md) | Rust core migration: crate DAG, patterns, prior art, differential oracle (Python = golden) | in execution — steps 0–4 built (`own-ir`/`own-syntax`/`own-cfg`/`own-diagnostics`/`own-analysis`, #214/#249); remaining steps = the #250 child-issue DAG (#255–#262); Python authoritative until cutover |
44+
| [P-022](P-022-rust-core-migration.md) | Rust core migration: crate DAG, patterns, prior art, differential oracle (Python = golden) | in execution — steps 0–4 built (#214/#249); step 5a done (full diagnostic contract, #255 via #319/#320/#321); step 6a done (`spec/Bridge.md`, #258); step 6b underway (`own-lowered`/`own-bridge`, #259: lowering + MOS parity landed, analysis wiring open); next ready step 5b (#256); Python authoritative until cutover |
4545
| [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft |
4646
| [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft |
4747
| [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) |

0 commit comments

Comments
 (0)