Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions docs/proposals/P-022-rust-core-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ was #258 alone, which is satisfied. Per the checkpoints #259 itself defines:
| 4b — protocol analysis (OBL001–005) | **complete — Layer 3 parity over the measured set, protocol family included** | The gap in #259's checkpoint list is closed: `ownlang/obligations.py` has a port. The analysis is `own-analysis/src/obligation.rs` (BR-B1: the analysis owns its verdict) — the `{OPEN, CLOSED}` set lattice with min-line provenance, the opens-before-closes-before-barriers leaf order with allow beating barrier, the never-invent asymmetry of an opaque write, the exits with the OWN001 anchor-at-acquire precedent, the loop's silent fixpoint and single emitting pass, the reachability-blind close-line evidence and the four-part sort key. The typed `Protocol`/`MethodEvents` values are built by **one** implementation of the grammar with two consumers — `own-ir/src/protocol.rs` grew from validate-only to validate-and-construct, so the strict door and the analysis cannot drift into two readings of one grammar — and no strict-door error text or category moved (the cp1 ledger's protocol controls stayed green). A third **fact-parity family** at the analysis level mirrors the DI/effect one (`tests/test_obligation_fact_parity.py` → `tests/fixtures/obligation_fact_parity.json` → `own-analysis/tests/obligation_parity.rs`): the reference's own `check_protocols` / `unmatched_scopes` freeze every violation WHOLE, and the replay compares every member with zero Python. Cases carry RAW documents, so each side builds the values with its own half of the grammar. The bridge maps BR-P3 in its BR-V1 place (after effects, before OWN050): the `(kind, definite)` table, the four deliberately line-free wordings, the identity derivations, the anchorless advisory OBL005 and the opened→barrier(→late-close) slice. `refuse_protocols` is **gone** and both reference documents are **promoted** out of `rust_replay_excluded` — the exclusion ledger shrinks by two and its pinned set with it, which is the contract change this checkpoint exists to make. Neither golden was regenerated to reach it. Eight synthetic controls (seven Layer 3, one rendered) close every row the corpus could not reach, insertion-stable. Two things are **measured and recorded rather than claimed**: the BR-V5 "a slice shorter than two steps is dropped" rule is not applied on the protocol path (a leak off the end carries a one-step slice), and the family's append POSITION is unobservable end to end because the sort key's code component decides first. One golden family IS regenerated, for a stated reason: the shadow artifact and trace of the protocol document, whose Rust `verdicts` layer moves from `refused` to `produced` — the same shape of change #339 recorded for `partial` → `full`; the `own-shadow` reducer still refuses Layer 3, which is #260's boundary and unmoved. Counts live only in the generated fragments [`p022-cp4-census.md`](../generated/p022-cp4-census.md), [`p022-cp5-inventory.md`](../generated/p022-cp5-inventory.md) and [`p022-cp4b-mutations.md`](../generated/p022-cp4b-mutations.md) ([note](../notes/p022-bridge-verdict-checkpoint4b.md)) |
| 5 — full fact-to-verdict parity | **complete at the cp5 surface — Layer 3 parity over the measured set at the full `Finding` and the rendered surfaces** | the goldens already carry `message`, `related` and `flow`; cp5 is the message matrix (BR-V4), the evidence slices, the rendered surfaces (`render*`, `build_sarif` on the bridge path — BR-V9) and tightening the cp4 replay to full equality on the **same frozen goldens** — no regeneration beside the implementation. The **surface inventory** is taken and generated, never typed ([`p022-cp5-inventory.md`](../generated/p022-cp5-inventory.md), read by [the cp5 note](../notes/p022-bridge-verdict-checkpoint5.md)): one ledger row per BR-V4 wording branch with **who owns the string** (the bridge's matrix, the DI/effect finders' own `message`, or the core diagnostic's — which this core does not have at all, carrying each code's TITLE instead), one per BR-V5 evidence family and degradation rule, and one per BR-V9 rendered-surface rule; a row at zero over the replayed set is a branch cp5 must add a control for, not one it may declare. **cp5.1** put `message`, `related` and `flow` on `own_bridge::Finding`, ported the BR-V4 matrix and the BR-V5 slice builders, and tightened the replay to EVERY `Finding` member — against the goldens exactly as committed, none regenerated. The analysis-owned halves went where the reference keeps them: the DI finders' own `message` and ctor/registration metadata, and the effect storm's message, kind, mint line and reference chain, all additive in `own-analysis` with no existing core surface changed. Synthetic cases close every reachable branch the inventory named (insertion-stable: zero existing records rewritten, in the verdict ledger and in the shadow digest ledger alike); the branches no facts document can reach are pinned by `verdict::tests` controls driven through `map_core`, and their expected text is not written in those tests: it is read from `tests/fixtures/unreachable_branches.json`, the reference's own output recorded by `tests/test_unreachable_branch_probe.py` running `check_facts` with its lowering substituted — so the oracle's answer is re-runnable rather than a claim, and there is no second copy of the text for a port to drift into agreeing with. **cp5.2** removed the last comparison boundary on a refusal: `own_cfg::Diag` now carries the reference's message for the resolver text the BR-V3 map-or-raise line interpolates (measured to be the only one the corpus reaches), `own-analysis` reads it, and the three `hoist_neg_*` refusals compare byte for byte. The unported remainder is an `Option`, so a code whose text is missing renders as its title and the first golden that refuses on it goes red demanding the message — a tripwire, not a blind spot. Removing the cut immediately exposed the defect it was hiding: cp4's `py_repr` quoted unconditionally with `'`, where CPython switches to `"` for a string containing a single quote — which every core message naming an identifier does. Fixed in Rust and pinned against CPython's own output. One consequence in `own-shadow`: its verdict capture declared a *partial* projection whose reason said cp5 was not ported, so it now emits every member and declares `full`, with the projection check extended to validate a `full` claim too — the reducer still **refuses** the verdict layer, which is #260's boundary and unmoved. **cp5.3** built the surface that had no golden at all: `ownlang/renders.py` (an observer beside `verdicts.py`), the `tests/fixtures/verdict_renders/` family under its own frozen ledger where each case names the BR-V9 rows it is the control for, a verify/`--write` harness, and a Rust replay comparing the **bytes** — SARIF key order is part of this surface, so the port reconstructs the document through typed structs whose field order is the emitter's key order rather than a map type that sorts. `codeFlows` reuses `own_diagnostics::code_flow` verbatim; `relatedLocations` deliberately does **not**, because the core's builder drops a step with an empty file and the bridge's does not — reuse there would have been a port bug, and a golden pins the difference. `own-diagnostics` is unchanged on the core path. The checkpoint-4 `subject` tail is closed: both sides scan the rendered bytes for a `subject` key rather than restating that none can appear. Row **4b** has since landed (the protocol family is ported and replayed), and so has the coordinate-domain decision that was the last thing #259's final acceptance owed. That decision landed Python-first and as its own change with its own parity evidence: §4.2 bounds every `line` to `[0, 2147483647]` and every `column` to `[1, 2147483647]`, because int32 is the line type of every consumer this project feeds — never "Rust is `u32`, so the reference is wrong". `0` stays legal as the reference's own absent sentinel, the strict door refuses an out-of-domain coordinate as `Location` and the tolerant door degrades it to `0` (degrade, never clamp), and the four boundary controls are promoted out of the exclusion ledger — see the [final-acceptance note](../notes/p022-bridge-verdict-final-acceptance.md) |

**#259 final acceptance reached: Layer 3 parity over the *full* #258 family inventory at the full `Finding` and the rendered surfaces; declared boundary: the two OD-1 door controls (#294), measured, not open work.** The coordinate-domain contract landed Python-first, the port mirrors it, and the exclusion ledger the replay executes now names only those two. Not shadow mode — that is #260's acceptance and is blocked on its own two decisions; not "P-022 done"; not "Rust is the default", which is #262's cutover. The measured record is the [final-acceptance note](../notes/p022-bridge-verdict-final-acceptance.md) and the generated fragments it links.
**#259 final acceptance reached: Layer 3 parity over the *full* #258 family inventory at the full `Finding` and the rendered surfaces; declared boundary: the two OD-1 door controls (#294), measured, not open work.** The coordinate-domain contract landed Python-first, the port mirrors it, and the exclusion ledger the replay executes now names only those two. Not shadow mode — that is #260's acceptance, since reached (row 7a); not "P-022 done"; not "Rust is the default", which is #262's cutover. The measured record is the [final-acceptance note](../notes/p022-bridge-verdict-final-acceptance.md) and the generated fragments it links.

**Open steps — each owned by exactly one child issue under #250:**

Expand All @@ -75,10 +75,13 @@ was #258 alone, which is satisfied. Per the checkpoints #259 itself defines:
| 6a | OwnIR **bridge semantics formalized** before the port | #258 | **complete** — see above |
| 6b | Rust `own-bridge`, layered OwnIR parity | #259 | **final acceptance reached** — see the checkpoint table and the line above it |
| 7a | dual-engine shadow mode + zero-diff reproduction artifacts | #260 (supported by #269) | **final acceptance REACHED**. The only wording it earns: *dual-engine compare mode reports zero acceptance-unexplained over its full test matrix — the committed corpus, the C# samples, the examples, the five pinned OSS repositories of #243 and the large-solution controls — at all three layers and on the derived SARIF, on byte-attested same input, with the OD-1 typed-door boundaries declared by policy; Python remains the public engine.* It is **not** "P-022 done" and **not** "Rust is the default", which is #262's cutover behind #261. The sweep is what the acceptance surfaces over the committed corpus deliberately left owed: ten documents over six targets, each repository at its **verified** pin (drift is a failed target, never a newer measurement), each extracted **once** through `own-check.sh --emit-facts` and compared from those bytes — the five directory walks, the largest `.sln` of every target that has one (a different extractor path, and measurably a differently *ordered* document rather than a subset), and `examples/`. Coverage is defined so that it cannot be faked: a repository is not covered because extraction succeeded, so the driver fails a run that compared zero documents AND a declared target it never reached, and the **denominators are recorded per target**. The driver gained the identity the #342 review asked for — every result and failure report names the adapter by `sha256` and byte length, taken from the file that ran — plus manifest runs whose every document is verified against its `facts_sha256` before any engine starts (`shadow_compare_version` 2; the artifact format v3 is untouched). Taking the measurement found six HARNESS defects and no engine divergence: a cross-drive `relpath` that killed the driver on a label, a timeout that never returned when the adapter had children, a control group that could not execute on Windows at all (and so had never caught the timeout one), and three in the mutation harness that between them meant no campaign could be recorded anywhere but Linux — rewritten line endings that made it refuse its own run, a catcher name that took the host's path separator and so reported five protected rules as unprotected, and a layer decoded with the console codepage. The five repositories' facts documents are not committed — their identities are. The scheduled/manual gate is `.github/workflows/shadow-sweep.yml`; every count lives in the generated fragments ([sweep](../generated/p022-shadow-sweep.md), [census](../generated/p022-shadow-census.md), [campaigns](../generated/p022-shadow-mutations.md)) and never here; the records are [the sweep note](../notes/p022-shadow-sweep.md) and [the acceptance note](../notes/p022-shadow-acceptance.md), which name what is measured-not-claimed. The owner decisions remain D-4..D-7, B-2, B-3, R-1 and R-2 in [the ledger](../notes/p022-shadow-infra-owner-decisions.md), unreopened. No production behaviour changed |
| 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 |
| 7b | Rust `own-cli`: the production OwnIR executable — command/output/exit-code parity behind the existing launcher | #261 (residual `.own`/dev CLI: #345) | **decision packet ratified, implementation not started.** Owner decisions C-1..C-5 (2026-09-08, recorded verbatim in #261): the oracle is split by surface and behaviour class — `python -m ownlang` for the core semantics of `ownir`, through Python-authored fixtures replayed with zero Python; the public `owen` convention for help/version/usage, whose text becomes a cross-implementation parity surface rather than an invented Python contract, the core's own `--help` reaction being a measured defect whose Python-first repair is hygiene, not a prerequisite; `report` struck, not deferred (#256); the production seam — `ownir <facts> --format --severity`, the one core invocation `own-check.*`, the Action and `owen check` make — separated from the residual PoC CLI (`cfg`, `summaries`, `explain`, `.own check`, `emit` behind #257 → #345), which stays a migration obligation but not a gate; engine selection stays outside the executable — one engine, no silent fallback, orchestration in the dev driver now and in the launcher at #262's stages; the production slice alone is #262's CLI prerequisite. Rulings: stdin is not part of the contract; a catchable panic is one actionable stderr line and rc 70; an uncatchable death is a visible hard failure, never findings and never clean; SIGINT is measured on the Windows and Linux reference before anything is contracted. The executable lives **behind** the unchanged `owen` launcher: no default changes, nothing is published. A ratified decision is not completed code — no crate, fixture or CI job exists yet |
| 8 | Rust-default **cutover**, rollback gate, Python distribution removal | #262 | blocked by #261 — the production OwnIR executable alone (C-5); #260 reached; #345 is not on this path. #263's baselines are the evidence prerequisite of the cutover decision, not a normative blocker. Launcher rulings recorded in #262: engine selection is the launcher's, never the executable's; an unexpected Rust child exit code outside the legal set takes the public internal-error path with the raw child status retained in the evidence; no silent fallback |

**Preferred queue:** #261. cp5, 4b, the coordinate-domain
**Preferred queue:** #261 — its decision packet is ratified, the production
OwnIR executable is next — then #262; in parallel and off the critical chain:
#257, #263 (the evidence prerequisite of #262's decision), #345 once #261's
skeleton exists, and #269's reconciliation. cp5, 4b, the coordinate-domain
decision and #260's own acceptance decisions are all off this queue: each is
complete at its surface. The #260/#269 *infrastructure* slice ran in parallel by
design — it is measured on the landed checkpoints and takes no position on the
Expand Down Expand Up @@ -238,7 +241,7 @@ Arrow = "is depended on by" (dependency → dependent, i.e. build order):
# (today's ownir.py beyond the schema; the flagship C#→facts→verdicts path;
# own-syntax is required — the lowering *constructs* core AST nodes)

{all of the above, incl. own-bridge} ─▶ own-cli (check / emit / cfg / report / ownir / explain)
{all of the above, incl. own-bridge} ─▶ own-cli (ownir — #261; cfg / summaries / explain / check — #345; emit after #257; report struck at #256)
own-cli ◀─ own-oracle (dev/test: differential harness vs Python)
own-shadow (dev/test: shadow-mode INFRASTRUCTURE — step 7a)
```
Expand Down
Loading
Loading