@@ -129,69 +129,103 @@ data IntentsLinearAcceptable : List OwnershipIntent -> Type where
129129 IntentsLinearAcceptable rest
130130 -> IntentsLinearAcceptable (BorrowsExclusive s :: rest)
131131
132- ||| `SpecAccepts m` — the Idris2 spec accepts a module summary. Built
133- ||| from `IntentsLinearAcceptable` on every function's intent list.
134- ||| Stated as a `data` with one constructor so future extensions
135- ||| (L13 cross-module checks, L14 session-state) can be added as new
136- ||| constructors without breaking existing witnesses.
132+ ||| Per-function structural acceptance lifted across the function list,
133+ ||| matching how the verifier walks the module's export section. Used
134+ ||| by `SpecAccepts`, `VerifierAccepts`, and `SourceAccepts` so all
135+ ||| three agree on the structural witness shape at L7+L10.
136+ public export
137+ data FunctionsAccepted : List FunctionSummary -> Type where
138+ FANil : FunctionsAccepted []
139+ FACons : IntentsLinearAcceptable f.intents
140+ -> FunctionsAccepted rest
141+ -> FunctionsAccepted (f :: rest)
142+
143+ ||| `SpecAccepts m` — the Idris2 spec accepts a module summary. Wraps
144+ ||| `FunctionsAccepted` on the function list so spec / verifier /
145+ ||| source-checker share a single canonical structural witness at the
146+ ||| L7+L10 layer. Future extensions (L13 cross-module checks, L14
147+ ||| session-state) add new constructors without breaking existing
148+ ||| structural witnesses.
137149public export
138150data SpecAccepts : ModuleSummary -> Type where
139151 MkSpecAccepts :
140- (perFunction : (f : FunctionSummary)
141- -> Elem f m. functions
142- -> IntentsLinearAcceptable f. intents)
152+ FunctionsAccepted m. functions
143153 -> SpecAccepts m
144154
145155-- ============================================================================
146- -- Verifier acceptance (opaque — pinned by the differential harness )
156+ -- Verifier acceptance (inductive — structural + differential cases )
147157-- ============================================================================
148158--
149- -- The Rust verifier's acceptance set is not directly representable in
150- -- Idris2 (it consumes raw wasm bytes via wasmparser). We model it as
151- -- an OPAQUE predicate `VerifierAccepts m` indexed by the same
152- -- `ModuleSummary` shape; the differential testing harness in
153- -- `tests/cross_compat.rs` is what pins down whether the predicate
154- -- holds for a given module.
159+ -- The Rust verifier's acceptance set has two flavours of evidence:
155160--
156- -- The predicate's introduction rule is intentionally non-public: the
157- -- only legitimate way to construct a `VerifierAccepts` witness is
158- -- through `differentialAccepted` (below), which calls out the
159- -- harness's role. This makes the data flow auditable in the proof:
160- -- you can search for `MkVerifierAccepts` and find exactly the places
161- -- where the trust is being injected.
162-
163- ||| Opaque verifier-acceptance predicate. Constructible only by
164- ||| `differentialAccepted` (statement-level promise; no `Refl` body
165- ||| could ever justify it without the harness's external evidence).
161+ -- 1. STRUCTURAL — for modules whose ownership intents are visible at
162+ -- the typed surface, the verifier's L7+L10 acceptance is exactly
163+ -- the same predicate the spec uses. No trust-injection needed.
164+ -- 2. DIFFERENTIAL — for modules whose typed surface is partial (the
165+ -- Rust verifier inspects wasm bytes via wasmparser), the only
166+ -- legitimate way to assert verifier-acceptance is through a row
167+ -- in the differential testing table (`tests/cross_compat.rs`).
168+ --
169+ -- The earlier opaque design (A13) collapsed both cases into a single
170+ -- "external evidence" constructor. That made every agreement
171+ -- direction unprovable (no introspection into the witness). The
172+ -- inductive split below restores provability of the structural cases
173+ -- while keeping the differential case auditable.
174+
175+ ||| Inductive verifier-acceptance predicate, indexed by `ModuleSummary`.
176+ ||| Two constructors expose the trust shape:
177+ |||
178+ ||| * `VAStructural` — verifier acceptance derived from the same
179+ ||| structural predicate the spec uses. No external trust;
180+ ||| introspectable witness.
181+ ||| * `VADifferential` — verifier acceptance via the differential
182+ ||| harness. External trust pinned to a fixture row. Searching
183+ ||| for this constructor enumerates every trust-injection site.
166184public export
167185data VerifierAccepts : ModuleSummary -> Type where
168- MkVerifierAccepts :
186+ ||| Structural verifier acceptance: every exported function's
187+ ||| intent list passes the L10 single-consumption / L7 aliasing check.
188+ ||| This is the case the spec and the verifier agree on by definition.
189+ VAStructural :
190+ FunctionsAccepted m. functions
191+ -> VerifierAccepts m
192+ ||| Differential verifier acceptance: external evidence from
193+ ||| `tests/cross_compat.rs` pinning a fixture row. Constructible
194+ ||| only via `differentialAccepted` so the trust boundary is
195+ ||| inspectable.
196+ VADifferential :
169197 (differentialEvidence : String)
170198 -> (fixtureId : Nat )
171199 -> VerifierAccepts m
172200
173201||| Construct a `VerifierAccepts` witness from differential-harness
174202||| evidence (fixture name + numeric id from `tests/cross_compat.rs`).
175203||| Naming the fixture in the witness makes the trust boundary
176- ||| inspectable: every `VerifierAccepts ` use can be traced back to a
204+ ||| inspectable: every `VADifferential ` use can be traced back to a
177205||| concrete row in the differential table.
178206public export
179207differentialAccepted : (fixtureName : String) -> (fixtureId : Nat )
180208 -> VerifierAccepts m
181- differentialAccepted name fid = MkVerifierAccepts name fid
209+ differentialAccepted name fid = VADifferential name fid
182210
183211-- ============================================================================
184212-- Source-checker acceptance (item 8 surface)
185213-- ============================================================================
186214
187215||| Source-checker acceptance predicate. Like `VerifierAccepts`, the
188- ||| source checker's acceptance set is determined by an external
189- ||| implementation (the AffineScript front-end at present, replaced by
190- ||| an Idris2 parser when Track A lands). The predicate is opaque and
191- ||| witnessed by the source-side test harness.
216+ ||| source checker has both a structural surface (typed AST) and a
217+ ||| differential side (cross-checked against fixtures when the source
218+ ||| AST is partial). Same two-constructor shape, same audit story.
192219public export
193220data SourceAccepts : ModuleSummary -> Type where
194- MkSourceAccepts :
221+ ||| Structural source-checker acceptance: lifted from
222+ ||| `FunctionsAccepted` exactly as the spec sees it.
223+ SAStructural :
224+ FunctionsAccepted m. functions
225+ -> SourceAccepts m
226+ ||| Differential source-checker acceptance: external evidence from a
227+ ||| source-side fixture row.
228+ SADifferential :
195229 (sourceEvidence : String)
196230 -> (fixtureId : Nat )
197231 -> SourceAccepts m
@@ -200,7 +234,7 @@ data SourceAccepts : ModuleSummary -> Type where
200234public export
201235sourceAccepted : (fixtureName : String) -> (fixtureId : Nat )
202236 -> SourceAccepts m
203- sourceAccepted name fid = MkSourceAccepts name fid
237+ sourceAccepted name fid = SADifferential name fid
204238
205239-- ============================================================================
206240-- Agreement obligations — items 7 and 8
@@ -283,3 +317,151 @@ specImpliesSource :
283317 -> SourceAccepts m
284318specImpliesSource vsa sva m specAcc =
285319 sva. verifierImpliesSource m (vsa. verifierIsComplete m specAcc)
320+
321+ -- ============================================================================
322+ -- Structural agreement — first concrete witnesses (items 7 + 8, partial)
323+ -- ============================================================================
324+ --
325+ -- The full `VerifierSpecAgreement` / `SourceVerifierAgreement` records
326+ -- above remain obligations because their generality covers both
327+ -- structural and differential evidence: the differential case requires
328+ -- an external connection (wasm-bytes semantics or fixture-row trust)
329+ -- and is multi-week.
330+ --
331+ -- What IS provable, total, no-trust-injection, is the agreement
332+ -- restricted to the structural cases. The records and lemmas below
333+ -- give those a concrete home. Future work plugs the differential
334+ -- cases in by extending these records (e.g. by lifting fixture
335+ -- evidence into a stratified-acceptance predicate).
336+ --
337+ -- Convention: every name in this section ends in `Structural` so the
338+ -- restriction to the structural sublattice is visible at call sites.
339+
340+ ||| `FunctionsAccepted` directly witnesses `SpecAccepts`. This is the
341+ ||| spec's structural inhabitant: any function-list witness gives a
342+ ||| spec acceptance witness for the enclosing module.
343+ public export
344+ functionsAcceptedImpliesSpec :
345+ {m : ModuleSummary}
346+ -> FunctionsAccepted m. functions
347+ -> SpecAccepts m
348+ functionsAcceptedImpliesSpec fa = MkSpecAccepts fa
349+
350+ ||| Inverse direction: a spec acceptance witness contains the
351+ ||| structural per-function witness.
352+ public export
353+ specImpliesFunctionsAccepted :
354+ {m : ModuleSummary}
355+ -> SpecAccepts m
356+ -> FunctionsAccepted m. functions
357+ specImpliesFunctionsAccepted (MkSpecAccepts fa) = fa
358+
359+ ||| Spec → verifier (structural case). Lifts spec acceptance directly
360+ ||| into `VAStructural` — no trust-injection.
361+ public export
362+ specImpliesVerifierStructural :
363+ {m : ModuleSummary}
364+ -> SpecAccepts m
365+ -> VerifierAccepts m
366+ specImpliesVerifierStructural (MkSpecAccepts fa) = VAStructural fa
367+
368+ ||| Spec → source (structural case). Symmetric to the verifier
369+ ||| direction; lifts spec acceptance into `SAStructural`.
370+ public export
371+ specImpliesSourceStructural :
372+ {m : ModuleSummary}
373+ -> SpecAccepts m
374+ -> SourceAccepts m
375+ specImpliesSourceStructural (MkSpecAccepts fa) = SAStructural fa
376+
377+ ||| Verifier → spec (structural case only). Defined on `VAStructural`
378+ ||| witnesses; the `VADifferential` case is the multi-week obligation
379+ ||| and is therefore reflected as a `Maybe` here so totality is
380+ ||| preserved without `believe_me`.
381+ public export
382+ verifierImpliesSpecStructural :
383+ {m : ModuleSummary}
384+ -> VerifierAccepts m
385+ -> Maybe (SpecAccepts m)
386+ verifierImpliesSpecStructural (VAStructural fa) = Just (MkSpecAccepts fa)
387+ verifierImpliesSpecStructural (VADifferential _ _ ) = Nothing
388+
389+ ||| Source → spec (structural case only). Mirrors
390+ ||| `verifierImpliesSpecStructural` for the source side.
391+ public export
392+ sourceImpliesSpecStructural :
393+ {m : ModuleSummary}
394+ -> SourceAccepts m
395+ -> Maybe (SpecAccepts m)
396+ sourceImpliesSpecStructural (SAStructural fa) = Just (MkSpecAccepts fa)
397+ sourceImpliesSpecStructural (SADifferential _ _ ) = Nothing
398+
399+ ||| Bundle of structural-case agreement directions. Differs from
400+ ||| `VerifierSpecAgreement` / `SourceVerifierAgreement` in three ways:
401+ |||
402+ ||| 1. Restricted to the structural sublattice — `VADifferential`
403+ ||| and `SADifferential` are NOT covered.
404+ ||| 2. Provable as a total Idris2 value (`structuralAgreement` below).
405+ ||| 3. Symmetric across all three predicates simultaneously.
406+ |||
407+ ||| This is the first concrete agreement value in the codebase that
408+ ||| relates the spec / verifier / source-checker acceptance predicates
409+ ||| without invoking external evidence.
410+ public export
411+ record StructuralAgreement where
412+ constructor MkStructuralAgreement
413+ saSpecToVerifier :
414+ (m : ModuleSummary) -> SpecAccepts m -> VerifierAccepts m
415+ saSpecToSource :
416+ (m : ModuleSummary) -> SpecAccepts m -> SourceAccepts m
417+ saVerifierStructuralToSpec :
418+ (m : ModuleSummary) -> FunctionsAccepted m.functions -> SpecAccepts m
419+ saSourceStructuralToSpec :
420+ (m : ModuleSummary) -> FunctionsAccepted m.functions -> SpecAccepts m
421+
422+ ||| Concrete witness for `StructuralAgreement`. Total. No
423+ ||| `believe_me`, no `postulate`, no external trust. Closes the
424+ ||| structural-case portion of items 7 and 8 from the post-A10 audit.
425+ public export
426+ structuralAgreement : StructuralAgreement
427+ structuralAgreement = MkStructuralAgreement
428+ (\ m, sa => specImpliesVerifierStructural sa)
429+ (\ m, sa => specImpliesSourceStructural sa)
430+ (\ m, fa => functionsAcceptedImpliesSpec fa)
431+ (\ m, fa => functionsAcceptedImpliesSpec fa)
432+
433+ -- ============================================================================
434+ -- Concrete instance proofs — empty module
435+ -- ============================================================================
436+ --
437+ -- The empty-module case is the first concrete `SpecAccepts`
438+ -- inhabitant: no functions, so the per-function obligation is
439+ -- trivially satisfied. Used by the regression test as a smoke check
440+ -- that the predicates aren't vacuously unprovable.
441+
442+ ||| Structural witness for an empty function list. Closes the L7+L10
443+ ||| obligations vacuously.
444+ public export
445+ emptyFunctionsAccepted : FunctionsAccepted []
446+ emptyFunctionsAccepted = FANil
447+
448+ ||| The spec accepts every module whose function list is empty.
449+ ||| Concrete inhabitant of `SpecAccepts`. Demonstrates the predicate
450+ ||| is not vacuously empty.
451+ public export
452+ emptyModuleSpecAccepts :
453+ (n : String) -> SpecAccepts (MkModuleSummary n [])
454+ emptyModuleSpecAccepts n = MkSpecAccepts FANil
455+
456+ ||| The verifier accepts every empty-module summary via the structural
457+ ||| constructor (no differential evidence needed).
458+ public export
459+ emptyModuleVerifierAccepts :
460+ (n : String) -> VerifierAccepts (MkModuleSummary n [])
461+ emptyModuleVerifierAccepts n = VAStructural FANil
462+
463+ ||| The source checker accepts every empty-module summary.
464+ public export
465+ emptyModuleSourceAccepts :
466+ (n : String) -> SourceAccepts (MkModuleSummary n [])
467+ emptyModuleSourceAccepts n = SAStructural FANil
0 commit comments