Skip to content

Commit a1e9cbc

Browse files
theory: EchoSearch — Axis 8 witness-search machine (thin slice) (#80)
Adds `proofs/agda/EchoSearch.agda` (+ `EchoSearchInstance.agda` for smoke pins), the enumerator-bounded witness-search refinement of `Echo` named in `docs/echo-types/taxonomy.md` §Axis 8 / refinement (4) "witness-search abstract machine". ## What lands - `SearchStrategy A := ℕ → A` (a total enumeration; partiality is a separate axis-8(2) refinement) - `EchoS enum f y n := Σ ℕ λ k → (k < n) × (f (enum k) ≡ y)` — the bound-indexed witness, using stdlib `Data.Nat._<_` so `<-≤-trans` / `≤-<-trans` apply without conversion glue - Headlines `echo-search-intro`, `echo-search-relax` (bound monotonicity via `<-≤-trans`), `echo-search-forget` (project to plain `Echo`), `echo-search-bound-zero` (vacuity at budget 0), `echo-search-postcompose` (post-compose with `g`, same index, same bound) - `EchoSearchInstance.agda` pins each headline at the trivial `A = B = ⊤` enumerator (mirrors the `EchoApproxInstance` pattern so `Smoke.agda` can enumerate the parameterised lemmas in a `using` clause) - Wired into `All.agda` + `Smoke.agda` ## Design notes The roadmap term "abstract machine" admits several honest readings; the smallest is **enumerator-bounded witness** — a search strategy `enum : ℕ → A` paired with a step budget `n : ℕ`. The `ℕ`-bound *is* the step budget; a heavier machine (Turing-bounded, polynomial-time, configurations + step relation) projects onto this thin slice by forgetting everything except the index queried and the bound queried under. This sits at the bottom of the axis-8(4) lattice in the same sense `EchoDecidable` sits at the bottom of axis-8(3). **On composition.** The brief asked for "a sensible compositional rule" and flagged that a full product-strategy compose may be awkward without more machinery. I shipped `echo-search-postcompose` — the genuinely-honest compositional content available without further machinery: a bound-`n` search witnessing `f` at `y` also witnesses `g ∘ f` at `g y`, at the **same** step index and the **same** bound (the enumerator and queried step have not changed; only the answer has). Sequential / product-strategy composition `EchoS enum f b n₁ → EchoS enum' g y n₂ → EchoS (paired) (g ∘ f) y (n₁ * n₂)` needs a `ℕ × ℕ ↔ ℕ` pairing bijection and is deferred to a separate slice; this and a real abstract-machine refinement + a bridge to `EchoDecidable` are documented in the module's "where next" section. ## Invariants - `--safe --without-K` on every new module - No postulates, no funext, no escape pragmas, no `believe_me`-equivalents - Clean-worktree Agda build green: `agda --safe --without-K All.agda` exits 0 (Smoke pins all resolve) Refs Axis 8(4) — `docs/echo-types/taxonomy.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0f37f12 commit a1e9cbc

4 files changed

Lines changed: 274 additions & 0 deletions

File tree

proofs/agda/All.agda

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ open import EchoCost
2525
open import EchoCostInstance
2626
open import EchoIndexed
2727
open import EchoDecidable
28+
open import EchoSearch
29+
open import EchoSearchInstance
2830
open import EchoAccess
2931
open import EchoFiberCount
3032
open import EchoEpistemicResidue

proofs/agda/EchoSearch.agda

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
{-# OPTIONS --safe --without-K #-}
2+
3+
-- EchoSearch — Axis 8 witness-search abstract machine (thin slice).
4+
--
5+
-- Axis 8 of `docs/echo-types/taxonomy.md` distinguishes
6+
-- information-theoretic inhabitation of `Echo f y` from a
7+
-- *computational* witness extracted by an algorithm. Refinement (4)
8+
-- in that section names the heaviest reading:
9+
--
10+
-- "Witness-search abstract machine. Model the extractor as a
11+
-- term in a bounded-step abstract machine and pair it with
12+
-- the echo."
13+
--
14+
-- A faithful "abstract machine" with steps, configurations, and a
15+
-- semantics would be a sizeable separate development; the *honest*
16+
-- thin slice of that idea is the enumerator-bounded witness:
17+
--
18+
-- a search strategy = an enumerator `enum : ℕ → A`
19+
-- a bound-`n` echo = a witness `enum k ≡ x` with `k < n`
20+
-- together with the usual `f x ≡ y`
21+
--
22+
-- The `ℕ`-bound is the "step budget" of the would-be machine. Every
23+
-- machine of the heavier refinement (e.g. a Turing-bounded extractor,
24+
-- a polynomial-time enumeration) projects onto this thin slice by
25+
-- forgetting everything except the index it queried and the bound it
26+
-- queried under. So this module sits at the *bottom* of the
27+
-- axis-8(4) lattice in the same sense that `EchoDecidable` sits at
28+
-- the bottom of the axis-8(3) lattice (`docs/echo-types/taxonomy.md`
29+
-- §"Open question / lattice"), and a heavier machine model lands on
30+
-- top later without renaming or invalidating these lemmas.
31+
--
32+
-- Design choices, in line with `EchoApprox` / `EchoFiberCount`:
33+
--
34+
-- * `SearchStrategy A := ℕ → A`. A total function. Partiality is
35+
-- not modelled here — that is a separate refinement (axis 8(2)).
36+
-- A total enumerator is exactly the right shape for a
37+
-- bound-respecting machine: at step `k` it produces the element
38+
-- `enum k`; nothing else.
39+
--
40+
-- * `EchoS enum f y n := Σ ℕ λ k → (k < n) × (f (enum k) ≡ y)`.
41+
-- Crucially `_<_` is stdlib's `Data.Nat.Base._<_`, i.e.
42+
-- `suc m ≤ n` — the strict form. This is the form `≤-<-trans` /
43+
-- `<-≤-trans` from `Data.Nat.Properties` operate on without any
44+
-- conversion glue.
45+
--
46+
-- * Composition: we ship `echo-search-postcompose`, the
47+
-- "post-compose with `g`" rule. This is the search analogue of
48+
-- "f x ≡ y ⇒ (g ∘ f) x ≡ g y" — it preserves the bound exactly
49+
-- (the same k, the same enumerator step, the same `< n` proof).
50+
-- This is the genuinely-honest compositional content available
51+
-- without further machinery; a product/sequential composition
52+
-- under two strategies needs more (a `ℕ × ℕ` index, a paired
53+
-- bound, and a choice of pairing function on the index set),
54+
-- which is a separate slice. See "where next" below.
55+
--
56+
-- Where next:
57+
--
58+
-- * Sequential composition `EchoS enum f b n₁ → EchoS enum' g y n₂
59+
-- → EchoS (paired-enum) (g ∘ f) y (n₁ * n₂)` under a pairing
60+
-- enumerator on `ℕ × ℕ`. Honest but needs a bijection
61+
-- `ℕ × ℕ ↔ ℕ`; defer to the slice that wants it.
62+
--
63+
-- * A real abstract-machine refinement: configurations + a step
64+
-- relation, with `EchoS` recovered as `∃ trace . trace.length < n
65+
-- ∧ trace.last ≡ x ∧ f x ≡ y`. The current `EchoS` projects from
66+
-- that by collapsing the trace to its terminal index.
67+
--
68+
-- * A *bounded-search-is-decidable* lemma in the presence of
69+
-- decidable equality on `B`: search up to `n` either yields an
70+
-- `EchoS enum f y n` or proves `¬ EchoS enum f y n`. This is the
71+
-- concrete bridge to `EchoDecidable`, kept as a separate slice
72+
-- because it needs a `_≟_` on `B` and a finite-walk recursion.
73+
74+
module EchoSearch where
75+
76+
open import Function.Base using (_∘_; id)
77+
open import Data.Nat.Base using (ℕ; zero; suc; _≤_; _<_; z≤n; s≤s)
78+
open import Data.Nat.Properties using (≤-<-trans; <-≤-trans)
79+
open import Data.Empty using (⊥; ⊥-elim)
80+
open import Data.Product.Base using (Σ; _,_; _×_; proj₁; proj₂)
81+
open import Relation.Nullary using (¬_)
82+
open import Relation.Binary.PropositionalEquality
83+
using (_≡_; refl; sym; trans; cong)
84+
85+
open import Echo using (Echo)
86+
87+
----------------------------------------------------------------------
88+
-- Search strategies and bound-indexed echoes
89+
----------------------------------------------------------------------
90+
91+
-- A search strategy on `A` is a total enumeration of its elements
92+
-- indexed by ℕ. Total, by design — partiality is a separate axis 8(2)
93+
-- refinement and would obscure the bound semantics here.
94+
SearchStrategy : {a} Set a Set a
95+
SearchStrategy A = A
96+
97+
-- The witness-search echo at bound `n`: a step index `k < n` at
98+
-- which the enumerator produced a preimage of `y` under `f`.
99+
EchoS :
100+
{a b} {A : Set a} {B : Set b}
101+
(enum : SearchStrategy A) (f : A B) (y : B) (n : ℕ) Set b
102+
EchoS enum f y n = Σ ℕ (λ k (k < n) × (f (enum k) ≡ y))
103+
104+
----------------------------------------------------------------------
105+
-- Headlines
106+
----------------------------------------------------------------------
107+
108+
-- Introduction. If at step `k < n` the enumerator returns an element
109+
-- whose image is `y`, we have a bound-`n` search echo.
110+
echo-search-intro :
111+
{a b} {A : Set a} {B : Set b}
112+
(enum : SearchStrategy A) (f : A B) {y : B}
113+
(k : ℕ) (n : ℕ) (k<n : k < n)
114+
f (enum k) ≡ y
115+
EchoS enum f y n
116+
echo-search-intro enum f k n k<n eq = k , k<n , eq
117+
118+
-- Bound monotonicity. A larger budget admits every shorter-budget
119+
-- search; the same step index, lifted along `<-≤-trans`.
120+
echo-search-relax :
121+
{a b} {A : Set a} {B : Set b}
122+
(enum : SearchStrategy A) (f : A B) {y : B} {n m : ℕ}
123+
(n≤m : n ≤ m)
124+
EchoS enum f y n EchoS enum f y m
125+
echo-search-relax enum f n≤m (k , k<n , eq) =
126+
k , <-≤-trans k<n n≤m , eq
127+
128+
-- Forgetful projection. Throw away the step budget and the index
129+
-- bound and keep only the underlying intensional `Echo`. This is the
130+
-- canonical "search refines inhabitation" arrow.
131+
echo-search-forget :
132+
{a b} {A : Set a} {B : Set b}
133+
{enum : SearchStrategy A} {f : A B} {y : B} {n : ℕ}
134+
EchoS enum f y n Echo f y
135+
echo-search-forget (k , _ , eq) = _ , eq
136+
137+
-- Empty-budget vacuity. No witness can live at bound 0, because
138+
-- `k < 0` is uninhabited in stdlib's `Data.Nat._<_`.
139+
echo-search-bound-zero :
140+
{a b} {A : Set a} {B : Set b}
141+
(enum : SearchStrategy A) (f : A B) (y : B)
142+
¬ EchoS enum f y 0
143+
echo-search-bound-zero enum f y (k , () , eq)
144+
145+
-- Post-composition. The honest compositional rule available without
146+
-- a product-strategy / pairing-bijection on the index set: a
147+
-- bound-`n` search witnessing `f` at `y` also witnesses `g ∘ f` at
148+
-- `g y`, at the *same* step index and the *same* bound. The bound
149+
-- is preserved exactly because the enumerator and the queried step
150+
-- have not changed — only what we report as the "answer" has.
151+
echo-search-postcompose :
152+
{a b c} {A : Set a} {B : Set b} {C : Set c}
153+
(enum : SearchStrategy A) (f : A B) (g : B C) {y : B} {n : ℕ}
154+
EchoS enum f y n EchoS enum (g ∘ f) (g y) n
155+
echo-search-postcompose enum f g (k , k<n , eq) =
156+
k , k<n , cong g eq
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{-# OPTIONS --safe --without-K #-}
2+
3+
-- A concrete instance for `EchoSearch` headlines, in the same spirit
4+
-- as `EchoApproxInstance` for `EchoApprox.Approx`.
5+
--
6+
-- `EchoSearch` is parameterised in `A`, `B`, `enum`, `f`, `y`, `n`,
7+
-- so to pin a closed term per headline lemma in `Smoke.agda` we
8+
-- specialise to the trivial-on-⊤ choice:
9+
--
10+
-- A := ⊤
11+
-- B := ⊤
12+
-- enum := λ _ → tt
13+
-- f := λ _ → tt
14+
-- y := tt
15+
-- n := 1
16+
--
17+
-- Every order / equality / bound obligation reduces to a one-step
18+
-- inhabitant (`z<s : 0 < 1`, `refl : tt ≡ tt`, etc.). These pins are
19+
-- proof-of-life that the parameterised lemmas typecheck at *some*
20+
-- concrete instance — exactly the hygiene contract the working
21+
-- rules of `CLAUDE.md` ask for. When a non-trivial enumerator
22+
-- instance lands, the pins can be re-pointed.
23+
24+
module EchoSearchInstance where
25+
26+
open import Data.Unit.Base using (⊤; tt)
27+
open import Data.Nat.Base using (ℕ; suc; zero; _<_; s≤s; z≤n)
28+
open import Data.Nat.Properties using ()
29+
open import Relation.Binary.PropositionalEquality using (refl)
30+
31+
open import Echo using (Echo)
32+
open import EchoSearch using
33+
( SearchStrategy
34+
; EchoS
35+
; echo-search-intro
36+
; echo-search-relax
37+
; echo-search-forget
38+
; echo-search-bound-zero
39+
; echo-search-postcompose
40+
)
41+
42+
----------------------------------------------------------------------
43+
-- The trivial enumerator on `⊤`
44+
----------------------------------------------------------------------
45+
46+
trivialEnum : SearchStrategy ⊤
47+
trivialEnum _ = tt
48+
49+
trivialF :
50+
trivialF _ = tt
51+
52+
----------------------------------------------------------------------
53+
-- Per-lemma proof-of-life pins.
54+
--
55+
-- Closed top-level identifiers (one per headline) so `Smoke.agda`
56+
-- can enumerate them via a `using` clause. Definitions use `=` so
57+
-- the original term's type is inferred — exactly the typeability
58+
-- check the hygiene invariant asks for.
59+
----------------------------------------------------------------------
60+
61+
-- Concrete bound-1 witness at the trivial instance. Uses `z<s` (the
62+
-- smart constructor for `0 < n+1`) and `refl : tt ≡ tt`.
63+
search-intro-⊤ : EchoS trivialEnum trivialF tt 1
64+
search-intro-⊤ = echo-search-intro trivialEnum trivialF 0 1 (s≤s z≤n) refl
65+
66+
-- Bound monotonicity at the trivial instance: 1 ≤ 2 lifts the
67+
-- bound-1 witness to a bound-2 witness.
68+
search-relax-⊤ : EchoS trivialEnum trivialF tt 2
69+
search-relax-⊤ = echo-search-relax trivialEnum trivialF (s≤s z≤n) search-intro-⊤
70+
71+
-- Forgetful projection at the trivial instance. The plain `Echo` for
72+
-- `trivialF` at `tt` is trivially inhabited (every fibre is full).
73+
-- The signature is given explicitly because `echo-search-forget`
74+
-- takes all of its identifying parameters implicit, so type
75+
-- inference at the use-site cannot resolve them from the witness.
76+
search-forget-⊤ : Echo trivialF tt
77+
search-forget-⊤ = echo-search-forget search-intro-⊤
78+
79+
-- Empty-budget vacuity at the trivial instance: bound 0 admits no
80+
-- witness even on the trivial enumerator.
81+
search-bound-zero-⊤ = echo-search-bound-zero trivialEnum trivialF tt
82+
83+
-- Post-composition at the trivial instance with `g := trivialF`.
84+
-- The same step index, the same bound; the equality becomes
85+
-- `cong trivialF refl ≡ refl`.
86+
search-postcompose-⊤ = echo-search-postcompose trivialEnum trivialF trivialF search-intro-⊤

proofs/agda/Smoke.agda

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,36 @@ open import EchoDecidable using
176176
; echo-dec-compose-fin
177177
)
178178

179+
-- Axis 8(4) thin slice (taxonomy.md §"Witness-search abstract
180+
-- machine"): the enumerator-bounded refinement of `Echo`. Lands the
181+
-- search strategy + bound-indexed carrier, introduction, bound
182+
-- monotonicity, forgetful projection to plain `Echo`, empty-budget
183+
-- vacuity, and the honest post-composition rule. Sequential /
184+
-- product-strategy composition needs a `ℕ × ℕ ↔ ℕ` pairing
185+
-- bijection and lands in a separate slice; see the module preamble
186+
-- "where next" section.
187+
open import EchoSearch using
188+
( SearchStrategy
189+
; EchoS
190+
; echo-search-intro
191+
; echo-search-relax
192+
; echo-search-forget
193+
; echo-search-bound-zero
194+
; echo-search-postcompose
195+
)
196+
197+
-- Per-lemma pins for the parameterised EchoSearch via
198+
-- EchoSearchInstance — same hygiene pattern as EchoApproxInstance.
199+
open import EchoSearchInstance using
200+
( trivialEnum
201+
; trivialF
202+
; search-intro-⊤
203+
; search-relax-⊤
204+
; search-forget-⊤
205+
; search-bound-zero-⊤
206+
; search-postcompose-⊤
207+
)
208+
179209
-- Axis 8 second formal artifact (taxonomy.md §8): graded access
180210
-- modality. Order layer (enum, Hasse-enumerated order, transitivity,
181211
-- propositionality) + Σ-shape carrier + `_≤a_`-indexed degrade

0 commit comments

Comments
 (0)