From 2cb38221089fadc4cd515c528f505bb1ff546587 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 08:32:24 +0000 Subject: [PATCH 1/2] docs(P-022): parity-work discipline + status reconciliation at fdcb222 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01CJF7MBi1ijU5m9cJVWgQsM --- docs/proposals/P-022-rust-core-migration.md | 154 +++++++++++++++++--- docs/proposals/README.md | 2 +- 2 files changed, 138 insertions(+), 18 deletions(-) diff --git a/docs/proposals/P-022-rust-core-migration.md b/docs/proposals/P-022-rust-core-migration.md index 837666a1..c24b2415 100644 --- a/docs/proposals/P-022-rust-core-migration.md +++ b/docs/proposals/P-022-rust-core-migration.md @@ -6,9 +6,21 @@ rationale below is historical and unchanged; the live sequencing is the #250 child-issue DAG. Revised per the post-merge review in [`docs/notes/p022-review-notes.md`](../notes/p022-review-notes.md). -### Implementation status (reconciled after #214/#249 — see #250/#251) - -**Implemented** (workspace members in `rust/Cargo.toml`, parity-gated by +### Implementation status + +> **Reconciled at `fdcb222`** against `rust/Cargo.toml`, the crate sources and +> the child-issue states under #250. Written fresh from the tree, not carried +> over from an earlier reconciliation — a stale status block patched with a +> stale fix stays stale. Statuses are **checkpoint-level**, and each open step +> separates its *normative* blocker (what its acceptance actually requires) from +> #250's *preferred* sequencing (what order is cheapest); conflating those is +> what let this table drift twice already. +> +> Keeping it true is a rule, not a habit: see +> [Parity-work discipline](#parity-work-discipline) below, and the status-drift +> rule in #250. + +**Complete** (workspace members in `rust/Cargo.toml`, parity-gated by `scripts/oracle_exact.py` and the shared fixtures in `tests/fixtures/`): - `own-ir` — OwnIR serde + schema round-trip (step 1); @@ -17,24 +29,47 @@ child-issue DAG. Revised per the post-merge review in `tests/fixtures/cfg_parity.json` (steps 0/3; the seam the strategy below said "still needs building" **is built** — `python -m ownlang cfg --format json` + the `--write`-regenerated parity fixtures); -- `own-diagnostics` — the data-only diagnostics layer; - `own-analysis` — the worklist solver + ownership/lifetime/buffer/effect/DI analyses (step 4; the **analysis-heart milestone**, completed in #214 / PR #249, replaying `diag_parity.json` and the DI/effect fact-parity - fixtures). - -**Next steps — each owned by exactly one child issue under #250:** - -| Step | Deliverable | Issue | + fixtures); +- `own-diagnostics` — the verdict model **and** the full normalized diagnostic + contract (step 5a, #255 **closed completed**, PRs #319/#320/#321): structural + identity with a non-collapsing comparison key, canonical `render` / + `render_pretty` text, the emission ordering contract, and a self-policing + ledger covering all 47 `TITLES` codes; +- `own-lowered` — the normalized Layer 2 document the bridge lowers into (the + differential-testing representation named by #259 checkpoint 2); +- `spec/Bridge.md` + `spec/BridgeBehaviorMatrix.md` — the normative bridge + contract and its completeness ledger (step 6a, #258 **closed completed**; + PR #297 merged, both documents on `main`). + +**In progress — step 6b, `own-bridge` (#259).** It landed ahead of #250's +*preferred* order ("preferably after #255/#256"); its *normative* blocker was +#258 alone, which is satisfied. Per the checkpoints #259 itself defines: + +| #259 checkpoint | Status | Evidence / what remains | |---|---|---| -| 5a | diagnostic messages + ordered Evidence parity | #255 | -| 5b | `.ownreport.json` + SARIF projection, canonical parity | #256 | -| 5c | `own-codegen` (analysis-independent sibling) | #257 | -| 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) | -| 6b | Rust `own-bridge`, layered OwnIR parity | #259 | -| 7a | dual-engine shadow mode + zero-diff reproduction artifacts | #260 (supported by #269 — normalized `AnalysisTrace` + first-divergence minimizer) | -| 7b | Rust `own-cli`: command/output/exit-code parity | #261 | -| 8 | Rust-default **cutover**, rollback gate, Python distribution removal | #262 | +| 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 | +| 2 — fact lowering | **complete** | `lower()` → `own_lowered`; **27/27** `rust_replay` cases in `tests/fixtures/lowered/manifest.json` byte-exact | +| 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 | +| 4 — analysis wiring | **not started** | the crate states its own boundary: "no diagnostics, no analysis" | +| 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 | + +**Open steps — each owned by exactly one child issue under #250:** + +| Step | Deliverable | Issue | Status | +|---|---|---|---| +| 5a | diagnostic messages + ordered Evidence parity | #255 | **complete** — see above | +| 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 | +| 5c | `own-codegen` (analysis-independent sibling) | #257 | **ready**, independent of the analysis path — parallelizable | +| 6a | OwnIR **bridge semantics formalized** before the port | #258 | **complete** — see above | +| 6b | Rust `own-bridge`, layered OwnIR parity | #259 | **in progress** — checkpoint table above | +| 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 | +| 7b | Rust `own-cli`: command/output/exit-code parity | #261 | blocked — needs the production bridge and the output surfaces | +| 8 | Rust-default **cutover**, rollback gate, Python distribution removal | #262 | blocked by #260/#261 and final parity | + +**Preferred queue:** #256 → #259 remaining (cp1 → cp4 → cp5) → #260/#269. The Datalog/Ascent rule layer stays strictly **post-cutover** (strategy step 8 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 Throughout, Python stays authoritative; the Rust crates light up behind the ratchet. +## Parity-work discipline + +Four rules, each paid for by a real defect during step 5a (#255, PRs +#319/#320/#321). They are written **wider than this port on purpose**: nothing +below depends on Rust, on Python, or on the diagnostics layer, so they outlive +P-022 and apply to the next migration that pins one implementation against +another. + +Scope: parity/migration work. This is the only home — the rules are not +duplicated into `AGENTS.execution-surfaces.md`, because two copies of one law +drift, which is the failure mode the status-drift rule already exists to stop. + +### 1. Oracle over reviewer prose + +**Rule.** A reviewer finding is a *hypothesis* until reproduced against the +reference implementation. Preserve the observed oracle behaviour, not the +reviewer's explanation of it. + +**Why.** A reviewer — human or bot — can be right about *what* is wrong and +wrong about *why*, and the explanation is what you would otherwise encode. + +**Failure mode paid for.** A review argued the word-boundary rule from probes +using single-ended patterns (`\b-foo`). The reference builds a **both-ended** +pattern (`\b…\b`), which gives a different answer at the right edge. The +conclusion was correct; the stated reason was not, and implementing the reason +would have been wrong. Re-probing with the real pattern shape settled it. + +### 2. Mutation over plausible tests + +**Rule.** A test written to catch a specific regression is not evidence until +the corresponding mutation makes it fail **through the production surface it +claims to protect**. + +**Why.** A test that has never failed has never been shown to test anything. +"Through the production surface" is the load-bearing half: a test can exercise a +private copy of the logic and pass while the public path rots. + +**Failure mode paid for.** The ordering replay sorted a parallel vector with a +*copy* of the sort key and then compared only `(line, code)` — which tied +records share by construction. `ties_keep_emission_order` would have passed +against `sort_unstable_by`, the exact defect it was written for. Driving the +label order through the public helper made it real. + +### 3. No fail-fast during mutation campaigns + +**Rule.** Mutation validation must expose **all** independent catching layers, +not merely the first failing test target. + +**Why.** Stopping at the first failure tells you *a* test caught the mutation. +It does not tell you which layers did and which silently would not have. + +**Failure mode paid for.** `cargo test` halts after the first failing target. A +mutation appeared to be caught by one unit test only; with `--no-fail-fast` the +same mutation showed three catchers across unit and replay layers — and a second +mutation, invisible in the first run, was caught only at the replay layer. + +### 4. Insertion-stable generated goldens + +**Rule.** When a fixture's shape is derived from a vocabulary, it must depend on +**stable item identity**, never on ordinal position. + +**Normative acceptance:** + +```text +insert one synthetic vocabulary member + existing-record churn == 0 + new-record delta == 1 +``` + +The **invariant is the law**. A content hash is today's way of satisfying it in +`tests/test_diag_ledger_fixtures.py`, not the requirement — any stable mapping +that holds the two lines above conforms, and swapping the mapping is not a +violation. (Whatever is chosen must be reproducible across processes: Python's +`hash()` randomises string hashing per run and cannot be used.) + +**Why.** A vocabulary-derived golden exists to make *adding a member* legible. +If insertion rewrites unrelated records, the one diff a reviewer needs is buried +in churn, and a genuine change to the generator hides inside it. + +**Failure mode paid for.** The ledger rotated case shapes by sorted index. +Inserting one code (`DI006`) rewrote **42 of 47** existing records — measured, +not estimated. The docstring meanwhile claimed the shapes were "deterministic, +so the golden stays stable", true only while the vocabulary never changed, which +is the single event the ledger exists to expose. + ## Open questions (to resolve as we go) - **Parser**: hand-roll vs `chumsky`/`winnow`. Leaning hand-roll for golden-parity of diff --git a/docs/proposals/README.md b/docs/proposals/README.md index 7c78840c..45097a10 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -41,7 +41,7 @@ proposal is marked `done` with a pointer. | [P-017](P-017-multi-stack-frontends.md) | Multi-stack frontends (OwnTS / OwnJVM: OwnJava + OwnKotlin) | draft | | [P-020](P-020-ownts-react-effects.md) | OwnTS React effects profile (`Own.React`) — the effect-storm angle | draft | | [P-021](P-021-async-audit-pack.md) | Async audit pack (`Own.Async`) | draft | -| [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 | +| [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 | | [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft | | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [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) | From 267e38ca8e5872160c2ae78a0debfcf55435d7fa Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 09:40:48 +0000 Subject: [PATCH 2/2] docs(P-022): state the rule-4 enforcement gap; markdown structure fixes Three of four CodeRabbit findings on 2cb3822, each checked against the tree before acting (rule 1 of the section this PR adds). - Rule 4 declared `churn == 0` AND `delta == 1` as normative acceptance while `_insertion_churn()` gates only the first. A generator that dropped the new record entirely would pass every executable check. The norm stays as written; the gap is now stated rather than implied, which is the whole point of the status-drift rule this PR introduces. - `## Implementation status`: every other top-level section in the file is `##`; this one was the only `###` hanging directly off the h1. - Reflowed two paragraphs so `#258` and `#319` no longer begin a line. Renders the same on GFM (ATX needs a space), but the pattern is fragile on non-CommonMark renderers and trips MD018. The fourth finding is rejected in the PR thread: "in execution" is pre-existing on main, matches the proposal's own Status line, and changing only the index row would manufacture the exact drift this PR removes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CJF7MBi1ijU5m9cJVWgQsM --- docs/proposals/P-022-rust-core-migration.md | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/proposals/P-022-rust-core-migration.md b/docs/proposals/P-022-rust-core-migration.md index c24b2415..1a829487 100644 --- a/docs/proposals/P-022-rust-core-migration.md +++ b/docs/proposals/P-022-rust-core-migration.md @@ -6,7 +6,7 @@ rationale below is historical and unchanged; the live sequencing is the #250 child-issue DAG. Revised per the post-merge review in [`docs/notes/p022-review-notes.md`](../notes/p022-review-notes.md). -### Implementation status +## Implementation status > **Reconciled at `fdcb222`** against `rust/Cargo.toml`, the crate sources and > the child-issue states under #250. Written fresh from the tree, not carried @@ -45,8 +45,8 @@ child-issue DAG. Revised per the post-merge review in PR #297 merged, both documents on `main`). **In progress — step 6b, `own-bridge` (#259).** It landed ahead of #250's -*preferred* order ("preferably after #255/#256"); its *normative* blocker was -#258 alone, which is satisfied. Per the checkpoints #259 itself defines: +*preferred* order ("preferably after #255/#256"); its *normative* blocker +was #258 alone, which is satisfied. Per the checkpoints #259 itself defines: | #259 checkpoint | Status | Evidence / what remains | |---|---|---| @@ -598,11 +598,11 @@ Throughout, Python stays authoritative; the Rust crates light up behind the ratc ## Parity-work discipline -Four rules, each paid for by a real defect during step 5a (#255, PRs -#319/#320/#321). They are written **wider than this port on purpose**: nothing -below depends on Rust, on Python, or on the diagnostics layer, so they outlive -P-022 and apply to the next migration that pins one implementation against -another. +Four rules, each paid for by a real defect during step 5a +(#255, PRs #319/#320/#321). They are written **wider than this port on +purpose**: nothing below depends on Rust, on Python, or on the diagnostics +layer, so they outlive P-022 and apply to the next migration that pins one +implementation against another. Scope: parity/migration work. This is the only home — the rules are not duplicated into `AGENTS.execution-surfaces.md`, because two copies of one law @@ -671,6 +671,14 @@ that holds the two lines above conforms, and swapping the mapping is not a violation. (Whatever is chosen must be reproducible across processes: Python's `hash()` randomises string hashing per run and cannot be used.) +**Enforcement today is partial — the norm deliberately outruns the guard.** +`_insertion_churn()` in `tests/test_diag_ledger_fixtures.py` computes and gates +the **first** line only (`churn == 0`). Nothing yet asserts `delta == 1`, so a +generator that dropped the new record entirely would still satisfy every +executable check. The acceptance above is the norm regardless of how much of it +is currently wired up; closing the gap is a queued test change, not a +relaxation of the rule. + **Why.** A vocabulary-derived golden exists to make *adding a member* legible. If insertion rewrites unrelated records, the one diff a reviewer needs is buried in churn, and a genuine change to the generator hides inside it.