Repository navigation
fix(abi): real jk_* FFI, ABI typechecks under Idris2 0.8.0, gates and claims ledger (JNK-01, C of 3) - #151
Conversation
…num codecs and a linear handle The previous Foreign.idr was the RSR template FFI (januskey_init, januskey_process, januskey_get_string, …) against template types (createHandle, handlePtr, Result) that Types.idr never defined. It never described ffi/zig, which exports jk_init … jk_version per ffi/zig/include/januskey.h. Now: - One %foreign declaration per exported symbol (13), C types as in the header; out-pointer parameters are caller-owned AnyPtr cells. - Status: a closed sum of the 12 header codes; statusCode / statusFromCode total; statusRoundTrip (one Refl per constructor) and statusCodeInjective (derived from the round trip, so it cannot drift from the table); statusFromInt refuses negatives and >11. decodeStatus keeps the raw int on failure; nothing is read as Ok by default. - opKindCode / algorithmCode in Types.idr's constructor order (= the header's enum order), each with a round-trip lemma. - LibHandle : LibState -> Type; closeOnce : (1 h : LibHandle Open) -> IO (LibHandle Closed) — destroy at most once, at the type. Scope stated in the docstring: linearity is a property of the binding; a handle bound by an IO <- is unrestricted, the guarantee holds for linear parameters. - Typed wrappers over the operations return Either Int Status. Not claimed: anything about the Zig implementation's behaviour (that is ffi/zig/test/integration_test.zig's job). Typechecks under Idris2 0.8.0 together with the next commit; this commit alone leaves Types/Layout/Proofs' known errors in place, so idris-abi.yml stays red until the stack lands. Refs: JNK-01 step 2 (ISSUES-2026-10-12). Assisted-by: Claude (claude.ai session, 2026-10-12) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9
…laced by theorems or removed The package had never typechecked (PR #140's "expected red"). Fixes, all semantics-preserving where the statement was true: - Types: import Data.String (isInfixOf) and Data.Nat; KeyId and ObliterationProof moved above ResultOf (forward references); MkHandle and MkTx now inhabit every state (their functions were unconstructible with a Pending/Available-only constructor); LinearFile and LinearKey keep the handle linear but their fields unrestricted (a linear field cannot be read twice or dropped); oblitMostExpensive (`So (3*n >= n)` by Oh on an open n) restated as oblitCostDominates : LTE n (3 * n) = lteAddRight n. - Layout: Aligned stated as divisibility with an explicit quotient (`mod` on Nat does not reduce, so `mod size boundary = 0` could never be Refl); bodiless copyDeleteCNO (false as stated: IsCNO for *any* op of kind Copy) replaced by copyOp + copyIsCNO, a theorem on the (path, hash) log model; canRollback (returned its argument) replaced by obliterateNotReversible. - Proofs: overwriteCoversAll via gteRefl; chainNoGaps no longer matches the index (EmptyChain has none: absurd); cnoPairCompose (Unit) replaced by seqOp + seqCNO (composition of CNOs is a CNO); tamperDetectable (Unit) replaced by tamperBreaksLink; errorCodeDeterministic now over Foreign.Status (its CError was never defined). Removed, reason recorded in source beside the replacement: memoryDefeatsGPU, timeCostMonotonic, outputIs256Bits, threePassMinimum, defaultExceedsMinimum, defaultMeetsOWASP, defaultMemoryOWASP, identityIsCNO, hashOneWay, linearFileConsumed (ill-typed; claimed LinearFile -> Void), systemForgetsOldKey. None was a statement about the thing its name claimed. Verified: idris2 --typecheck src/abi/januskey-abi.ipkg, 4/4 modules, zero warnings, Idris2 0.8.0 (bootstrap build, locally); no believe_me / assert_total / assert_smaller / %partial / holes in src/abi. Refs: JNK-01 steps 1–2 (ISSUES-2026-10-12), #145. Assisted-by: Claude (claude.ai session, 2026-10-12) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9
…us table in PROOF-NEEDS - scripts/check-abi-layout.sh (just abi-layout-check; CI step before the typecheck): Idris2 StructSize vs Zig @sizeof asserts, statusCode table vs januskey.h JK_* #defines, op-kind/algorithm tables vs the header enums, %foreign symbols vs header prototypes vs `pub export fn`s. No compiler needed. Negative control: one perturbed status code -> FAIL. - scripts/check-idris-reject.sh (just test-proofs-reject; CI step after the typecheck): each tests/reject-idris fixture is appended to a scratch copy of the package as JanusKey.ABI.Reject.<Name> and must be refused. DoubleClose ("linear name h in non-linear context"), ForgottenClose ("0 uses of linear name h"), UnknownStatusIsOk (unification) — all three refused under 0.8.0. - PROOF-NEEDS.adoc: status table A-1..A-5, C-1..C-5, G-1..G-3, X-1 in the five-status vocabulary with artefacts; the removed-placeholder list; model limits; note that README/TOPOLOGY/READINESS still carry the "does not typecheck (#145)" wording and #146 ledger counts, to be reconciled by dashboard-check when this stack lands (not hand-edited). - README.adoc: the one sentence about src/abi that became false, corrected (the caution that data-loss impossibility is not a theorem stays). Refs: JNK-01 (ISSUES-2026-10-12), #145, #146. Assisted-by: Claude (claude.ai session, 2026-10-12) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9
…ose now tests the second close
check-idris-reject.sh counted any refusal as the expected one. Pinning
each fixture to its message showed that DoubleClose was not testing what
it claims: it was refused at its *first* close ("Trying to use linear
name h in non-linear context", line 14 col 18), because IO's bind is not
linear. A single close in IO do-notation is refused the same way, so
the fixture never reached the second close.
- DoubleClose puts the two closes in a linear pair (`closeOnce h #
closeOnce h`) and is refused with "There are 2 uses of linear name h".
Positive controls, run locally and not committed: `closeOnce h` and
`closeOnce h # pure ()` both typecheck.
- Each fixture carries `-- expect: <text>`. The gate fails if that line
is missing, if the fixture typechecks, or if the message (Idris's echoed
` NN | ` source lines excluded, since they contain the expect comment)
does not contain the text. ForgottenClose expects "There are 0 uses of
linear name h"; UnknownStatusIsOk expects "Mismatch between: Just Ok and
Nothing".
- The gate takes optional fixture names, so one ledger row can run one
fixture; a named fixture that does not exist fails.
- Foreign.idr (docstring only): plain IO's bind is not linear, so a
linear LibHandle cannot be closed inside IO do-notation at all;
sequencing needs a linear monad (Control.Linear.LIO), not used here.
Negative controls (Idris2 0.8.0, not committed): a fixture refused for a
missing import -> FAIL; the old DoubleClose shape -> FAIL "refused, but
not with 'There are 2 uses of linear name h'"; no expect line -> FAIL; an
accepted fixture -> FAIL; an unknown fixture name -> FAIL.
tests/aspect/cross_cutting_test.sh: PROOF-NEEDS cites it for a census of
believe_me, assert_total, assert_smaller, partial and holes, but it
checked only believe_me, assert_total, postulate and sorry. It now also
checks assert_smaller, partial (also catching `%default partial`) and
holes (`?name`), with comment and docstring lines stripped: 32/32.
Controls: each bypass in a scratch module fails its own check; the same
words in comments and docstrings do not. The "(30+ proofs)" label on
the Proofs.idr existence check is dropped; that count was false.
Refs: JNK-01, #145.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9
…tems 3 and 5) #145 asks that adversaryCannotKnow be deleted or restated honestly and marked OPEN, and that the misleading "No postulate" header go. The stack did neither. - Proofs.idr header: "Proves correctness properties for all JanusKey operations ... no postulate — fully total" becomes a list of what the module holds, pointing at the claims ledger for what each states and at the aspect test for the bypass census. - Types.adversaryCannotKnow (definition unchanged): its docstring said "Proof: adversary cannot recover obliterated content". It now says the type is uninhabited only because Knows has no Adversary constructor, a property of the model, and that recovery after obliteration is OPEN in the ledger (row E-1, next commit). PostObliteration's docstring ("this is an impossibility proof") is restated as what it is, a marker. - Proofs.revokedKeyUnusable removed: it was adversaryCannotKnow at KeyId and took no revocation, so its name claimed what it did not state (the same reason the stack removed systemForgetsOldKey). Nothing referenced it. - Types.systemKnowsNewKey: "but not the old" dropped from its docstring; Knows has no negative form. idris2 --typecheck src/abi/januskey-abi.ipkg: 4/4, no warnings (0.8.0). Refs: #145. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9
…conciled; dashboard-check gets the provers
The fourth step of the runbook's T0: the status table in PROOF-NEEDS
("Same day, three commits later") enters the claims ledger, and the
dashboards take the count dashboard-check measures, not one written by
hand.
Ledger (26 rows, one line each, IDs matching the status table):
- PROVEN (8): A-1 statusCodeInjective, A-2 opKindRoundTrip, C-1 seqCNO,
C-2 copyIsCNO, C-3 obliterateNotReversible, C-4 tamperBreaksLink,
C-5 oblitAligned (command: idris2 --typecheck src/abi/januskey-abi.ipkg);
G-1 lossless-trace-all (command: bash scripts/check-agda.sh).
- TESTED (+5): A-3 check-abi-layout; A-4 DoubleClose and ForgottenClose;
A-5 UnknownStatusIsOk; the src/abi bypass census. The execute/undo row
is marked as G-1's cancellation premise.
- ASSUMED: G-2 (Idris2 IsCNO <-> Agda ReversiblePair, by transcription),
X-1 (hash one-wayness, physical erasure).
- OPEN: G-3 (bridge homomorphism), E-1 (attacker recovery / revoked-key
use; #145 item 3), the six security obligations, MPR linked to the
Rust. The two #145-pointing OPEN rows are replaced: their typecheck
half is now the PROVEN rows, and #145 closes with this stack.
Status table: one status per row from the five-status vocabulary. A-4
"PROVEN at the type; TESTED" -> TESTED (the fixtures are the evidence);
C-5 and G-1 qualifiers moved out of the status cell; X-1 "EXTERNAL" ->
ASSUMED (EXTERNAL is not a ledger status); E-1 added. Census line now
32/32. Current State, Recommended Prover (Agda kernel, Idris2 ABI, per
the 2026-10-12 ruling) and Priority no longer say the ABI is missing or
placeholder.
Dashboards: TOPOLOGY MPR line and READINESS justification, row 16,
totals and debt list say the ABI typechecks and 8 proofs are checked
(ABI and grade lemmas, not the security obligations). Positive control:
before these edits dashboard-check reported exactly the three stale
"0 proofs" claims (TOPOLOGY:80, READINESS:12, READINESS:67).
dashboard-check.yml: PROVEN rows run their checker, so the job now runs
in the pinned idris2-pack image idris-abi.yml uses, installs
agda-bin=2.6.3-1build1 from apt (the version every local check here
used) and curl for rustup; timeout 15 -> 30 min. check-abi-layout.sh
prints PASS on success, which a TESTED row needs.
Verified (Idris2 0.8.0, Agda 2.6.3): cargo run -p dashboard-check --
--check . -> OK, "119 tests measured, 8 PROVEN claims", every row
discharged. Negative control: a scratch copy with seqCNO's proof broken
fails all seven Idris PROVEN rows.
Refs: JNK-01, #145, #146.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
CI on
Generated by Claude Code |
|
@coderabbitai review Generated by Claude Code |
|
ad0c833
into
proofs/reversibility-grade
… Hypatia on #150) (#152) ## Summary Small follow-ups to the JNK-01 stack (#149, #150, #151, now on `main` as `ad85593` and `bf54181`). These findings posted after their PRs merged. - **`ffi/zig/include/januskey.h` (CodeRabbit, #149).** The header said `Foreign.idr` is "checked by the ABI package typecheck", but the typecheck never compares the two files. The comparison is `scripts/check-abi-layout.sh` (symbols, status codes, enums, sizes; ledger row A-3), and the comment now names it. - **`scripts/check-abi-tree.sh` (CodeRabbit, #149).** A dangling symlink at `src/idris-abi` passed `[ -e ]`, so the retired farm could come back as a broken link unnoticed. The check is now `-e || -L`. - **`.github/workflows/agda-proofs.yml` (Hypatia WH006 and missing_timeout_minutes, #150).** Job `proofs` had no `timeout-minutes`, so a hang would hold a runner for the 360-minute default. It now has 15 minutes, matching `idris-abi.yml`; the job takes about 35 s. - **`.github/workflows/agda-proofs.yml`.** The header still said "DESIGNED until its first green run". Its first run on #150 was green under Agda 2.6.4.3, with both fixtures refused for their stated reasons. ## Type of change - [x] 🐛 Bug fix: the tree gate missed a dangling link - [x] 🔧 Build / CI / tooling: job timeout - [x] 📖 Documentation: two comments made true ## 📌 New pins None. ## How has this been verified? - **Reproduction:** with `ln -s does-not-exist src/idris-abi`, the old `check-abi-tree.sh` exits 0 and the new one exits 1 with `FAIL: src/idris-abi exists (retired symlink farm)`. - `bash scripts/check-abi-tree.sh` → `OK: one ABI tree (4 modules, 1 package)`. - `bash scripts/check-abi-layout.sh` → `PASS`. The edited header line contains nothing the layout gate parses. - `agda-proofs.yml` parses, `jobs.proofs.timeout-minutes` is 15, and its SPDX header stays on line 1. ## Checklist - [x] Commits are signed. - [x] Project checks pass locally (above). - [ ] New files carry SPDX identifiers: n/a, no new files. - [x] Docs updated (the two comments). - [x] No soundness hole introduced: the gate is stricter, nothing else changes. ## Notes for reviewers The red checks that are already red on `main` (the #135 set) will show here too; #149 has their causes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9 --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
JNK-01, stacked PR C of 3. The base is B, #150, which sits on A, #149. Review the commits from
0e477cbon. With this PR the Idris2 ABI typechecks, its obligations are gated, and the dashboards carry a proof count thatdashboard-checkmeasures. It covers #145's acceptance criteria; #145 will be closed with the gate link once this is green.0e477cbForeign.idrdeclares the 13 realjk_*symbols fromjanuskey.h;Statusclosed sum with total decoder, round-trip and injectivity; op-kind/algorithm codecs;closeOnce : (1 h : LibHandle Open) -> IO (LibHandle Closed)7ff5767seqCNO,copyIsCNO,tamperBreaksLink,obliterateNotReversible, or removed91ee42fcheck-abi-layout.sh(Idris ↔ header ↔ Zig),check-idris-reject.shwith three fixtures, status table in PROOF-NEEDS922fa43DoubleClosewas not testing a double close (below). The aspect-test census now coversassert_smaller,partialand holes, as PROOF-NEEDS already claimed it did8327321revokedKeyUnusableremoved: it took no revocationc132e0fdashboard-check.ymlgets Idris2 and Agda4a89651Two findings the handoff did not have
DoubleClosedid not test a double close. Idris2 refused it at the firstcloseOnce h(line 14, col 18) with "Trying to use linear name h in non-linear context". IO's>>=is not linear, so a single close indois refused the same way. The fixture now puts both closes in a linear pair (closeOnce h # closeOnce h) and is refused with "There are 2 uses of linear name h". Positive controls typecheck:closeOnce handcloseOnce h # pure (). The consequence, now inLibHandle's docstring: a linearly held handle cannot be closed inside plain IOdoat all. Sequencing linear steps needsControl.Linear.LIO, which this module does not use.-- expect:comment matched itself, and my first wrong-reason control passed. The gate now matches message lines only (NN |excerpts excluded), and that control now fails.How has this been verified?
HANDOFF §3, run exactly on the unmodified five-patch stack (
d565dbf), Idris2 0.8.0 (bootstrap build of tag v0.8.0) and Agda 2.6.3Every line matches the handoff. Filtered for length: Agda's per-dependency "Checking" lines, the 29 individual aspect
[PASS]lines and section banners.The empty
()reasons and theDoubleClosereason ("non-linear context") are the two anomalies that commits5f17a45(in B) and922fa43fix.This head:
just test-proofs test-proofs-reject test-agdaand the aspect testcargo run -q -p dashboard-check -- --check .on this headThe command exits 0 and every row is discharged; the final line is
✓ 119 tests measured, 8 PROVEN claims.seqCNO's proof broken, all seven Idris PROVEN rows fail with`idris2 --typecheck src/abi/januskey-abi.ipkg` failed. This is the first time PROVEN has been exercised on real rows rather than the unit tests' fake runner (P1-0 (B): claims ledger — run every TESTED row, reconcile test/proof counts #146).Negative controls for the gates, run locally and not committed:
DoubleCloseshape fails ("refused, but not with 'There are 2 uses of linear name h'"); a missing expect line fails; an accepted fixture fails; an unknown fixture name fails.assert_smaller, barepartial,%default partialand?holein a scratch module fails its own check, and the same words in comments or docstrings do not.Status rows moved
Foreign.idr::statusCodeInjective,::opKindRoundTrip;Proofs.idr::seqCNO,::tamperBreaksLink;Layout.idr::copyIsCNO,::obliterateNotReversible,::oblitAligned; commandidris2 --typecheck src/abi/januskey-abi.ipkgReversibility.agda::lossless-trace-all; commandbash scripts/check-agda.shcheck-abi-layout.sh;tests/reject-idris/{DoubleClose,ForgottenClose,UnknownStatusIsOk}.idr;tests/aspect/cross_cutting_test.shType of change
ffi/zig📌 New pins
dashboard-check.ymlruns inghcr.io/stefan-hoeck/idris2-pack@sha256:f0758996a931fb35d9ecb1de273c4d59dabe2a09b433afc7e357f65a08b7e1ff. This is the digestidris-abi.ymlalready uses; it is new to this workflow.agda-bin=2.6.3-1build1(Ubuntu noble), installed in that container, the version every local check here used. Assumption to confirm in CI: that the pinned pack image is the noble build. pack publishes noble and jammy;latestis noble. I could not read the image config from this session.Checklist
gpgsigheader. Note: the merge commit4a89651lacks the session trailer lines; it carries no content of its own.dashboard-check, not written by hand.Notes for reviewers
seqOpkeeps the first op's kind tag.ReversibleOphas no sequence kind, and nothing depends on the tag. Documented at the definition.LibHandlevsTypes.Handleare two notions, deliberately.LibHandleis the wire handle with ownership.Types.Handleis the L7 aliasing model.generated/idrisiser/. Regenerate it with capitalised module names, or remove it. It is outside the gate and still does not typecheck.STATE.a2ml[test-coverage], a dated 2026-04 snapshot that still saysidris2-proofs = "30", and itsidris2-in-ciblocker.dashboard-checkdoes not reconcile those fields. Owner's call.🤖 Generated with Claude Code
https://claude.ai/code/session_01Kjdf4Mf4Z41bNQh2TrsEp9
Generated by Claude Code