Skip to content

Commit 209c96e

Browse files
Un-break CI: restore the DOC-04 banner, re-arm the disarmed gates, revert a stale merge's silent ledger regression (#744)
The `build` job has been red on `main` since **2026-06-28**. Three separable causes, one commit each. ## 1. The red itself (`3d25fb6`) `3661d98` removed the DOC-04 status banner from `README.adoc`, which `tools/check-doc-truthing.sh` requires. Restored, plus three references to `SOUNDNESS-LEDGER.adoc` — a file that does not exist — repointed at `docs/SOUNDNESS.adoc`. ## 2. Why nothing else reported (`5c0366a`) GitHub Actions skips every later step in a job once one fails. The doc-truthing failure therefore disarmed the soundness-ledger gate, the capability-anchor gate and the formatting check for ten weeks. They reported `skipped`, not `failure` — **worse than red, because `skipped` reads as absence of news rather than as a gate that never ran.** `if: ${{ !cancelled() }}` on all four makes each report its own verdict. ## 3. What the disarmed gate was hiding (`f3a70ae`) With the gates re-armed, the soundness-ledger gate failed on three of its five properties. The cause is not a soundness regression: `152d959` (2026-08-18) merged `origin/claude/capability-test-anchors`, a branch whose copy of `docs/SOUNDNESS.adoc` predated three fixes already on the main line. **It raised a real conflict, and the conflict was resolved by deleting main's side.** Replaying the merge shows this directly: ```console $ git merge-tree --write-tree 808e859 fb27a7f CONFLICT (content): Merge conflict in docs/SOUNDNESS.adoc # exit 1, two hunks ``` Git's own auto-merge output still contained both of main's fixes next to the conflict markers. Diffing that replayed tree against what was committed gives **60 deletions, 0 insertions** — the six marker lines plus 54 lines of main's side, nothing added: the signature of a pure "take theirs" resolution. Against its first parent the merge is 27 insertions / 51 deletions with **no new content**, reverting all three: 1. The #555 row was re-pointed at `test_resume_nontail_known_shallow`, a name that was only ever *planned* (in the 2026-06-17 handoff) and has never existed in the tree. The pin that does exist, `test_resume_nontail_xfail` (`test/xfail/test_xfail_pins.ml:56`), covers the same fixture and the same behaviour and is what the seal hashes. 2. The **Pinned-residual discipline** section was deleted — the section naming the `XPASS` hand-off as the one place the anti-drift machinery relies on a human. 3. Two claims regressed to being false: #624 restated as "flagged but not yet fenced" although `f020a6f` fenced it, and the mechanisation paragraph rolled back to "#620/#621", dropping P-2, P-3, F-3 and F-4. This commit restores the file to `808e859`, the main-line parent of that merge. Nothing has touched it since, so no later work is lost. ### The seal was never wrong — and was NOT resealed Property 3 fails with a message offering `--reseal (if intentional)`. Taking that offer would have hashed a test that does not exist: the computed digest was `e3b0c442…` — the SHA-256 of the empty string. **`tools/soundness-anchors.sha256` is unchanged.** The ledger was the side that drifted, and it is the side that was fixed. ## Verification All three gates run locally from a clean tree: ``` check-doc-truthing OK exit 0 check-soundness-ledger OK exit 0 all 5 properties (anchors + back-links + content-bound + stamp-fresh + pins live) check-capability-anchors OK exit 0 ``` Property 5 exercises the xfail harness, so its pass is a live confirmation that `test_resume_nontail_xfail` still fails-as-expected — the #555 hole is still pinned, not silently closed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 8abeaaa commit 209c96e

3 files changed

Lines changed: 68 additions & 30 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ jobs:
100100
# rationale + recovery instructions.
101101
run: ./tools/check-no-extension-ts.sh
102102
- name: Issue #176 — doc-truthing re-drift guard (DOC-01..09)
103+
if: ${{ !cancelled() }}
103104
# Single toolchain-free gate enforcing both halves of the doc-truthing
104105
# MONITOR: the presence invariants (DOC-04/05 — banner pointers, matrix
105106
# primacy + anti-over-claim section, STATE.a2ml mirror keys) AND the
@@ -108,6 +109,7 @@ jobs:
108109
# See tools/check-doc-truthing.sh.
109110
run: ./tools/check-doc-truthing.sh
110111
- name: Soundness-ledger anti-staleness gate
112+
if: ${{ !cancelled() }}
111113
# docs/SOUNDNESS.adoc is the single source of truth for soundness-hole
112114
# status. This gate fails if the ledger loses its primacy declaration or
113115
# freshness stamp, if any test fixture it names as an anchor goes
@@ -116,13 +118,15 @@ jobs:
116118
# See tools/check-soundness-ledger.sh.
117119
run: ./tools/check-soundness-ledger.sh
118120
- name: Capability-matrix test-anchor gate
121+
if: ${{ !cancelled() }}
119122
# docs/CAPABILITY-MATRIX.adoc anchors each feature-readiness claim to an
120123
# executable test ("== Test anchors"). This gate fails if that section
121124
# disappears or if any test it names goes missing — so a "works" status
122125
# row cannot outlive the test that backs it.
123126
# See tools/check-capability-anchors.sh.
124127
run: ./tools/check-capability-anchors.sh
125128
- name: Check formatting
129+
if: ${{ !cancelled() }}
126130
run: opam exec -- dune build @fmt
127131
lint:
128132
runs-on: ubuntu-latest

README.adoc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,21 @@ delivery mechanism. They _are_ the product.
2323

2424
== Status
2525

26+
[IMPORTANT]
27+
====
28+
Authoritative per-feature status lives in
29+
link:docs/CAPABILITY-MATRIX.adoc[docs/CAPABILITY-MATRIX.adoc]; soundness-hole
30+
status lives in link:docs/SOUNDNESS.adoc[docs/SOUNDNESS.adoc]. Where this
31+
README and those documents disagree, *they win* --
32+
`.machine_readable/descriptiles/STATE.a2ml` mirrors the matrix and does not
33+
lead it.
34+
====
35+
2636
Early and experimental. This is *v0.2*: the architecture is settled, the
2737
implementation and the metatheory are partial and still moving. It is suitable
2838
for experimentation, teaching, and small sound components -- not yet for
2939
production. What is proven, what is implemented, and what is still prose are
30-
stated plainly below and tracked in `SOUNDNESS-LEDGER.adoc`.
40+
stated plainly below and tracked in link:docs/SOUNDNESS.adoc[docs/SOUNDNESS.adoc].
3141

3242
== What you get
3343

@@ -142,7 +152,7 @@ risk here, and it is not yet solved.
142152
Soundness is *partially mechanised, and honestly tracked.* An initial,
143153
axiom-free, machine-checked result for code-generation preservation exists
144154
(Coq/Rocq). A number of residuals remain open; they are recorded -- not hidden
145-
-- in `SOUNDNESS-LEDGER.adoc`, which states for each claim whether it is
155+
-- in link:docs/SOUNDNESS.adoc[docs/SOUNDNESS.adoc], which states for each claim whether it is
146156
mechanised or still argued in prose.
147157

148158
The ledger is the source of truth for what currently holds. This README
@@ -192,7 +202,7 @@ totality cut, and a WebAssembly target -- rather than any one ingredient.
192202
== Documentation
193203

194204
// TODO: link the design notes and the examples directory once locations are stable.
195-
* Soundness status: `SOUNDNESS-LEDGER.adoc`
205+
* Soundness status: link:docs/SOUNDNESS.adoc[docs/SOUNDNESS.adoc]
196206

197207
== License
198208

docs/SOUNDNESS.adoc

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,17 @@ without reified continuations, and previously returned a silently-wrong value).
145145
| `test/e2e/fixtures/handle_resume_multishot.affine`; `lib/interp.ml` resume
146146
invocation counter
147147

148-
|*#555* (interp, non-tail single-shot)
149-
|`let x = op(); x + 100` resuming to `5` returns `5`, not `105`: the shallow
150-
tree-walking continuation has already unwound the bind chain. This is the one
151-
genuinely-still-silent shape, on the *interpreter* path only. It needs a CPS
152-
rewrite of `eval` (blocked: OCaml 4.14 has no native effect handlers and the
153-
interpreter must stay `js_of_ocaml`-compatible — owner-steer item). Tracked: #623.
154-
|*residual (pinned)*
155-
|`test/e2e/fixtures/handle_resume_nontail.affine` +
156-
`test_resume_nontail_known_shallow` (asserts the wrong-but-known value; flips
157-
to failing the day delimited continuations land)
148+
| #555 (interp, non-tail single-shot) / #623
149+
| `let x = op(); x + 100` resuming to `5` returns `5`, not `105`: the shallow
150+
tree-walking continuation has already unwound the bind chain. This is the one
151+
genuinely-still-silent shape, on the interpreter path only. It needs a CPS
152+
rewrite of `eval` (blocked: OCaml 4.14 has no native effect handlers and the
153+
interpreter must stay `js_of_ocaml`-compatible — owner-steer item, #623).
154+
| `residual (pinned)`
155+
| `test/e2e/fixtures/handle_resume_nontail.affine` + `test_resume_nontail_xfail`
156+
(xfail: asserts the correct `105`; currently fails-as-expected; flips to an
157+
unexpected pass the day delimited continuations land). See _Pinned-residual
158+
discipline_.
158159

159160
| #556
160161
| Async CPS table-miss fails loud instead of silently lowering synchronously.
@@ -218,26 +219,49 @@ The implementation holes marked `fixed` / `loud-fail` / `removed` above are
218219
closed. Honesty requires naming what is *not* a guarantee. Each item here is a
219220
row in the table above, restated for the hurried reader — not a separate list:
220221

221-
* *Interpreter non-tail resume* (#555 residual, pinned; tracked as #623) — see
222-
the ledger row.
223-
* *Stub backends drop `return`.* The Lean and Why3 *experimental* code
224-
generators drop `return` statements wholesale — a broader codegen-honesty gap
225-
than #555, flagged but not yet fenced (tracked as #624). Treat all
226-
non-reference backends as experimental (see `docs/CAPABILITY-MATRIX.adoc`).
227-
* *Generic-subsumption coherence* (#559 follow-up) — see the ledger row.
222+
* *Interpreter non-tail resume* (#555 / #623, `residual (pinned)`) — the
223+
`5`-not-`105` shape. Silent on the interpreter path; pinned by
224+
`test_resume_nontail_xfail`.
225+
(Stub backends dropping `return` (#555-stub / #624) is now `loud-fail` — Lean and
226+
Why3 reject an early `return` instead of silently dropping it — so it is no longer
227+
a "still open" residual. Treat all non-reference backends as experimental anyway,
228+
see `docs/CAPABILITY-MATRIX.adoc`.)
229+
230+
If you are deciding whether AffineScript is "sound enough" for a use, the
231+
interpreter non-tail resume row above is the one remaining residual, and the
232+
metatheory caveat below applies to it.
233+
234+
=== Pinned-residual discipline
235+
236+
A `residual (pinned)` / `open (tracked)` row carries an *xfail* test that asserts
237+
the *desired* behaviour and currently fails because the hole is present. The
238+
xfail harness (`test/xfail/test_xfail_pins.ml`) tolerates that expected failure
239+
and reports `XFAIL-OK`; if the assertion ever *passes*, the harness reports
240+
`XPASS`, exits non-zero, and the gate surfaces a distinct "pin for #N is passing
241+
— is the hole fixed? update the row" message.
242+
243+
That second job is a *social* guarantee wearing a mechanical costume. The harness
244+
mechanically catches the flip; but it relies on the engineer reading `XPASS` as
245+
"good — now advance the ledger row to `fixed`" rather than "annoying — silence
246+
it". The gate cannot tell those apart. So: *when an `*_xfail` pin reports `XPASS`,
247+
the correct first move is to open this ledger, not to silence the test.* This is
248+
the one place the anti-drift machinery hands off to a human, and it is named here
249+
so the hand-off is deliberate.
228250

229251
== Closed holes are not proofs
230252

231-
Closing an *implementation* hole is not the same as having *metatheory*. The
232-
soundness *arguments* for the holes above (`docs/academic/proofs/*.adoc`, the
233-
comments in `lib/borrow.ml`) remain `prose`, and the `Solo` core fragment's
234-
`progress` / `preservation` are still `?todo`. Mechanisation has *started* — an
235-
axiom-free Coq/Rocq `formal/` track (codegen-preservation: K-1
236-
`K1_CodegenPreservation.v`, K-1-with-`let` `K1Let_CodegenPreservation.v`, and
237-
the F-1 transformer-preservation composition; #620/#621) — but it does not yet
238-
cover any hole in this ledger. The proof obligations, their rigour tiers, and
239-
their (mostly `prose` / `absent`) status are catalogued in
240-
`docs/PROOF-NEEDS.adoc` (umbrella issue #513). The one-line distinction:
253+
Closing an implementation hole is not the same as having metatheory. The
254+
soundness arguments for the holes above (`docs/academic/proofs/*.adoc`, the
255+
comments in `lib/borrow.ml`) remain *prose*, and the Solo core fragment's
256+
`Soundness.idr` progress / preservation are still `?todo`. Mechanisation has
257+
*grown substantially* — the axiom-free Coq/Rocq `formal/` track now covers the
258+
Wave-0 codegen-preservation seed (K-1 `K1_CodegenPreservation.v`, K-1-with-`let`
259+
`K1Let_CodegenPreservation.v`), the F-1 transformer-preservation composition, and
260+
the four Wave-0 siblings P-2 (progress), P-3 (borrow-graph, with loan edges +
261+
move-locality), F-3 and F-4 (#620–#627). These target core/fragment metatheory;
262+
*none yet covers a hole in this ledger* — they are not the full AffineScript
263+
borrow checker / interpreter / backends. The obligations, their rigour tiers, and
264+
their exact status are catalogued in `docs/PROOF-NEEDS.adoc` (umbrella #513).
241265

242266
The one distinction to carry away, stated so the polish of *this ledger* is not
243267
mistaken for the rigour of the *metatheory*:

0 commit comments

Comments
 (0)