|
| 1 | +// SPDX-License-Identifier: MPL-2.0 |
| 2 | +// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
| 3 | += Architecture Decision Record: 0005-trustedfixture-audit-boundary |
| 4 | +<!-- SPDX-License-Identifier: MPL-2.0 --> |
| 5 | +<!-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> --> |
| 6 | + |
| 7 | +# 5. `MkTrustedFixture` is the formal audit boundary for the `VerifierAccepts` / `SourceAccepts` predicates |
| 8 | + |
| 9 | +Date: 2026-06-02 |
| 10 | + |
| 11 | +## Status |
| 12 | + |
| 13 | +Accepted |
| 14 | + |
| 15 | +Closes |
| 16 | +https://github.com/hyperpolymath/typed-wasm/issues/103[issue #103]. |
| 17 | +Promotes the audit-boundary half of PR #79's design (commit `06ec00e` |
| 18 | +on `main`) from an inline comment in |
| 19 | +link:../../src/abi/TypedWasm/ABI/VerifierSpec.idr[`VerifierSpec.idr`] |
| 20 | +to a load-bearing architecture decision. |
| 21 | + |
| 22 | +## Context |
| 23 | + |
| 24 | +PR #79 landed TOTAL bodies (`%default total`; no `believe_me`, no |
| 25 | +`assert_total`, no `postulate`, no `sorry`, no `Admitted`) for both |
| 26 | +post-A10 audit obligations: |
| 27 | + |
| 28 | +* `verifierSpecAgreement : VerifierSpecAgreement` — bundles |
| 29 | + `verifierIsSound` (verifier ⇒ spec) and `verifierIsComplete` |
| 30 | + (spec ⇒ verifier). |
| 31 | +* `sourceVerifierAgreement : SourceVerifierAgreement` — bundles |
| 32 | + `sourceImpliesVerifier` and `verifierImpliesSource`. |
| 33 | + |
| 34 | +The lemma bodies are total by case analysis on the constructor of the |
| 35 | +input. Both `VerifierAccepts m` constructors — `VAStructural` and |
| 36 | +`VADifferential` — surface a `FunctionsAccepted m.functions` payload |
| 37 | +that wraps directly into `SpecAccepts`. `SourceAccepts m` mirrors the |
| 38 | +shape. The composition lemmas (`sourceImpliesSpec` / `specImpliesSource`) |
| 39 | +inherit totality. |
| 40 | + |
| 41 | +What did NOT change in PR #79: the differential constructors |
| 42 | +(`VADifferential`, `SADifferential`) still ultimately depend on a |
| 43 | +non-Idris-proven step — the Rust differential harness's |
| 44 | +accept-verdict. PR #79's design choice was to **package** that |
| 45 | +attestation inline, via the `TrustedFixture m` record: |
| 46 | + |
| 47 | +[source,idris2] |
| 48 | +---- |
| 49 | +record TrustedFixture (m : ModuleSummary) where |
| 50 | + constructor MkTrustedFixture |
| 51 | + trustedFixtureName : String |
| 52 | + trustedFixtureId : Nat |
| 53 | + trustedWitness : FunctionsAccepted m.functions |
| 54 | +---- |
| 55 | + |
| 56 | +The structural witness `trustedWitness` is **required** at construction |
| 57 | +time. Once a `MkTrustedFixture` value exists, every downstream consumer |
| 58 | +operates on a structural Idris2 proof; no further trust is injected |
| 59 | +anywhere in the chain. The trust-injection moment is therefore exactly |
| 60 | +the act of writing `MkTrustedFixture` in source. |
| 61 | + |
| 62 | +Issue #103 framed the residual obligation: |
| 63 | + |
| 64 | +> Closing this issue means **either** discharging those bodies to |
| 65 | +> constructive proofs **or** documenting the trust-injection moment |
| 66 | +> formally (single grep point: `MkTrustedFixture` construction). |
| 67 | + |
| 68 | +and listed three closure paths: |
| 69 | + |
| 70 | +. WasmCert-Isabelle tie-back for the Rust-verifier soundness step. |
| 71 | +. A constructive proof of the Rust verifier's soundness, in-tree. |
| 72 | +. An explicit ADR documenting `MkTrustedFixture` as the audit |
| 73 | + boundary. |
| 74 | + |
| 75 | +Paths (1) and (2) are both multi-month external/upstream commitments. |
| 76 | +Path (3) is the same audit story PR #79 already implemented as a |
| 77 | +docstring; promoting it to an ADR gives the inspection invariants a |
| 78 | +load-bearing home and a stable cross-reference target for the proof |
| 79 | +code and the proof-debt documents. |
| 80 | + |
| 81 | +## Decision |
| 82 | + |
| 83 | +Adopt `MkTrustedFixture` as the **formal, single, named audit |
| 84 | +boundary** for the `VerifierAccepts` / `SourceAccepts` predicates in |
| 85 | +link:../../src/abi/TypedWasm/ABI/VerifierSpec.idr[`VerifierSpec.idr`]. |
| 86 | + |
| 87 | +### What this means concretely |
| 88 | + |
| 89 | +. The audit-boundary statement is normative: every `VerifierAccepts m` |
| 90 | + or `SourceAccepts m` value whose history includes a |
| 91 | + `VADifferential` / `SADifferential` step **must** route through a |
| 92 | + `MkTrustedFixture` construction. The smart constructors |
| 93 | + `differentialAccepted` and `sourceAccepted` enforce this at the |
| 94 | + type level (both take a `FunctionsAccepted m.functions` argument |
| 95 | + and wrap it into a `MkTrustedFixture` inside the differential |
| 96 | + constructor — there is no other surface for differential |
| 97 | + acceptance). |
| 98 | +. `MkTrustedFixture` is the **single grep point**: a complete |
| 99 | + enumeration of trust injections is `git grep MkTrustedFixture` in |
| 100 | + the proof code (and its companion in the test/wiring code if a |
| 101 | + fixture-table module is added in future work). |
| 102 | +. The inspection invariants below (§Inspection invariants) define what |
| 103 | + reviewers verify at each `MkTrustedFixture` call site. These |
| 104 | + invariants are the spec the ADR pins; they replace the implicit |
| 105 | + "trust the harness" reading. |
| 106 | +. The closure paths (1) and (2) from issue #103 remain valid future |
| 107 | + upgrades. This ADR does NOT preclude them: an Isabelle tie-back or |
| 108 | + a constructive verifier-soundness proof would **subsume** the |
| 109 | + audit boundary by allowing every `VAStructural`/`SAStructural` |
| 110 | + construction to absorb what `VADifferential`/`SADifferential` |
| 111 | + currently delegate. Either upgrade would supersede this ADR. |
| 112 | + |
| 113 | +### Inspection invariants |
| 114 | + |
| 115 | +Every `MkTrustedFixture m` construction site is audited against the |
| 116 | +following three invariants. Inspection is the load-bearing step the |
| 117 | +ADR formalises. |
| 118 | + |
| 119 | +[discrete] |
| 120 | +==== I1. Provenance — fixture identity matches a harness row |
| 121 | + |
| 122 | +The pair `(trustedFixtureName, trustedFixtureId)` MUST correspond to |
| 123 | +an existing row in the Rust differential harness |
| 124 | +(link:../../crates/typed-wasm-verify/tests/cross_compat.rs[`crates/typed-wasm-verify/tests/cross_compat.rs`]). |
| 125 | + |
| 126 | +* `trustedFixtureName` MUST equal the harness fixture's name as it |
| 127 | + appears in the `cross_compat` test table. |
| 128 | +* `trustedFixtureId` MUST equal the harness row's numeric id (the row |
| 129 | + index used by the cross-compat report). |
| 130 | + |
| 131 | +Worked example (PR #79 commit `06ec00e`): |
| 132 | + |
| 133 | +[source,idris2] |
| 134 | +---- |
| 135 | +fixtureCleanLinearConsumerTrusted = MkTrustedFixture |
| 136 | + "fixture_clean_linear_consumer" -- name matches harness fixture |
| 137 | + 1 -- id matches harness row 1 |
| 138 | + fixtureCleanLinearConsumerWitness -- structural witness — see I2 |
| 139 | +---- |
| 140 | + |
| 141 | +[discrete] |
| 142 | +==== I2. Witness fidelity — `trustedWitness` matches the harness's verdict |
| 143 | + |
| 144 | +The `FunctionsAccepted m.functions` payload passed as `trustedWitness` |
| 145 | +MUST be the structural witness the harness's ACCEPT verdict |
| 146 | +establishes — i.e., the L7+L10 structural acceptance predicate over |
| 147 | +`m.functions`. In particular: |
| 148 | + |
| 149 | +* If the harness REJECTS the fixture, no `MkTrustedFixture` may be |
| 150 | + constructed for that fixture. (The reviewer should flag any such |
| 151 | + attempt; the type system already rules out fabricating a |
| 152 | + `FunctionsAccepted` for a fixture that lacks one — see the |
| 153 | + `notVerifierStructuralAcceptsBadDoubleConsume` family in |
| 154 | + `VerifierSpec.idr` for the negative side.) |
| 155 | +* If the harness ACCEPTS the fixture but for a strictly weaker |
| 156 | + property than `IntentsLinearAcceptable` (e.g., a partial decode |
| 157 | + pass), no `MkTrustedFixture` may be constructed at the full |
| 158 | + `FunctionsAccepted` strength. Add a strictly-weaker predicate |
| 159 | + instead, in a separate, audited follow-up. |
| 160 | + |
| 161 | +[discrete] |
| 162 | +==== I3. Module shape — `ModuleSummary` mirrors the harness's view |
| 163 | + |
| 164 | +The `ModuleSummary` value indexing the `TrustedFixture` MUST mirror |
| 165 | +the Rust harness's `ModuleSummary` reconstruction for the same |
| 166 | +fixture bytes: |
| 167 | + |
| 168 | +* Function order MUST match the harness's iteration order over the |
| 169 | + module's exports. |
| 170 | +* `OwnershipIntent` list per function MUST match the harness's |
| 171 | + decoded `typedwasm.ownership` carrier section (or its absence — |
| 172 | + see the cross-references below). |
| 173 | + |
| 174 | +A mismatch between the Idris2 `ModuleSummary` and the harness's |
| 175 | +decoded view is a fixture-wiring bug, not a soundness bug, but it |
| 176 | +voids `I2` and therefore voids the `MkTrustedFixture`. |
| 177 | + |
| 178 | +### What this does NOT do |
| 179 | + |
| 180 | +* It does not constitute a constructive proof of Rust-verifier |
| 181 | + soundness. The Rust verifier remains the trusted base for the |
| 182 | + differential path; the ADR pins the audit story for that trust, |
| 183 | + not its discharge. |
| 184 | +* It does not change the Idris2 source. The audit-boundary docstring |
| 185 | + PR #79 placed near `MkTrustedFixture` already states the same |
| 186 | + invariants in narrative form; this ADR is the load-bearing, |
| 187 | + cross-referenced version of that statement. |
| 188 | +* It does not gate the structural path. `VAStructural` and |
| 189 | + `SAStructural` continue to inject zero trust — they only require a |
| 190 | + pure Idris2 `FunctionsAccepted m.functions` value. The |
| 191 | + `verifierIsComplete` direction (spec ⇒ verifier) routes through |
| 192 | + `VAStructural` and therefore is trust-free in both PR #79 and this |
| 193 | + ADR. |
| 194 | + |
| 195 | +## Consequences |
| 196 | + |
| 197 | +### Positive |
| 198 | + |
| 199 | +* **Single grep point** — `MkTrustedFixture` is the unique |
| 200 | + trust-injection name. `git grep MkTrustedFixture` enumerates every |
| 201 | + audit-relevant call site, in proof code and test wiring alike. |
| 202 | +* **Total Idris2 chain preserved** — the audit boundary does not add |
| 203 | + any escape hatch (no `believe_me`, no `assert_total`, no `postulate`, |
| 204 | + no `sorry`, no `Admitted`); the trust budget lives in a record |
| 205 | + constructor, not in a soundness lemma. |
| 206 | +* **Stable cross-reference for proof-debt docs** — `PROOF-NEEDS.md` |
| 207 | + and `docs/proof-debt.md` can now point at ADR-0005 instead of |
| 208 | + re-stating the invariants inline. |
| 209 | +* **Explicit subsumption path** — a future WasmCert-Isabelle |
| 210 | + tie-back or constructive verifier-soundness proof has a named |
| 211 | + ADR to supersede, not an inline docstring to argue with. |
| 212 | +* **Audit checklist for reviewers** — the I1/I2/I3 invariants give |
| 213 | + PR reviewers a concrete, three-bullet checklist for any new |
| 214 | + `MkTrustedFixture` site. |
| 215 | + |
| 216 | +### Negative |
| 217 | + |
| 218 | +* **Trust is real and named** — the ADR makes explicit that the Rust |
| 219 | + verifier (and the source-side harness) remain the trusted base for |
| 220 | + the differential path. Anyone reading `verifierSpecAgreement.idr` |
| 221 | + as "no trust anywhere" is wrong; the audit boundary moved to |
| 222 | + `MkTrustedFixture`, it did not disappear. |
| 223 | +* **Per-fixture review burden** — adding a fixture means a manual |
| 224 | + invariant check against the harness; the ADR does not automate |
| 225 | + this. (A future CI script that grep-cross-references |
| 226 | + `MkTrustedFixture` against `cross_compat.rs` fixture names is a |
| 227 | + cheap follow-up.) |
| 228 | + |
| 229 | +### Neutral |
| 230 | + |
| 231 | +* The ADR does not change any Idris2 type. PR #79's record shape |
| 232 | + remains canonical. |
| 233 | +* The smart constructors `differentialAccepted` / `sourceAccepted` |
| 234 | + continue to be the recommended surface for constructing a |
| 235 | + fixture-backed acceptance witness; they are documented as the |
| 236 | + pre-`MkTrustedFixture` ergonomic wrapper. |
| 237 | + |
| 238 | +## Future work — when this ADR is superseded |
| 239 | + |
| 240 | +The two paths that would supersede this ADR by collapsing the audit |
| 241 | +boundary into a constructive proof: |
| 242 | + |
| 243 | +. **WasmCert-Isabelle tie-back** — port the Rust verifier's |
| 244 | + acceptance criterion to the WasmCert-Isabelle development and |
| 245 | + prove it sound against the WebAssembly spec's typing rules. The |
| 246 | + Idris2 side would then receive the Isabelle theorem as an external |
| 247 | + trust commit (the trusted base shifts to WasmCert-Isabelle), and |
| 248 | + `VADifferential` could be replaced with a constructor that |
| 249 | + carries the Isabelle theorem instead of a fixture row. |
| 250 | +. **Constructive Rust-verifier soundness** — prove the Rust verifier |
| 251 | + sound in a system (Rust + Creusot, Lean, Coq, or Idris2) that can |
| 252 | + be checked in the same CI run. The Idris2 side would then receive |
| 253 | + the verifier-soundness theorem and absorb the differential |
| 254 | + constructor into `VAStructural`. |
| 255 | + |
| 256 | +Either upgrade would land its own ADR that supersedes this one. Until |
| 257 | +then, the audit boundary is `MkTrustedFixture` and the inspection |
| 258 | +invariants are I1/I2/I3. |
| 259 | + |
| 260 | +## Cross-references |
| 261 | + |
| 262 | +* link:../../src/abi/TypedWasm/ABI/VerifierSpec.idr[`src/abi/TypedWasm/ABI/VerifierSpec.idr`] |
| 263 | + — the proof code this ADR pins. PR #79 / commit `06ec00e`. |
| 264 | +* link:../../crates/typed-wasm-verify/tests/cross_compat.rs[`crates/typed-wasm-verify/tests/cross_compat.rs`] |
| 265 | + — the Rust differential harness. Fixture names and row ids in I1 |
| 266 | + refer to entries in this file. |
| 267 | +* link:../../PROOF-NEEDS.md[`PROOF-NEEDS.md`] — proof-debt handoff. |
| 268 | + Long-tail item "Full proof bodies for `VerifierSpecAgreement` / |
| 269 | + `SourceVerifierAgreement`" is closed via this ADR (audit-boundary |
| 270 | + path). Cross-references back to ADR-0005 from the post-A10 / A13 |
| 271 | + RECONCILIATION banner. |
| 272 | +* https://github.com/hyperpolymath/typed-wasm/issues/103[issue #103] — |
| 273 | + this ADR closes #103. |
| 274 | +* https://github.com/hyperpolymath/typed-wasm/pull/79[PR #79] — landed |
| 275 | + the statement-level records and TOTAL bodies; this ADR formalises |
| 276 | + the audit-boundary half of that PR's design. |
| 277 | +* https://github.com/hyperpolymath/typed-wasm/pull/74[PR #74] — the |
| 278 | + superseded alternative design (Maybe-returning bridges + closed-world |
| 279 | + `RegisteredFixture` GADT). PR #79 picked the inline-witness shape |
| 280 | + this ADR pins; the `RegisteredFixture` machinery remains available |
| 281 | + as a composable add-on if a fixture-table module is added in |
| 282 | + future work. |
| 283 | +* link:../../PROOF-NEEDS.md[`PROOF-NEEDS.md` RECONCILIATION 2026-05-27 |
| 284 | + (post-A10 items 7 + 8 bodies closed)] — the prior reconciliation |
| 285 | + banner whose audit-boundary claim this ADR backs. |
0 commit comments