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
Parent: #250
Recommended tier: strong + owner decision
CLI prerequisite satisfied — #261 / 261.B: the production Rust OwnIR executable landed via PR #347 (merged as 206e9c7) and #261 is closed completed; it was the sole CLI prerequisite of this cutover by owner decision C-5 (ratified 2026-09-08). #260 is reached. Not blocked by #345, the residual .own/dev CLI split out of #261.
Evidence prerequisite for the cutover decision, not a normative blocker: #263's baselines — the performance gates below cannot be judged without them.
Also depends on: #256 and #259 (both reached)
Goal
Make an explicit, evidence-backed decision to switch Owen's default engine from Python to Rust, preserve a tested rollback path, and only later remove Python as a distribution dependency.
This is not an implementation catch-all. It is a cutover gate with measurable correctness, reliability, distribution and performance requirements.
An unexpected Rust child exit code outside the legal set maps to the public internal-error path (exit 5), and the raw child status is retained in the evidence/report. A catchable panic in the child is one actionable stderr diagnostic and exit 70 (mapped onto the internal-error path as today); an uncatchable death is a visible hard failure, never findings and never a clean exit, with no particular OS exit number contracted.
SIGINT/cancellation: the Windows and Linux reference behaviour is measured first; 130 is not invented as a universal contract.
stdin is not part of the production OwnIR contract.
Known differences recorded ahead of the packet (2026-09-08, from #261's 261.B measurement)
Named here so they enter the decision packet's Known differences: line as recorded facts rather than being discovered at cutover:
Windows console/pipe encoding — a deliberate behavior change at cutover, not a regression. On a piped Windows stdout the Python reference encodes with cp1252, translates line endings to CRLF, and dies with UnicodeEncodeError on non-ASCII output that the Linux reference renders. The Rust executable emits canonical UTF-8 on both platforms. So a Windows user's bytes change at cutover — cp1252 / CRLF (and the occasional UnicodeEncodeError) today, canonical UTF-8 tomorrow. Three claims, kept apart:
A — canonical reference parity: claimed. Rust reproduces the Linux/UTF-8 reference byte for byte.
B — Rust portability: claimed. Rust on Linux and Rust on Windows emit identical bytes.
C — native-Windows Python parity: NOT claimed. It does not exist today, because the Windows reference is not byte-portable; "better" here still means "different", so it is a behavior change, not folded into a parity claim.
Invalid UTF-8 input. The Python reference exits 70 on a UnicodeDecodeError (its load() catches OSError/JSONDecodeError only, so a decode failure escapes to the exit-70 catch-all). A Python-first hygiene fix is owed before public cutover — UnicodeDecodeError → OwnIRError → rc 2. P-022 step 8: Rust-default cutover, rollback gate, and Python distribution removal #262 is the tracker of record for this cutover prerequisite; Roadmap: ship Owen Alpha and complete the Rust production vertical after #214 #250 mirrors this tail in its Still missing list. Until it lands the Rust executable reproduces the code and shape but claims no byte contract for it (a CPython exception's wording is deliberately not a cross-language contract).
Parser-domain rulings V1–V4 (owner-ratified 2026-09-08, surfaced by #261's Version census)
The ownir_version gate exposes classes where CPython's permissive json and serde_json decode the same source differently — V1–V3, acceptance / kind / sub-branch divergences, not message wording — plus one representation-only boundary, V4, where the two sides' Unicode tables are independently versioned. They are ruled here:
V1 — non-finite constants NaN / Infinity / -Infinity: declared Python-reference defect. CPython json.loads accepts them, so the reference reaches the Version door (got nan / got inf / got -inf); serde_json rejects them at the JSON door (kind Json). The Rust JSON refusal is not a P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261 parity failure and the parser is not taught non-standard JSON. Python-first hygiene owed before public cutover: reject them at the JSON door → OwnIRError / rc 2 with a frozen message.
V4 — str.isprintable() is answered from a version-dependent Unicode table: declared reference-version exception. Unlike V1–V3 this is not an accept/reject divergence and does not touch the parser: serde_json remains the sole parser deciding acceptance. It is a representation / diagnostic boundary — when a rejected ownir_version is a string, its repr escapes or prints each code point by str.isprintable(), which each side answers from the Unicode table it was built with. The supported CPython reference matrix is itself version-dependent — Python 3.11 links UCD 14.0.0, 3.12 links 15.0.0, 3.13 links 15.1.0 — so for a code point whose classification differs between those releases there is no single "what Python does". No single static Unicode-property table can be byte-identical to every supported CPython reference version on such code points; a pin would buy parity with one interpreter and silently lose it against another, which is a change to the P-022 reference contract rather than a technical fix — so V4 is not described as "unrepairable from Rust". V4 stays excluded from the byte-parity denominator but remains separately declared and measured. Its reach in the frozen fixture family is zero, evidenced first by the green Python-oracle matrix on 3.11/3.12/3.13 (three different Unicode tables, all green) and corroborated by a whole-plane sweep. Any exact mismatch count is a specific two-version measurement and must be labelled as such — e.g. the 15 097 code points measured for CPython 3.11.15 / UCD 14.0.0 against unicode-properties 0.1.4 / UCD 17.0.0 — never presented as a version-independent size of V4. Reopens if the supported Python matrix changes, the Rust Unicode snapshot changes, a divergent code point enters the frozen fixtures, or this difference reaches accept/reject/verdict behaviour or any previously byte-parity production output. The same is_printable helper is carried by own-syntax, so the boundary is a repository-wide tail to consolidate (one measurement, one place) rather than anything P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261 introduces.
No severity or diagnostic-count drift is accepted without a separate Python-first decision.
Reliability gates
malformed OwnIR and malformed source inputs do not panic;
Rust crashes are visible and produce reproduction artifacts;
cancellation/interruption behavior is tested against the measured reference (the SIGINT ruling above);
deterministic reruns produce identical normalized output;
memory/resource limits for hostile or very large inputs are documented;
Windows and Linux clean-machine paths are covered.
Distribution gates
Rust binary is packaged for all supported platforms;
public Owen.Cli install and Action use work without undeclared runtimes;
package upgrade/uninstall/reinstall tested;
rollback engine selection is documented and tested during the observation stage;
release workflow tests the actual packed artifact, not a project build.
Performance gates
Establish and publish baselines for:
process startup;
OwnIR parse;
bridge/lowering;
analysis;
CLI/SARIF rendering;
end-to-end C# project/solution run;
peak RSS/allocations where practical.
#263 produces these baselines; without them this section cannot be judged, which is why #263 is the evidence prerequisite of the cutover decision. Do not require a theatrical multiplier. Rust must meet an explicit budget and must not materially regress the user-visible path. Profile before optimizing; JSON and rendering may dominate once analysis becomes cheap.
Cutover decision packet
The PR/ADR must include:
Observation window:
Fast compare result:
Five-repo compare result:
Large-solution result:
Windows packaging result:
Linux packaging result:
Startup delta:
End-to-end delta:
Peak memory delta:
Known differences:
Rollback command/config:
Python-removal timing:
The Known differences: line begins from the section recorded above (the Windows A/B/C behavior change, the invalid-UTF-8 tail, and the parser-domain rulings V1–V4), plus anything the observation window adds.
Rollback
Rollback must be a documented engine selection or package patch, not a hidden automatic fallback.
A Rust failure must remain observable.
Immutable release tags are never moved.
A broken release is corrected by a new patch release.
Guardrails
No new diagnostics.
No semantic cleanup mixed into cutover.
No removal of Python reference/oracle tests merely because distribution no longer needs Python.
No public Rust default before the evidence packet is approved.
No Python dependency removal in the same PR that first changes the default.
No engine selection inside the Rust executable, and no fallback anywhere (C-4).
Acceptance
Explicit owner-approved cutover decision exists.
Rust is public default only after all gates pass.
Rollback path is tested and documented.
Observation-period results are recorded.
Python distribution removal occurs in a later, separately reviewable PR.
Public Owen install/Action behavior remains correct on supported platforms.
Parent: #250
Recommended tier: strong + owner decision
CLI prerequisite satisfied — #261 / 261.B: the production Rust OwnIR executable landed via PR #347 (merged as
206e9c7) and #261 is closed completed; it was the sole CLI prerequisite of this cutover by owner decision C-5 (ratified 2026-09-08). #260 is reached. Not blocked by #345, the residual.own/dev CLI split out of #261.Evidence prerequisite for the cutover decision, not a normative blocker: #263's baselines — the performance gates below cannot be judged without them.
Also depends on: #256 and #259 (both reached)
Goal
Make an explicit, evidence-backed decision to switch Owen's default engine from Python to Rust, preserve a tested rollback path, and only later remove Python as a distribution dependency.
This is not an implementation catch-all. It is a cutover gate with measurable correctness, reliability, distribution and performance requirements.
Staged rollout
Stage 1: opt-in Rust
own-cli ownir) — command, output and exit-code parity behind the existing launcher #261: the executable presents one engine and knows nothing of Python).Stage 2: repository CI/dogfood Rust default
Stage 3: public Rust default
Stage 4: remove Python distribution dependency
Separate PR after an observation period:
OWEN_PYTHONand legacyOWN_PYTHONaccording to a documented compatibility plan;Launcher rulings (ratified 2026-09-08 with #261's decision packet)
own-cli ownir(P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261) — one engine. Engine selection, the staging above and the escape hatch are the launcher's, explicit and documented; no silent fallback from a Rust failure to a Python success at any stage.Known differences recorded ahead of the packet (2026-09-08, from #261's 261.B measurement)
Named here so they enter the decision packet's
Known differences:line as recorded facts rather than being discovered at cutover:cp1252, translates line endings to CRLF, and dies withUnicodeEncodeErroron non-ASCII output that the Linux reference renders. The Rust executable emits canonical UTF-8 on both platforms. So a Windows user's bytes change at cutover — cp1252 / CRLF (and the occasionalUnicodeEncodeError) today, canonical UTF-8 tomorrow. Three claims, kept apart:UnicodeDecodeError(itsload()catchesOSError/JSONDecodeErroronly, so a decode failure escapes to the exit-70 catch-all). A Python-first hygiene fix is owed before public cutover —UnicodeDecodeError→OwnIRError→ rc 2. P-022 step 8: Rust-default cutover, rollback gate, and Python distribution removal #262 is the tracker of record for this cutover prerequisite; Roadmap: ship Owen Alpha and complete the Rust production vertical after #214 #250 mirrors this tail in its Still missing list. Until it lands the Rust executable reproduces the code and shape but claims no byte contract for it (a CPython exception's wording is deliberately not a cross-language contract).Parser-domain rulings V1–V4 (owner-ratified 2026-09-08, surfaced by #261's Version census)
The
ownir_versiongate exposes classes where CPython's permissivejsonandserde_jsondecode the same source differently — V1–V3, acceptance / kind / sub-branch divergences, not message wording — plus one representation-only boundary, V4, where the two sides' Unicode tables are independently versioned. They are ruled here:NaN/Infinity/-Infinity: declared Python-reference defect. CPythonjson.loadsaccepts them, so the reference reaches the Version door (got nan/got inf/got -inf);serde_jsonrejects them at the JSON door (kindJson). The Rust JSON refusal is not a P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261 parity failure and the parser is not taught non-standard JSON. Python-first hygiene owed before public cutover: reject them at the JSON door →OwnIRError/ rc 2 with a frozen message.-0: declared cross-parser encoding defect. CPython reads-0as the integer0, so{"ownir_version": -0}is accepted as v0 (measured);serde_jsonreads it as the float-0.0, so the Rust door refuses it wrong-type. This is the ambiguity P-022 step 7a: add dual-engine shadow mode and zero-diff reproduction artifacts #260 already froze a refusal for (tests/fixtures/repro/domain_negative_zero.facts.json; manifest reason "the literal -0: this reference reads it as the integer 0, serde_json as the float -0.0. Refused rather than reconciled"). Python's acceptance is not emulated in Rust for P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261 — an exact owner-declared exception to C-1, scoped to the top-level scalarownir_versionvalue the type check reads. A-0inside a wrong-type container is not this exception and needs no emulation:{"ownir_version": [-0]}rendersgot [0]byte-identically on both sides — the reference's ownreprreads the nested-0as the integer0— and P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261's Version census pins that case as byte parity (negative_zero_below_the_top_level_matches_the_reference), not a declared difference. Python-first hygiene owed before public cutover: reject the literal top-level-0at the OwnIR input boundary, consistent with P-022 step 7a: add dual-engine shadow mode and zero-diff reproduction artifacts #260's frozen domain.i64/u64: reproduced (byte parity). Such a value is anintto Python and takes the Version mismatch branch;serde_json's lossyf64would otherwise send the port to wrong-type. P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261's repair-2 reproduced the mismatch branch narrowly — without enablingserde_json/arbitrary_precision— for oversized values of both signs, byte-identical to the reference in the Version census (Group 1, the byte denominator). Accepted at P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261's 261.B head (PR feat(own-cli): the production Rust OwnIR executable, behind the unchanged launcher (#261 261.B) #347, merged 2026-09-08 as206e9c7): this is a completed parity fact, no longer an open divergence.str.isprintable()is answered from a version-dependent Unicode table: declared reference-version exception. Unlike V1–V3 this is not an accept/reject divergence and does not touch the parser:serde_jsonremains the sole parser deciding acceptance. It is a representation / diagnostic boundary — when a rejectedownir_versionis a string, itsreprescapes or prints each code point bystr.isprintable(), which each side answers from the Unicode table it was built with. The supported CPython reference matrix is itself version-dependent — Python 3.11 links UCD 14.0.0, 3.12 links 15.0.0, 3.13 links 15.1.0 — so for a code point whose classification differs between those releases there is no single "what Python does". No single static Unicode-property table can be byte-identical to every supported CPython reference version on such code points; a pin would buy parity with one interpreter and silently lose it against another, which is a change to the P-022 reference contract rather than a technical fix — so V4 is not described as "unrepairable from Rust". V4 stays excluded from the byte-parity denominator but remains separately declared and measured. Its reach in the frozen fixture family is zero, evidenced first by the green Python-oracle matrix on 3.11/3.12/3.13 (three different Unicode tables, all green) and corroborated by a whole-plane sweep. Any exact mismatch count is a specific two-version measurement and must be labelled as such — e.g. the 15 097 code points measured for CPython 3.11.15 / UCD 14.0.0 againstunicode-properties0.1.4 / UCD 17.0.0 — never presented as a version-independent size of V4. Reopens if the supported Python matrix changes, the Rust Unicode snapshot changes, a divergent code point enters the frozen fixtures, or this difference reaches accept/reject/verdict behaviour or any previously byte-parity production output. The sameis_printablehelper is carried byown-syntax, so the boundary is a repository-wide tail to consolidate (one measurement, one place) rather than anything P-022 step 7b: the production Rust OwnIR executable (own-cli ownir) — command, output and exit-code parity behind the existing launcher #261 introduces.Correctness gates
reportwas struck at P-022 step 5b: port the SARIF projection with canonical parity #256 and is not a surface here).own-cli ownir) — command, output and exit-code parity behind the existing launcher #261) is green. The residual.own/dev CLI (P-022 step 7b residual: Rust CLI for the.ownand dev surfaces —cfg,summaries,explain,.own check;emitbehind #257 #345) is not a gate of this cutover (C-5).Reliability gates
Distribution gates
Owen.Cliinstall and Action use work without undeclared runtimes;Performance gates
Establish and publish baselines for:
#263 produces these baselines; without them this section cannot be judged, which is why #263 is the evidence prerequisite of the cutover decision. Do not require a theatrical multiplier. Rust must meet an explicit budget and must not materially regress the user-visible path. Profile before optimizing; JSON and rendering may dominate once analysis becomes cheap.
Cutover decision packet
The PR/ADR must include:
The
Known differences:line begins from the section recorded above (the Windows A/B/C behavior change, the invalid-UTF-8 tail, and the parser-domain rulings V1–V4), plus anything the observation window adds.Rollback
Guardrails
Acceptance