Skip to content

Commit 877ee69

Browse files
authored
Merge pull request #390 from PhysShell/ccr-d3ed89e2-vnl6fk
feat(ownir)!: OwnIR v2 — must-understand proven_call (H1) + T0 Amendment 2
2 parents 1c70e86 + 450c39b commit 877ee69

440 files changed

Lines changed: 7864 additions & 1076 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎audit/static/run_static.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ def check(ok: bool, msg: str) -> None: # total derives from the call count
363363

364364
def _fake_own_check(target_, out_dir_, severity_, root=None):
365365
facts = Path(out_dir_) / "own-check.facts.json"
366-
facts.write_text(json.dumps({"ownir_version": 1, "module": "App", "components": [
366+
facts.write_text(json.dumps({"ownir_version": 2, "module": "App", "components": [
367367
{"name": "CustomerView", "file": "Views/CustomerView.xaml.cs", "subscriptions": [
368368
{"event": "_bus.Changed", "handler": "OnChanged", "line": 21,
369369
"released": False}]}]}), encoding="utf-8")
@@ -419,7 +419,7 @@ def _fake_own_check(target_, out_dir_, severity_, root=None):
419419
' x:Class="App.Views.CustomerView" Loaded="OnLoaded" />\n',
420420
encoding="utf-8")
421421
(out5 / "own-check.facts.json").write_text(json.dumps({
422-
"ownir_version": 1, "module": "Stale", "components": [
422+
"ownir_version": 2, "module": "Stale", "components": [
423423
{"name": "CustomerView", "file": "Views/CustomerView.xaml.cs",
424424
"subscriptions": [{"event": "_bus.Changed", "handler": "OnChanged",
425425
"line": 21, "released": False}]}]}), encoding="utf-8")

‎audit/static/tools/xaml_join.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def check(ok: bool, msg: str) -> None:
303303
"event_handlers": [{"event": "Loaded", "handler": "OnLoaded", "line": 4}],
304304
"bindings": [], "named_elements": []},
305305
]}
306-
ownir = {"ownir_version": 1, "module": "App", "components": [
306+
ownir = {"ownir_version": 2, "module": "App", "components": [
307307
{"name": "CustomerView", "file": "Views/CustomerView.xaml.cs", "subscriptions": [
308308
{"event": "_bus.Changed", "handler": "OnChanged", "line": 21, "released": False}]},
309309
{"name": "CleanView", "file": "Views/CleanView.xaml.cs", "subscriptions": [
@@ -363,7 +363,7 @@ def check(ok: bool, msg: str) -> None:
363363
wrong_ns = {"documents": [{"file": "Features/Billing/CustomerView.xaml",
364364
"x_class": "Billing.CustomerView",
365365
"event_handlers": [{"event": "Loaded", "handler": "OnLoaded", "line": 4}]}]}
366-
other = {"ownir_version": 1, "components": [
366+
other = {"ownir_version": 2, "components": [
367367
{"name": "CustomerView", "file": "Legacy/CustomerView.cs", "subscriptions": [
368368
{"event": "_bus.Changed", "handler": "OnChanged", "line": 9, "released": False}]}]}
369369
check(join(wrong_ns, other) == [],

‎docs/evidence/calibration/p022-263a-design-constants.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"artifact": "p022-263a-calibration-design-constants",
3-
"bound_measurement_harness_digest": "1a26aa63fd5fbbe06e7ae72dd5e1c8f2d611bff8856af4a5b93ae36d2d23a9b1",
3+
"bound_measurement_harness_digest": "b92f08c990fcf6d74df29aad333b330b6c3c4f9e1c1bad55efe8164c39dea57c",
44
"bound_policy_implementation_digest": "c3068ed7fa880a7083866ead25fe8bf65c87889d242d8af1f7eee01582cd5cbf",
55
"constants": {
66
"G": [

‎docs/evidence/calibration/p022-263a-policy-freeze.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"artifact": "p022-263a-calibration-policy-freeze",
3-
"measurement_harness_digest": "1a26aa63fd5fbbe06e7ae72dd5e1c8f2d611bff8856af4a5b93ae36d2d23a9b1",
3+
"measurement_harness_digest": "b92f08c990fcf6d74df29aad333b330b6c3c4f9e1c1bad55efe8164c39dea57c",
44
"policy_implementation_digest": "c3068ed7fa880a7083866ead25fe8bf65c87889d242d8af1f7eee01582cd5cbf",
55
"policy_implementation_digest_framing": "sha256 over the source set ordered by the UTF-8 bytes of each repo-relative POSIX path. Each file contributes, with no header and no separator: its path byte length as an 8-byte big-endian unsigned integer, its path's exact UTF-8 bytes, its blob byte length as an 8-byte big-endian unsigned integer, and its exact git blob bytes.",
66
"policy_source_commit": "b4f657a0abdfdfaae199cbc7eee0c47acd8b0057",

‎docs/evidence/calibration/p022-263a-training-preregistration.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
"anchor_commit": "eedf6d3ed44ecf7bda69fa509dcd23b45960cf76",
3131
"artifact": "p022-263a-calibration-training-preregistration",
3232
"bindings": {
33-
"design_constants_blob_sha1": "0ff316d5aea6af92c01679babaf0f0251191dcee",
34-
"measurement_harness_digest": "1a26aa63fd5fbbe06e7ae72dd5e1c8f2d611bff8856af4a5b93ae36d2d23a9b1",
33+
"design_constants_blob_sha1": "1059a69fe53ff3c2286414731e6104f848596ca7",
34+
"measurement_harness_digest": "b92f08c990fcf6d74df29aad333b330b6c3c4f9e1c1bad55efe8164c39dea57c",
3535
"policy_implementation_digest": "c3068ed7fa880a7083866ead25fe8bf65c87889d242d8af1f7eee01582cd5cbf",
3636
"training_scope_implementation_digest": "614bf9efe6ba2bb10e26a251c10d6c4a8fdaa99985d43ea03d76c6774447d25b",
3737
"training_scope_root": "scripts/training/"

‎docs/generated/p022-coord-census.md‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ Value classes follow the cp1 taxonomy's axis rather than blurring it: `outside-i
1010

1111
| measure | value |
1212
|------------------------------------|------:|
13-
| JSON files scanned | 625 |
14-
| coordinate slots found | 4175 |
13+
| JSON files scanned | 709 |
14+
| coordinate slots found | 4535 |
1515

1616
## By value class
1717

@@ -21,12 +21,12 @@ Value classes follow the cp1 taxonomy's axis rather than blurring it: `outside-i
2121
| `below-1` | 23 | 23 |
2222
| `bool` | 18 | 17 |
2323
| `float` | 3 | 3 |
24-
| `in-domain` | 3480 | 2193 |
24+
| `in-domain` | 3796 | 2424 |
2525
| `negative` | 26 | 26 |
26-
| `null` | 259 | 9 |
26+
| `null` | 261 | 9 |
2727
| `outside-int64` | 15 | 15 |
2828
| `string` | 19 | 19 |
29-
| `zero` | 308 | 26 |
29+
| `zero` | 350 | 26 |
3030

3131
## By family and slot
3232

@@ -162,10 +162,12 @@ Value classes follow the cp1 taxonomy's axis rather than blurring it: `outside-i
162162
| `heap_effects` | `summaries[].line` | `in-domain` | — | 56 | 10 | — |
163163
| `lowered` | `components[].subscriptions[].line` | `in-domain` | yes | 24 | 10 | — |
164164
| `lowered` | `functions[].<nested>[].column` | `in-domain` | yes | 2 | 2 | — |
165-
| `lowered` | `functions[].<nested>[].line` | `in-domain` | yes | 903 | 112 | — |
166-
| `lowered` | `functions[].params[].line` | `in-domain` | yes | 460 | 79 | — |
165+
| `lowered` | `functions[].<nested>[].line` | `in-domain` | yes | 1082 | 138 | — |
166+
| `lowered` | `functions[].params[].line` | `in-domain` | yes | 512 | 105 | — |
167167
| `lowered` | `functions[].params[].line` | `zero` | yes | 4 | 3 | — |
168-
| `lowered` | `handles[].line` | `in-domain` | — | 343 | 56 | — |
168+
| `lowered` | `handles[].line` | `in-domain` | — | 363 | 61 | — |
169+
| `lowered` | `heap_effects.methods[].calls[].line` | `in-domain` | — | 25 | 17 | — |
170+
| `lowered` | `heap_effects.methods[].line` | `in-domain` | — | 38 | 18 | — |
169171
| `lowered` | `services[].line` | `in-domain` | yes | 2 | 1 | — |
170172
| `ownir` | `components[].subscriptions[].column` | `in-domain` | yes | 2 | 1 | — |
171173
| `ownir` | `components[].subscriptions[].line` | `in-domain` | yes | 20 | 10 | — |
@@ -201,7 +203,7 @@ Value classes follow the cp1 taxonomy's axis rather than blurring it: `outside-i
201203
| `repro` | `traces[].layers[].steps[].value.params[].line` | `in-domain` | — | 4 | 1 | — |
202204
| `summaries` | `functions[].<nested>[].line` | `in-domain` | yes | 37 | 9 | — |
203205
| `summaries` | `functions[].params[].line` | `in-domain` | yes | 25 | 8 | — |
204-
| `summaries` | `summaries[].line` | `zero` | — | 252 | 63 | — |
206+
| `summaries` | `summaries[].line` | `zero` | — | 294 | 84 | — |
205207
| `verdict_renders` | `components[].subscriptions[].column` | `in-domain` | yes | 1 | 1 | — |
206208
| `verdict_renders` | `components[].subscriptions[].line` | `in-domain` | yes | 11 | 5 | — |
207209
| `verdict_renders` | `functions[].<nested>[].line` | `in-domain` | yes | 3 | 2 | — |
@@ -220,8 +222,8 @@ Value classes follow the cp1 taxonomy's axis rather than blurring it: `outside-i
220222
| `verdicts` | `effects[].line` | `negative` | yes | 1 | 1 | `-3` |
221223
| `verdicts` | `effects[].line` | `zero` | yes | 1 | 1 | — |
222224
| `verdicts` | `findings[].column` | `in-domain` | — | 17 | 7 | — |
223-
| `verdicts` | `findings[].column` | `null` | — | 198 | 93 | — |
224-
| `verdicts` | `findings[].line` | `in-domain` | — | 200 | 89 | — |
225+
| `verdicts` | `findings[].column` | `null` | — | 200 | 95 | — |
226+
| `verdicts` | `findings[].line` | `in-domain` | — | 202 | 91 | — |
225227
| `verdicts` | `findings[].line` | `zero` | — | 15 | 12 | — |
226228
| `verdicts` | `functions[].<nested>[].column` | `below-1` | yes | 1 | 1 | `0` |
227229
| `verdicts` | `functions[].<nested>[].column` | `in-domain` | yes | 7 | 2 | — |

‎docs/generated/p022-cp4-census.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ Computed by `tests/verdict_census.py` and `tests/verdict_render_census.py` (the
88

99
| measure | value |
1010
|-------------------------------------------------------------------------|------:|
11-
| goldens — Python's complete truth, one per planned case | 133 |
11+
| goldens — Python's complete truth, one per planned case | 154 |
1212
| … swept from `tests/fixtures/ownir` | 22 |
13-
| … swept from `tests/fixtures/lowered` | 64 |
13+
| … swept from `tests/fixtures/lowered` | 85 |
1414
| … swept from `tests/fixtures/summaries` | 9 |
1515
| … synthetic controls (`manifest.json` cases) | 38 |
16-
| reference refusals over all goldens | 9 |
17-
| reference findings over all goldens | 215 |
16+
| reference refusals over all goldens | 25 |
17+
| reference findings over all goldens | 217 |
1818
| declared Rust exclusions — the executable ledger `rust_replay_excluded` | 2 |
1919
| … refused at the typed `OwnIr` door (#294 OD-1) | 2 |
20-
| replayed by Rust (goldens minus exclusions) | 131 |
21-
| … reference refusals among them (compared in full) | 9 |
22-
| … findings among them (compared on every `Finding` member) | 213 |
20+
| replayed by Rust (goldens minus exclusions) | 152 |
21+
| … reference refusals among them (compared in full) | 25 |
22+
| … findings among them (compared on every `Finding` member) | 215 |
2323

2424
The differential counts over the replayed set — Python-only, Rust-only, changed, ordering-only, unexplained — are asserted, not measured here: the Rust replay compares every replayed case's full ordered verdict list (or its refusal text) against the golden on every member, collects every divergence without fail-fast, and fails if one exists. A green `cargo test -p own-bridge --test verdicts` is 0 / 0 / 0 / 0 / 0 by construction; a non-zero count is a red build.
2525

‎docs/generated/p022-cp5-inventory.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Checkpoint 4 proved identity, anchor, kind and tiering over the replayed set ([c
2626
| `flowlocal_own008` | bridge | flow-local release while borrowed | 1 | 1 |
2727
| `flowlocal_own011` | bridge | flow-local exclusive re-borrow | 4 | 4 |
2828
| `flowlocal_own012` | bridge | flow-local shared borrow under an exclusive one | 2 | 2 |
29-
| `flowlocal_own013` | bridge | flow-local direct use under an exclusive borrow | 5 | 5 |
29+
| `flowlocal_own013` | bridge | flow-local direct use under an exclusive borrow | 7 | 7 |
3030
| `flowlocal_own005_pool` | bridge | flow-local use after move on a pooled buffer | 1 | 1 |
3131
| `flowlocal_own007_pool` | bridge | flow-local consume/return while borrowed on a pooled buffer | 1 | 1 |
3232
| `flowlocal_own008_pool` | bridge | flow-local release while borrowed on a pooled buffer | 1 | 1 |

‎docs/generated/p022-shadow-census.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ acceptance work.
2424

2525
| corpus | documents |
2626
|---|---|
27-
| `tests/fixtures/lowered` | 64 |
27+
| `tests/fixtures/lowered` | 85 |
2828
| `tests/fixtures/ownir` | 22 |
2929
| `tests/fixtures/repro` | 3 |
3030
| `tests/fixtures/summaries` | 9 |
3131
| `tests/fixtures/verdicts` | 38 |
32-
| **total** | **136** |
32+
| **total** | **157** |
3333

3434
Every one of those documents is canonicalized and hashed by the reference
3535
(`ownlang/repro.py`) and re-hashed from the same file by the port
@@ -43,8 +43,8 @@ refuses to carry a foreign entry that has none rather than filling one in.
4343

4444
| surface | count |
4545
|---|---|
46-
| documents captured and digest-pinned | 136 |
47-
| tamper controls (one changed character per document, refusal required) | 136 |
46+
| documents captured and digest-pinned | 157 |
47+
| tamper controls (one changed character per document, refusal required) | 157 |
4848
| documents both engines must REFUSE to name (`domain_refusals`) | 6 |
4949
| reproduction artifacts committed and replayed byte-for-byte | 10 |
5050
| structural negative controls on `verify` (each side) | 34 |

‎docs/notes/h1-proven-call.md‎

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# H1 — proven calls in a state-protocol region (OwnIR v2)
2+
3+
Status: **landed** (owner ruling: OwnIR v2 and T0 Amendment 2 approved for H1).
4+
Base: `main` = `1c70e867eb408d931ae3dfffd8c0b351190f6547`. Follows
5+
[`h1-transport-stop.md`](h1-transport-stop.md), which found that the only
6+
fail-loud transport is a version bump, and [`heap-effect-summaries.md`](heap-effect-summaries.md)
7+
(H0), whose summaries this slice consumes.
8+
9+
## What changes for a C# user
10+
11+
```csharp
12+
static int Twice(int x) => x * 2;
13+
14+
Protocol.WithApproved(order, approved =>
15+
{
16+
var n = Twice(21); // before H1: refused. Now: admitted, clean.
17+
approved.Ship();
18+
});
19+
```
20+
21+
| Call inside a region | Before H1 | With H1 |
22+
|---|---|---|
23+
| direct call, touches no entity or token, proven harmless | refusal (extractor) | **admitted** |
24+
| same, but not proven harmless (a write, an escape, a global, Unknown anywhere) | refusal (extractor) | refusal (**core**, exit 2), naming the clause |
25+
| a call handed the borrowed entity (`Mutates(order)`) | `use` → verdict | unchanged |
26+
| an external, virtual, delegate or local-function call (`Console.WriteLine`) | refusal (extractor) | unchanged, same text |
27+
28+
## The transport
29+
30+
- **`proven_call`** (`site`, `callee`, `line`): a new flow op, so `OWNIR_VERSION` 1 → 2 (spec/OwnIR.md §2, §5.4).
31+
- **`heap_effects`:** a top-level section holding the H0 source facts. It holds one record per call site (the call expression walked like a body, every variable from outside it read as `heap`) and the records of the methods those sites reach through `direct` edges.
32+
- **The frontend decides nothing.** It emits a `proven_call` only for a call the summary layer could prove at all, meaning a call whose H0 dispatch is `direct`. The same shared classifier (`HeapEffectFacts.Dispatch`) produces both that decision and every H0 call fact.
33+
- **Fail-loud.** A v1 core refuses a v2 document on the stamp (IR1). With the stamp stripped it refuses on the unknown op (IR4). C1 below runs the real v1 core to show both.
34+
- **No compatibility shim** in either direction: a v2 core refuses v1 facts.
35+
36+
## The decision
37+
38+
The decision is made in the core and only there: `_admit_proven_calls` in `ownlang/ownir.py`, ported in `own-bridge/src/proven.rs`.
39+
40+
1. **When:** at the head of `to_module` / `lower_full`, before any function is lowered. The pass walks every function body through `then`/`else`/`body`, so no lowering path can carry a `proven_call` past it.
41+
2. **The predicate:** `heap_effects.site_verdict` / `harmless`. It lives in the shared summary layer, not in typestate code.
42+
3. **Strict on purpose:** a reference returned without an alias is still refused (`returns == []`). Unknown fails every clause it reaches.
43+
44+
Predicate v1. A site is admitted iff all of these hold:
45+
46+
- the site record exists and calls `callee` directly;
47+
- every call in the site is `direct` to a summarized method, and each such method is harmless;
48+
- the site's own solved summary is harmless.
49+
50+
A summary is harmless iff:
51+
52+
- every parameter and the receiver are at most `borrow`;
53+
- `writes.instance`, `writes.static` and `writes.indirect` are all `none`;
54+
- `returns` is `[]`.
55+
56+
Refusals are `OwnIRError`, with identical text in both engines, in this order (spec/Bridge.md BR-L14):
57+
58+
1. a malformed `site` or `callee`;
59+
2. a `proven_call` outside a region;
60+
3. no `heap_effects` section;
61+
4. a malformed section;
62+
5. no site record;
63+
6. a site that is not proven harmless.
64+
65+
## Evidence
66+
67+
**Kill fixtures.** Real C# files in `frontend/roslyn/protocol-samples/cases/H1*.cs`; the facts are committed as `typestate_cs_h1*`.
68+
69+
| Case | Result |
70+
|---|---|
71+
| `Twice(21)` | admitted, clean |
72+
| A → B → pure | admitted, clean |
73+
| pure SCC (`Even` ⇄ `Odd`) | admitted, clean |
74+
| `TouchesGlobalState()` | refused: `writes.static is may` |
75+
| A → B → global | refused: `writes.static is may` |
76+
| polluted SCC (`Ping` ⇄ `Pong` writes) | refused: `writes.static is may` |
77+
| A → `Console.WriteLine` | refused: `writes.instance is unknown` |
78+
| `Fill(buffer)`, outer array | refused: `parameter 0 is borrow_mut` |
79+
| `Mutates(order)` / `Escapes(order)` | OWN013, as before H1 |
80+
| R9 backdoor (`Backdoor.AnnotateLast()`) | refused, by the core now: `writes.instance is may` |
81+
| R18 `Console.WriteLine`, R19 interface call | refused by the extractor, unchanged |
82+
83+
**Compatibility controls** (`tests/test_proven_call.py`):
84+
85+
- **C1, old core on new facts.** `ownlang/` at `1c70e867` is taken out of git history and run on the extractor's v2 facts. It exits 2 on `schema v2, but this core understands v1`. With the stamp stripped it exits 2 on `unknown OwnIR flow op 'proven_call'`.
86+
- **C2/C3, missing or malformed evidence.** Eleven documents, each derived from the real H1a facts by one corruption, are refused. The Layer 2 goldens pin each text, and Rust replays them byte for byte. The corruptions:
87+
- no section;
88+
- no site record;
89+
- `site` is not a string;
90+
- empty `callee`;
91+
- the op outside the region;
92+
- a malformed section;
93+
- the wrong callee;
94+
- no callee record;
95+
- an Unknown callee;
96+
- an Unknown site;
97+
- a virtual call inside the site.
98+
- **C4, Unknown is poison.** A predicate that reads Unknown as harmless admits the transitive-Unknown fixture and both Unknown-derived documents. The real predicate refuses all three, so the pinned refusals go red under the mutant. The same mutant in the Rust port turns the Layer 2 replay red.
99+
- **C5, the op cannot be dropped.** Dropping the admission pass, or dropping the op from the facts, turns the four refused kill fixtures clean. Both differ from the pinned ledgers. The Rust mutant with no `admit` call is caught by the replay.
100+
101+
**Parity.**
102+
- The protocol gate's 29 C#-derived documents are byte-identical on both public CLIs.
103+
- The Layer 2, summaries, verdict, CLI, repro and validation ledgers are regenerated at v2 by their own writers and replayed by Rust.
104+
- The H0 sidecar goldens are unchanged.
105+
106+
## Not in this slice
107+
108+
- logger, BCL or annotation summaries: `Console.WriteLine` stays refused;
109+
- devirtualization;
110+
- property getters, constructors and operators inside a region: still refused by the extractor;
111+
- typed write targets, which would be needed to admit a write to state that is provably not the entity's type family;
112+
- DB-side mutation gaps.

0 commit comments

Comments
 (0)