feat(jeg): close all 17 deferred rules; report the one remaining hole - #104
Conversation
Seventeen rules matched a `-> ()` arm in `check`. That is not a partial
check — it is an ACCEPT. A forged node naming T-Close, T-App or T-Weave could
conclude any type at all and the graph still checked green. Since the JEG's
whole claim is "evidence, not a log", those arms were the part that made the
claim false.
## What made them underivable, and the fix
Three of them (T-App, T-Crossing, T-Weave) genuinely could not be re-derived
from the recorded data, because the judgement did not carry what the rule
reads. So the judgement was widened:
* `j_ctx` now holds `env_entry`, not `ty` — a T-App node cannot be checked
without the callee's SIGNATURE.
* `j_sigma` (new) holds the strand context — T-Crossing and T-Weave read
Sigma rather than premise types. They were previously bare LEAVES:
nodes asserting a type with nothing licensing it at all.
The rest were derivable all along and simply had not been written.
## Shared rule functions, so the JEG cannot drift
`infer_binop` was already a type-in/type-out function. The remaining rules
were inline in `infer_expr`, so the JEG would have had to RE-IMPLEMENT them —
and a JEG that re-implements the rules can drift from the typechecker, at
which point it certifies a rule the compiler does not apply and the evidence
is worthless.
Extracted 16 of them (`infer_close`, `infer_mirror`, `infer_echo_eq`,
`join_arm_ty`, ...) into one definition each, called by BOTH. Drift is now
impossible by construction rather than by discipline. The extraction is
behaviour-preserving: all pre-existing tests pass unchanged.
## Two rules with real substance
* T-Let checks the conclusion is the BODY's type AND that the body was
checked under the binding the let actually makes. Checking only the type
would accept a derivation whose body silently assumed `x` had some more
convenient type — there is a test for exactly that forgery.
* T-Weave re-runs the strand LINEARITY check, so a graph cannot launder a
weave that duplicates or drops a strand.
## The remaining hole is reported, not hidden
T-Add-Block's island has its own judgement (|-_hd). Rather than a silent
accept, it is recorded: new `unchecked : derivation -> (string * judgement)
list`. "check succeeded" and "check succeeded and re-derived every node" are
now distinguishable, and `--derive` prints which it was:
== c == (3 nodes, depth 3, every node re-derived)
## Verification
* JEG suite 19 -> 51 tests; an honest/forged PAIR for each rule closed, so
each test's forged half is a case that would have PASSED before
* 709 compiler tests pass (`dune runtest --force`)
* conformance 23/23, corpus + RSR gates green
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Note Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime. Code Review ✅ ApprovedCloses all 17 deferred rules in the JEG to eliminate partial acceptance holes, widening judgements and extracting shared rule functions to prevent typechecker drift. No issues found.
OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |
|
|
The problem
Seventeen rules matched a
-> ()arm incheck. That is not a partial check — it is an accept. A forged node namingT-Close,T-ApporT-Weavecould conclude any type at all and the graph still checked green.The JEG's entire claim is "evidence, not a log" — that a derivation can be validated without trusting whoever produced it. Those seventeen arms were the part that made the claim false.
Why three of them were genuinely underivable
T-App,T-CrossingandT-Weavecould not be re-derived from the recorded data, because the judgement didn't carry what the rule reads. So the judgement was widened:j_ctxnow holdsenv_entry, notty— aT-Appnode cannot be checked without the callee's signature.j_sigma(new) holds the strand context —T-Crossing/T-Weaveread Σ rather than premise types. They were previously bare leaves: nodes asserting a type with nothing licensing it.The other fourteen were derivable all along and simply hadn't been written.
Shared rule functions — the JEG now cannot drift
infer_binopwas already type-in/type-out. The remaining rules were inline ininfer_expr, so the JEG would have had to re-implement them — and a JEG that re-implements the rules can drift from the typechecker, at which point it certifies a rule the compiler doesn't apply and the evidence is worthless.Extracted 16 (
infer_close,infer_mirror,infer_echo_eq,join_arm_ty, …) into one definition each, called by both. Drift is impossible by construction rather than by discipline. The extraction is behaviour-preserving — every pre-existing test passes unchanged.Two rules with real substance
T-Letchecks the conclusion is the body's type and that the body was checked under the binding the let actually makes. Type-only checking would accept a derivation whose body silently assumedxhad a more convenient type — there's a test for exactly that forgery.T-Weavere-runs the strand linearity check from feat(typecheck): QTT quantity semiring — strands are linear in weave #103, so a graph cannot launder a weave that duplicates or drops a strand.The remaining hole is reported, not hidden
T-Add-Block's island has its own judgement (⊢_hd). Rather than a silent accept it's recorded — newunchecked : derivation -> (string * judgement) list. So "check succeeded" and "check succeeded and re-derived every node" are now distinguishable results, and--derivesays which:Verification
dune runtest --force)Follows #103 (merged).
feat/jeg-completeon origin is a superseded pre-rebase copy of this branch — safe to delete.🤖 Generated with Claude Code