Skip to content

Commit d93dfa2

Browse files
committed
P-037 B0: RESULT PASS — proof boundary closed for Phase B shadow
docs/notes/p037-phase-b-proof-boundary.md §E records the result against the pre-registered §§A-D (left as registered). - N1-N8 hold; F1-F10 fire. - Inventory: 23 source / 15 fast + 8 heavy / 0 missing / 0 extra / 0 overlap. - 16 load-bearing assumptions, all classified. - #368: option C, a blocker before any schedule-taking API is used in production, enforced by an executable rule; #368 stays open. - R: fail-closed, record-absence-boundary pinned. Four assumptions were found by the audit and added as case-2 manifest amendments: - A13: cross-SCC export must be finalized; the kernel models one SCC; - A14: static dispatch, inherited from legacy; a phase-C owner decision, and the finding closest to KILL 9; - A15: masks and seeds come from a fail-closed body/sidecar line join. The candidate hidden precondition (placement not recoverable) was refuted by the probes; - A16: normal-return semantics; catch blocks are dropped, inherited. One instrument defect was fixed before the result: the first run flagged own-analysis's unrelated solve_with by name. The note lists 16 B1 entry obligations and the interpretations applied, for the owner to overrule. No semantic wiring. B1 is not started. Refs #304 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiyfrkaG9sJruTcshM2oq
1 parent 7a4f0e4 commit d93dfa2

1 file changed

Lines changed: 172 additions & 3 deletions

File tree

‎docs/notes/p037-phase-b-proof-boundary.md‎

Lines changed: 172 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# P-037 Phase B0: the formal/production proof-boundary audit (pre-registered)
22

3-
> Status: **PRE-REGISTERED. No manifest, checker, or audit code written.**
4-
> §§A–D are committed before any audit artifact, so the result is judged
5-
> against them rather than an impression afterwards. Governance:
3+
> Status: **RESULT: PASS — PROOF BOUNDARY CLOSED FOR PHASE B SHADOW (§E),
4+
> with 16 B1 entry obligations and 2 phase-C obligations. One phase-C item
5+
> (A14, static dispatch) needs an owner decision before phase C.**
6+
> §§A–D were pre-registered in `ee73dd1`, before any audit artifact, and are
7+
> left as registered; the result is judged against them rather than an
8+
> impression afterwards. Governance:
69
> `docs/notes/p037-formal-kernel.md` §10.1 (finding classes), §10.4 (the
710
> Phase-B proof boundary), §10.6 (the R ruling). Base: A2.2 closed on #371 at
811
> `0ae821356ae03cd185e845cc46be45b700f1e064`. Branch:
@@ -185,3 +188,169 @@ under KILL 9.
185188
**Budget.** One small checker (`scripts/p037_proof_boundary.py`), one manifest
186189
(`formal/p037-kernel/proof-boundary.json`), this note, small selftests and
187190
mutants. A new general verification framework is a STOP.
191+
192+
## E. Result (MEASURED OBSERVATION unless tagged)
193+
194+
```text
195+
PHASE: B0 proof-boundary audit
196+
RESULT: PASS — PROOF BOUNDARY CLOSED FOR PHASE B SHADOW
197+
SEMANTIC WIRING: NONE
198+
HARNESS INVENTORY: 23 source / 15 fast + 8 heavy / 0 missing / 0 extra / 0 overlap
199+
UNCLASSIFIED ASSUMPTIONS: 0 (16 load-bearing: WF, EWF, A1–A12, A13–A16)
200+
#368: C_BLOCKER — no production file may call a schedule-taking kernel API
201+
(executable rule); #368 stays open, a blocker before any such use
202+
R: FAIL-CLOSED — NO_GUARDED_EVIDENCE, record-absence-boundary pinned
203+
```
204+
205+
The artifacts:
206+
- `formal/p037-kernel/proof-boundary.json`, the manifest;
207+
- `scripts/p037_proof_boundary.py` (`--check`, `--selftest`, `--mutants`);
208+
- `tests/test_p037_proof_boundary.py` (`--check` and `--selftest` on every
209+
`tests/run_tests.py`);
210+
- a `--mutants` step in the `formal-p037` CI job;
211+
- the #368 witness `issue_368_unfair_schedule_returns_a_non_fixpoint` (a
212+
kernel test; no kernel code changed);
213+
- the probe evidence under `docs/evidence/p037-b0-probes/`.
214+
215+
### E.1 Necessary conditions
216+
217+
| # | result |
218+
|---|---|
219+
| N1 | holds. All three sets are derived from source: 23 `#[kani::proof]`, 15 in `ci.yml` `formal-p037`, 8 in `formal-p037-gate.yml` `heavy-kani`. Each loop really runs `cargo kani --harness "$h"`. The README's hand-kept "22 harnesses" was stale (k11b arrived in A0.5) and is replaced by a pointer to the derivation |
220+
| N2 | holds. Every harness has a claim, concrete kernel subjects (each checked to be a `fn` in `lib.rs`), its assumptions and its CI class |
221+
| N3 | holds. Every assumption has exactly one class. `PRODUCTION_GUARANTOR` is used only where the mechanism exists **today**: A1, A2, A5, A10 are producer invariants pinned by census shapes; A7 and A8 are production-tree checker rules. Every kernel-only witness is labelled `seam: kernel`, and the checker refuses to relabel one as production |
222+
| N4 | holds. `MAX_COORDS = 3` / `MAX_EDGES = 2` are checked against `lib.rs`. Production is `dynamic`, and `kani_proves_production_bound: false`. The Rocq results are named as external evidence only |
223+
| N5 | holds, as classified. Election shape/import (EWF, A2, A5), Uncond diagonal (A3), id/neg (A2), G-V4 (A1), cell-local release (A4), fin before apply (A8, A13), full visitation (A7) and R (A11) each have a guarantor or an OUTSIDE entry with a B1 obligation |
224+
| N6 | holds. `apply` and `lower` finalize by construction. F5a/F5b (removing either `fin`) are killed by the `k7_` twins (`--mutants`). The production-facing rule F9 fails the audit if a production file that imports the kernel lowers or collapses cells outside `apply` |
225+
| N7 | holds with **option C**. Measured: no production file references the kernel at all, so none calls `solve_with` / `elect_with` / `lfp_chaotic`, and rule F8 makes that executable. The #368 witness shows `Some(non-fixpoint)` for both the solver and the election. The dynamic driver must be a full sweep with no schedule parameter (A7). **#368 stays open** as a defect of the generic API and is a mandatory blocker before that API is used in production |
226+
| N8 | holds. A11 is `NO_GUARDED_EVIDENCE`, with eight forbidden positive readings. `record-absence-boundary` is its control, and F6 (reading absence as `borrow`) fails the audit |
227+
228+
### E.2 Falsifiers
229+
230+
| id | outcome |
231+
|---|---|
232+
| F1 | fires: a dropped harness gives `missing`, a fake name gives `extra`, and a loop that stops running `cargo kani` fails |
233+
| F2 | fires: `overlap` |
234+
| F3 | fires: no class, and a vague guarantor ("the frontend handles this") |
235+
| F4 | fires: a nonexistent guarantor path, and a nonexistent control symbol |
236+
| F5 | fires (`--mutants`): apply-without-fin is killed by `k7_unknown_opaque_and_differing_unselected_cells_never_consume` and `k7_witness_an_unfinalized_opaque_read_would_consume`; lower-without-fin is killed by the first |
237+
| F6 | fires: absence read as `borrow` |
238+
| F7 | fires: fairness "left to the caller" fails, and the #368 witness holds on the real kernel |
239+
| F8 | fires: a kernel-importing production file calling `solve_with`. **A same-named unrelated `solve_with` stays green** (see E.3 item 5) |
240+
| F9 | fires: a kernel-importing production file that runs `lower(c.collapse())` |
241+
| F10 | fires: `k10a` stops declaring `WF` although its body calls `any_system` |
242+
243+
The selftest also covers: A8 with apply ordered before fin; a claimed
244+
production-size bound; a formal-crate witness relabelled as production; and
245+
a recorded blocker, which fails the gate.
246+
247+
### E.3 Findings, classified under §10.1
248+
249+
1. **A13: cross-SCC export must be finalized (case 2).**
250+
- The kernel models one SCC; composition across SCCs is not modelled.
251+
- The F2 hazard exists at the SCC boundary too: through an Opaque edge a
252+
raw `(must, ⊥)` collapses to `must`, while `(must, no)` collapses to
253+
`may`.
254+
- The legacy driver already finalizes before export (`mos.rs`,
255+
`unwrap_or(Transfer::No)`). The guarded driver must do the same; this is
256+
a B1 obligation.
257+
2. **A14: static dispatch (case 2, inherited; phase-C obligation). This is
258+
the finding closest to KILL 9.**
259+
- The kernel reads the summary of the statically resolved callee. The
260+
sidecar has no dispatch fact, so the assumption cannot be discharged
261+
fail-closed from the A2 facts.
262+
- Why it is not a B0 kill:
263+
1. it is not new to P-037: legacy `ConsumesParam` and the MOS keying make
264+
the identical assumption in today's verdicts;
265+
2. B1 moves no verdict;
266+
3. `INFERENCE`: while `ConsumesParam` folds any-path disposal into a
267+
release, the guarded consume set through a virtual call stays within
268+
the legacy end-to-end one.
269+
- It becomes live once A1 removes that fold. **Owner decision needed before
270+
phase C**: either a dispatch fact (a case-5 OwnIR amendment), or a ruling
271+
that accepts the inherited assumption. B1's shadow report must state that
272+
its refinement counts are conditional on static dispatch.
273+
3. **A15: the G-S4 masks and G-S2/G-S3 seeds come from a body⋈sidecar join
274+
(case 2).**
275+
- The pre-registration's candidate hidden precondition was that forward
276+
placement cannot be recovered from the A2 facts. It was **refuted**:
277+
legacy lowering puts a `use` / `release` op in the body tree for a
278+
relevant call, and the sidecar's `statement_line` joins to it.
279+
- What remains is a join whose ambiguity is detectable in every probed
280+
case. That gives the fail-closed rule recorded in A15.
281+
- `INFERENCE`: this is a probe-based argument, not a proof. If B1 cannot
282+
make the join provably fail-closed, B1 must STOP and ask for a placement
283+
fact (case 5).
284+
4. **A16: normal-return-only semantics (case 2, inherited).**
285+
- The legacy body drops catch blocks, so a release inside a catch is
286+
invisible to legacy and guarded alike.
287+
- This is P-036 vertical B, not widened by P-037. It is a phase-C
288+
classification note.
289+
5. **Instrument defect, fixed before the result.**
290+
- The first `--check` run flagged `rust/crates/own-analysis` for
291+
`solve_with`. That is the Rust core's own worklist dataflow solver over a
292+
`Schedule` enum, not the P-037 kernel.
293+
- The rule matched the name alone. It is now scoped to files that name the
294+
kernel crate, and the selftest pins that a same-named unrelated function
295+
stays green.
296+
6. **Doc drift.** `formal/p037-kernel/README.md` said "22 harnesses"; the
297+
source has 23. The count is now derived, never written.
298+
299+
### E.4 Interpretations applied (for the owner to overrule)
300+
301+
- **"Production guarantor" means a mechanism that exists today.** Production
302+
does not call the kernel yet. Assumptions whose only guarantor would be the
303+
future B1 adapter or driver are therefore `OUTSIDE_KANI_BOUNDARY`, each with
304+
a named `b1_obligation` and `derivable_from_a2_facts: true`. None of them is
305+
relabelled as a guarantor.
306+
- **KILL 5 ("pinned at the seam production will use").** That seam is the
307+
kernel's `apply` / `lower`: A1 moves the kernel functions rather than
308+
rewriting them (§8.1, acceptance item 6). It is pinned by F5 and by the
309+
production-tree rule F9. No guarded production seam exists yet; porting the
310+
K7 witness to it is a B1 obligation (A8).
311+
312+
### E.5 B1 entry obligations (the manifest is normative; one line each)
313+
314+
| id | obligation |
315+
|---|---|
316+
| WF / EWF | enforce the well-formedness predicates at construction; violation → `NO_GUARDED_EVIDENCE` |
317+
| A2 / A5 | the binding and transform table (Id/Neg only for a resolved callee at the elected ordinal; `call_result` Opaque in the solver) |
318+
| A3 | Uncond seeds only via `Cells::diag` |
319+
| A4 / A15 | seeds and masks only through the fail-closed join; the B0 probe shapes become B1 controls |
320+
| A6 | the dynamic driver composes the kernel's own `read` / `contribute` / `join` / `import` |
321+
| A7 | full sweep, no schedule parameter |
322+
| A8 | application only through `apply`; the K7 witness ported to the B1 seam |
323+
| A9 / A10 / A11 | absence, unresolved callees and ambiguous joins are `Unknown` / `NO_GUARDED_EVIDENCE`, never ⊥ and never positive |
324+
| A12 | pass bound `n·HEIGHT+1`; exceeding it → `NO_GUARDED_EVIDENCE` |
325+
| A13 | export only finalized cells across SCCs |
326+
| A14 | the shadow report is conditional on static dispatch |
327+
328+
### E.6 B0 probe shapes (the A15 join, measured)
329+
330+
Sources: `docs/evidence/p037-b0-probes/Probe{,2,3}.cs`. The extractor output
331+
(bodies and sidecars only) is in `observed.json`, taken at base `0ae8213`
332+
with `--flow-locals`.
333+
334+
| probe | shape | observed | under the A15 rule |
335+
|---|---|---|---|
336+
| `Probe.Branchy` | statement forwards in then / else of a guard | `release@20` in then, `release@24` in else; one call per line | placeable |
337+
| `Probe.TwoIfs` | two eligible `if`s on one line | two `if@31` ops, no column; guards at columns 9 and 36 | guard↔`if` ambiguous → `NO_GUARDED_EVIDENCE` |
338+
| `Probe.Early` | `if (keep) return; Sink(s);` | `if@37 then:[return]`, `release@38` after | placeable (negative literal) |
339+
| `Probe2.InBranch` / `AfterIf` | a borrowing forward inside vs after a guarded `if` | `use@19` inside then vs `use@27` after the `if`; the facts differ | placeable: the indistinguishability hypothesis is **refuted** |
340+
| `Probe2.Behind` | a wrapper forward under an ineligible `if (ready)` | `if@33 then:[release@35]`, `guards: []` | placeable; the kept path must join `no` |
341+
| `Probe3.ShortCircuit` | `c && Ok(s)`, where `Ok` disposes | form `expression`; `if@17` with **empty** branches, so the consuming call has no body op | expression form → `NO_GUARDED_EVIDENCE` |
342+
| `Probe3.Ternary` | `c ? Use(s) : 0` | **no `functions[]` record** | R: `NO_GUARDED_EVIDENCE` |
343+
| `Probe3.Switch` | `case 1: Sink(s);` | lowered to `if@22 then:[release@24]` | placeable |
344+
| `Probe3.TryCatch` | `Sink(s)` inside a catch | form `statement`, but **no body op**; the body models only the try | no op → `NO_GUARDED_EVIDENCE`; a local release in a catch is invisible (A16) |
345+
| `Probe3.Loop` | a one-line `for` | `while@37` and `release@37` on one line | structural op on the line → `NO_GUARDED_EVIDENCE` (conservative) |
346+
| census `ctor-initializer` | `: base(s, keep)` | no body op for the initializer | no op → `NO_GUARDED_EVIDENCE` |
347+
348+
### E.7 What B0 did not do
349+
350+
- No semantic wiring. No change to guarded values, solver output, MOS,
351+
verdicts, `ConsumesParam`, Roslyn facts, the OwnIR schema or kernel
352+
semantics; the only kernel change is one new `#[test]`.
353+
- #368 not fixed.
354+
- No R value experiment. That measurement starts after the first real
355+
Phase-B shadow run (§10.6).
356+
- B1 not started.

0 commit comments

Comments
 (0)