Skip to content

Commit 952d409

Browse files
committed
docs(notes): H1 stops at the transport — OWNIR_VERSION bump needs T0 Amendment 2
Kill-first for wiring H0 heap-effect summaries into ProtocolLowering. The only fail-loud transport that complies with OwnIR IR1-IR4 is a new flow op plus an OWNIR_VERSION 1 -> 2 bump; every v1-compatible encoding is fail-open. The bump forces the perf_baseline facts-generator literal to move, which moves the frozen T0 harness identity the P-022 merge gate checks, so it needs an owner amendment (Amendment 1 precedent). Records the decision asked of the owner and the ready H1 design. No code change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZFvhLx1fM9Gerg4dKsZcL
1 parent 1c70e86 commit 952d409

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

‎docs/notes/h1-transport-stop.md‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# H1 — harmless calls in a protocol region: STOPPED at the transport
2+
3+
Status: **STOP: needs an owner ruling (T0 Amendment 2).** No code changed.
4+
Base: `main` = `1c70e867eb408d931ae3dfffd8c0b351190f6547`, the H0 landing point
5+
([`heap-effect-summaries.md`](heap-effect-summaries.md)).
6+
7+
H1 would let a provably harmless direct source call stand inside a
8+
`[ProtocolRegion]` instead of being refused. The kill-first question was whether
9+
a fail-loud transport for that decision exists inside the current contracts.
10+
**It does not.** The only compliant transport bumps `OWNIR_VERSION`, and a bump
11+
moves the frozen T0 harness identity, which only an owner amendment may do.
12+
13+
## 1. Why the decision needs a must-understand transport
14+
15+
- `ProtocolLowering` refuses in the extractor, before any fact exists.
16+
- The H0 summary is solved in the core.
17+
- The frontend may not solve summaries (IR6, and the rule that frontends emit facts only).
18+
19+
So the facts must carry an **obligation**: "this call is admitted only if the core proves `Harmless(...)`". A core that does not understand the obligation must not read it as clean.
20+
21+
## 2. Every fail-loud mechanism in OwnIR v1 is a version bump
22+
23+
| Candidate transport | Old core (v1) reads it as | Verdict |
24+
|---|---|---|
25+
| Optional field on `call` (`requires: harmless`) | a call to an extern with no tracked argument, i.e. **nothing**, i.e. clean | **fail-open**: rejected |
26+
| Omit the admitted call from facts and put the obligation in a separate file or section | a valid v1 document without the call, i.e. clean | **fail-open**: rejected |
27+
| Extractor calls the core solver before writing facts | n/a | frontend depends on and decides with the core at runtime: an architectural decision no spec makes; **STOP** condition |
28+
| New flow op without a bump | `OwnIRError` (IR4: unknown op) | fail-loud, but violates IR3 ("add a flow op ⇒ bump"); a contract violation is not a transport |
29+
| New resource kind without a bump | `OwnIRError` at load | same: IR3 requires a bump |
30+
| **New flow op + `OWNIR_VERSION` 1 → 2** | `OwnIRError` twice: IR1 (version) and IR4 (op) | **the only compliant fail-loud transport** |
31+
32+
This is by design, not an accident: in OwnIR, "an old core fails loud" and "the vocabulary changed" are the same event (spec/OwnIR.md §2, IR1–IR4). No mandatory-capability or feature-gating mechanism exists. `fix_candidates_version` and the H0 `heap_effects_version` are additive and are ignored by the core.
33+
34+
## 3. Why the bump is not this change's to make
35+
36+
`scripts/perf_baseline.py`'s `facts` calibration generator stamps `"ownir_version": 1`. Under a v2 core:
37+
38+
1. **The literal must move,** or `perf-calibration-facts-current` goes red: the generator would write facts both engines refuse. This is exactly the defect Amendment 1 found.
39+
2. **Moving it moves the T0 harness identity** (`measurement_harness_digest`, currently `1a26aa63fd5f…`), because `perf_baseline.py` is in the harness source set.
40+
3. **The P-022 merge gate** (`scripts/step7/mergegate.py`, `p022-merge-gate.yml`) recomputes the live digest of the merged commit and requires it to equal the one T0-1 names, so the PR cannot merge green.
41+
4. **T0 rules it out unilaterally.** "the next move is a new amendment or it is a defect" (`p022-263-t0-protocol-freeze.md`, Amendment 1). The owner also "ruled out a compatibility shim on either side", so a v2 core that still accepts v1 is not an option either.
42+
43+
The P-022 freeze on verdict-changing inference itself is **not** the blocker: it lifted at the Stage 3 cutover (`P-022-rust-core-migration.md`, "With the cutover complete the P-022 feature freeze on verdict-changing inference lifts"). The blocker is the T0 harness binding that a version bump drags along.
44+
45+
**Precedent:** Amendment 1 is this exact situation (0 → 1 for `borrow_mut`/`move`), and the owner resolved it with one literal plus a re-binding of steps 4, 5 and 6.
46+
47+
## 4. What the owner is asked to rule
48+
49+
**Amendment 2: `OWNIR_VERSION` 1 → 2 for the `proven_call` op.** Mirroring Amendment 1:
50+
51+
| Item | What moves |
52+
|---|---|
53+
| Instrument | one literal in `perf_baseline.py`'s `facts` generator, `1` → `2`; nothing else in the harness source set |
54+
| Re-bound | T0 steps 4, 5, 6 (with `design_constants_blob_sha1`), to the new harness identity |
55+
| Re-run | the instrumentation controls Amendment 1 re-ran |
56+
| Unchanged | rules, budgets, populations, statistic, roll-ups, host predicate, retry budget |
57+
| Not rewritten | committed evidence of past runs |
58+
59+
**Mechanical churn of the bump itself:** three producer stamps (`ownlang/ownir.py`, `own-ir/src/lib.rs`, `Program.cs`) and 244 committed JSON fixtures carrying `"ownir_version": 1`:
60+
61+
| Fixture family | Files |
62+
|---|---:|
63+
| summaries | 82 |
64+
| lowered | 64 |
65+
| verdicts | 38 |
66+
| ownir | 22 |
67+
| repro | 19 |
68+
| verdict_renders | 9 |
69+
| cli_ownir | 4 |
70+
| `corpus/ownership-lab/h29` | 4 |
71+
| others | 2 |
72+
73+
All of them are regenerated by their own `--write` tooling, never by hand.
74+
75+
## 5. H1 as it would be built once ruled (design, ready)
76+
77+
- **Extractor (`ProtocolLowering`).** Only one site changes: an invocation inside a region that touches no entity and no token. Today it reaches `OpaqueCode` and is refused.
78+
- If its dispatch is `direct`, decided by the **same classifier H0 uses** (shared, not copied), it lowers to `{"op": "proven_call", "site": <id>, "line": n}`.
79+
- Every other call is refused exactly as today, with the same text: extern, virtual, delegate, local function, constructors, getters, operators.
80+
- An entity-touching call keeps today's `use` + core verdict.
81+
- **Site evidence.** Each site is a synthetic H0 record walked by the H0 producer over the call expression alone. Outer variables (the enclosing method's locals and parameters, the token) read as `heap`, so any mutation through them is a heap write, never silence.
82+
- **Section.** The facts carry a `heap_effects` section (H0 vocabulary v1): the site records plus the records reachable from them by `direct` edges. Selecting the records is closure only, not solving; a missing record solves to Unknown.
83+
- **Core (Python reference + Rust port, byte-parity).**
84+
- The section is required whenever a `proven_call` exists. Its absence, a missing site record, or a malformed section is an `OwnIRError` (refusal, exit 2).
85+
- The H0 solver solves the section. Each site is admitted iff its solved summary satisfies the predicate below; otherwise `OwnIRError` naming the site.
86+
- An admitted `proven_call` lowers to nothing: it touches no tracked resource.
87+
- **Predicate v1 (strict)** on the site summary, and so transitively on the callee:
88+
- no Unknown anywhere;
89+
- every parameter and the receiver at most `borrow`;
90+
- `writes.instance`, `writes.static` and `writes.indirect` all `none`;
91+
- `returns` is `[]` (inert, or no alias to a parameter, the receiver or the heap).
92+
- **Negative controls.**
93+
- A v1 core on v2 facts gives `OwnIRError`, on both engines.
94+
- A v2 core with the section removed gives `OwnIRError`.
95+
- A v2 core with a site record removed gives `OwnIRError`.
96+
- A mutant that reads Unknown as harmless must turn the suite red.
97+
- Python and Rust refusal texts are identical.
98+
- **Kill fixtures.**
99+
- `Twice` is admitted.
100+
- `Mutates(order)` and `Escapes(order)` touch the entity, so they keep `use` + verdict.
101+
- `TouchesGlobalState()`: refusal (static write).
102+
- `Console.WriteLine`: refusal in the extractor, unchanged text.
103+
- A → B pure: admitted. A → B mutating, escaping or global: refusal.
104+
- A pure SCC is admitted; an SCC with mutation, escape or Unknown is refused as a whole.
105+
- **Regression.** Protocol documents without a harmless call change only their version stamp. Their verdicts are unchanged on both CLIs.

0 commit comments

Comments
 (0)