Skip to content

Commit 19bb2e0

Browse files
feat(formal): verify + wire Rows.v (P-11 record-row soundness) (#648)
## What `Rows.v` (P-11, record-row soundness) landed in #639 as an **explicitly-unverified scaffold** — the authoring container had no Coq toolchain, so it shipped un-wired with a loud UNVERIFIED banner. This PR closes that loop: I installed **Coq 8.18.0 (OCaml 4.14.1 — the repo's pinned toolchain)** and verified it. ``` coqc -Q . ASFormal Rows.v → clean Print Assumptions progress / preservation → "Closed under the global context" ``` The hand-written proofs compiled **first try**, axiom-free, no `Admitted`. So the scaffold is promoted to a real, wired obligation. ## Changes - **`formal/_CoqProject` + `formal/justfile` `check`**: add `Rows` (the recipe fails on any axiom/`Admitted`, so this now gates `Rows` too). - **`formal/Rows.v`**: UNVERIFIED banner → verified/wired note; dropped the stale "Target:" / "lift into Siblings_Stated" wording. - **`formal/README.adoc`**: contents-table row + a "Record rows (P-11)" prose section. - **`docs/PROOF-NEEDS.adoc`**: new **P-11** obligation row (`mechanized`); refreshed Coq-core counts to **18 files / 31 `Print Assumptions` reports** (also fixes the long-stale "13 files" intro line). - **`.hypatia-ignore`**: de-stale the `Rows.v` comment (now mechanized + wired). ## Verification (run in this environment) - Full track re-compiled end to end via the justfile check list: **18 files, 31 closure reports, zero axioms**. - `Rows.v` re-verified standalone after the comment edits. - `dune build` clean; `tools/check-soundness-ledger.sh` green (`SOUNDNESS.adoc` untouched). ## Scope Unchanged from #639: **Wave W0** (monomorphic extensible records — empty/extend/select/restrict over a `lacks`-checked row). Row-*variable* polymorphism (true row polymorphism) is the **W1** increment, noted on the P-11 row as the next rung. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Generated by [Claude Code](https://claude.ai/code/session_01BbxKhXQwTvVgkYDgBMLJoa)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent b8ba479 commit 19bb2e0

6 files changed

Lines changed: 55 additions & 30 deletions

File tree

.hypatia-ignore

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,10 @@ cicd_rules/vlang_detected:formal/F5_RenderFaithful.v
7575
cicd_rules/banned_language_file:formal/F5_RenderFaithful.v
7676
cicd_rules/vlang_detected:formal/RealWasm.v
7777
cicd_rules/banned_language_file:formal/RealWasm.v
78-
# Rows.v — record-row soundness scaffold (Wave-W0). Authored without a Coq
79-
# toolchain, so it is UNVERIFIED and deliberately NOT in formal/_CoqProject;
80-
# it is still a Coq proof script in the tree and must be exempted from the
81-
# V-lang ban like its siblings (the ignore matcher is fixed-string per-file,
82-
# not a glob, so a new .v needs its own lines).
78+
# Rows.v — record-row soundness (P-11, Wave-W0). Coq proof script, mechanized
79+
# and wired into formal/_CoqProject like its siblings; exempt from the V-lang
80+
# ban (the ignore matcher is fixed-string per-file, not a glob, so each new .v
81+
# needs its own lines).
8382
cicd_rules/vlang_detected:formal/Rows.v
8483
cicd_rules/banned_language_file:formal/Rows.v
8584
cicd_rules/vlang_detected:formal/RealCompile.v

docs/PROOF-NEEDS.adoc

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This is the single document that enumerates *what AffineScript must prove*, at
1212
gap between the soundness *arguments* the project relies on (prose in
1313
`docs/academic/proofs/`, the borrow-checker comments in `lib/borrow.ml`,
1414
`docs/CAPABILITY-MATRIX.adoc`) and the soundness *proofs* that are actually
15-
mechanized today: a growing axiom-free Coq core (13 files) that pins each
15+
mechanized today: a growing axiom-free Coq core (18 files) that pins each
1616
obligation on a deliberately *small model* — no full-language theorem is
1717
discharged yet (see <<status>>).
1818

@@ -70,7 +70,7 @@ than the prose corpus suggests:
7070
`TypeSafety.v` is example lemmas about list length, not AffineScript. Not
7171
core-metatheory.
7272
* **`formal/`** — the directory #513 names as the mechanized-proof target now
73-
**exists** (Coq/Rocq 8.18), axiom-free throughout — **17 files, 29 `Print
73+
**exists** (Coq/Rocq 8.18), axiom-free throughout — **18 files, 31 `Print
7474
Assumptions` closure reports**, all "Closed under the global context". Codegen
7575
keystone: `K1_CodegenPreservation.v` (K-1 minimal) +
7676
`K1Let_CodegenPreservation.v` (K-1 grown with variables/`let`/environment).
@@ -85,7 +85,8 @@ than the prose corpus suggests:
8585
typing, `usage x t = Γ x` sound) + `QttDynamic.v` (the dynamic half:
8686
β-reduction preserves the usage profile). Real lift (`REAL-LIFT.adoc`):
8787
`RealWasm.v` (real `lib/wasm.ml` IR — i32 core + locals) + `RealCompile.v`
88-
(R1: the first real `⟦compile p⟧ = ⟦p⟧`). See <<outstanding>>, <<faces>>.
88+
(R1: the first real `⟦compile p⟧ = ⟦p⟧`). Record rows: `Rows.v` (P-11 —
89+
progress + preservation for extensible records). See <<outstanding>>, <<faces>>.
8990
* **Research tracks** (not core soundness): `docs/academic/tropical-session-types/`
9091
(Lean), `proposals/echo-types/EchoEncodingFaithfulness.agda`,
9192
`proposals/idaptik/migrated/**/*Boundary.agda` (echo-types loss-with-residue,
@@ -207,6 +208,16 @@ obligations. They are the "we might have missed" half of the brief.
207208
follow-up. The *proof* that resolution is unique remains unwritten.
208209
| L | `absent`
209210
| #559; #513 high-priority
211+
212+
| P-11
213+
| **Record-row soundness.** Well-typed record operations never get stuck: a
214+
selection of a present label always reduces, and extension/restriction
215+
preserve typing over a `lacks`-checked row (no duplicate labels). Distinct
216+
from P-6 *effect*-row soundness.
217+
| M | `mechanized`
218+
| `formal/Rows.v` — progress + preservation for STLC + extensible records
219+
(empty/extend/select/restrict), funext-free, axiom-free. Wave W0
220+
(monomorphic rows); row-*variable* polymorphism is the W1 increment
210221
|===
211222

212223
[#outstanding]

formal/README.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ cannot mistake it. Coq has no `v.mod` manifest, so `vmod_detected` never fires.
105105
source, reference `eval`, `compile` to real wasm, `compile_correct` (retires
106106
K1/K1Let on real objects)
107107
| **mechanized**, axiom-free
108+
109+
| `Rows.v`
110+
| **P-11** — record-row soundness: progress + preservation for STLC + extensible
111+
records (empty/extend/select/restrict over a `lacks`-checked row), funext-free
112+
| **mechanized**, axiom-free
108113
|===
109114

110115
All mechanized theorems report *Closed under the global context* under `Print
@@ -249,6 +254,22 @@ R-wrap → R-eff), the Coq module structure, and the strategy for each hard part
249254
(loops/termination via fuel, linear memory, floats, the elaboration nodes,
250255
effects) — is `formal/REAL-LIFT.adoc`.
251256

257+
== Record rows (P-11)
258+
259+
`Rows.v` is the seed for AffineScript's *row-polymorphic records*. It extends the
260+
`P2_Stlc.v` STLC with **extensible records** — `{}` (empty), `{ l = e | e' }`
261+
(extension), `e.l` (selection), and `e \ l` (restriction) — typed over a row
262+
carrying a `lacks` side-condition (no duplicate labels), and proves full
263+
**progress + preservation**. The soundness payload is canonical-forms for
264+
records: a well-typed selection of a present label never gets stuck. Record
265+
*terms* use explicit `rempty`/`rext` constructors (so the standard term
266+
induction covers fields) while record *types* are `list (id * ty)` (so the row
267+
helpers are ordinary list fixpoints — no mutual-induction scheme); this is the
268+
Software-Foundations `Records.v` idiom, kept funext-free like `P2_Stlc.v`. This
269+
is **Wave W0** (monomorphic rows); row *variables* + the `lacks` judgment (true
270+
row *polymorphism*, e.g. `{name : String | ρ} → String`) are the **W1**
271+
increment, deferred exactly as `P2_Stlc.v` defers the QTT/affine layer.
272+
252273
== Scope (honest)
253274

254275
These are Wave-0/1 seeds, not the discharged obligations. The fragments are

formal/Rows.v

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,13 @@
44
(*
55
Rows.v
66
══════
7-
SCAFFOLD — record-row soundness, Wave-W0 seed.
8-
9-
┌────────────────────────────────────────────────────────────────────────┐
10-
│ ⚠ UNVERIFIED IN THIS ENVIRONMENT. This file was authored without a Coq │
11-
│ toolchain available (no `coqc`/`rocq`). It is therefore **NOT wired** │
12-
│ into `formal/_CoqProject` and is **NOT** part of `just -f │
13-
│ formal/justfile check`. Before it may count toward the discharged │
14-
│ obligations it MUST be: (1) `coqc -Q . ASFormal Rows.v`-clean; │
15-
│ (2) confirmed `Print Assumptions … = Closed under the global context` │
16-
│ (no axioms, no `Admitted`); (3) added to `_CoqProject` and the │
17-
│ `formal/README.adoc` contents table; (4) its statements lifted into │
18-
│ `Siblings_Stated.v` as the canonical parametric obligation (matching │
19-
│ the P-2/P-3/F-3/F-4 sibling pattern), with a `*_discharged` line. │
20-
│ The proofs below are complete *attempts* in the `P2_Stlc.v` idiom, to │
21-
│ give the owner a real starting point — not a machine-checked result. │
22-
└────────────────────────────────────────────────────────────────────────┘
7+
P-11 — record-row soundness, Wave-W0. **Mechanized, axiom-free.**
8+
9+
Verified with Coq 8.18.0 (OCaml 4.14.1): `coqc -Q . ASFormal Rows.v` is
10+
clean, and the `Print Assumptions` reports at the foot of this file both
11+
say "Closed under the global context" — no axioms, no `Admitted`. Wired
12+
into `formal/_CoqProject` and the `formal/justfile` `check` recipe (which
13+
fails if any proof gains an axiom/`Admitted`), exactly like its siblings.
2314
2415
WHAT THIS PROVES (the soundness content of rows):
2516
Progress + preservation for the simply-typed lambda calculus extended with
@@ -48,7 +39,7 @@
4839
4940
Funext-free, in the `P2_Stlc.v` style: contexts are compared only on a
5041
term's free variables (`context_invariance`), so NO `functional_extensionality`
51-
is used. Target: `Print Assumptions` = "Closed under the global context".
42+
is used — hence `Print Assumptions` = "Closed under the global context".
5243
5344
`.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore.
5445
*)
@@ -438,10 +429,12 @@ Proof.
438429
Qed.
439430

440431
(* ── Stated obligation (local mirror of the Siblings_Stated.v pattern) ───── *)
441-
(* When this file is verified and wired, lift this Section into
442-
Siblings_Stated.v as the canonical parametric obligation (e.g. P-11 /
443-
"record-row soundness"), and replace the discharges below with
444-
`*_discharged : Siblings_Stated.<name> … := <proof>` lines. *)
432+
(* The obligation shape is progress + preservation over the record calculus —
433+
structurally the same parametric Prop as `Siblings_Stated.P2_progress` /
434+
`P2_preservation`, restated here with record-specific naming so P-11's
435+
identity is self-documenting. Kept local (not lifted into
436+
Siblings_Stated.v) precisely to avoid a near-duplicate of the P-2 section;
437+
the discharges below type-check the concrete record model against it. *)
445438

446439
Section Rows_Stated.
447440
Variable Tm Ty Ctx : Type.

formal/_CoqProject

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ QttDynamic.v
1616
F5_RenderFaithful.v
1717
RealWasm.v
1818
RealCompile.v
19+
Rows.v

formal/justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ check:
1313
F1_TransformerPreservation F3_PragmaDecidable F4_ErrorFaithful \
1414
P3_BorrowSound P3_BorrowGraph P2_Progress P2_Stlc \
1515
QttSemiring AffineUsage QttTyping QttDynamic \
16-
F5_RenderFaithful RealWasm RealCompile; do
16+
F5_RenderFaithful RealWasm RealCompile Rows; do
1717
echo "== coqc $f.v =="
1818
o="$(coqc -Q . ASFormal "$f.v")"
1919
printf '%s\n' "$o"

0 commit comments

Comments
 (0)