|
| 1 | += ArghDA MVP Specification |
| 2 | +:toc: macro |
| 3 | +:toclevels: 2 |
| 4 | + |
| 5 | +toc::[] |
| 6 | + |
| 7 | +== Purpose |
| 8 | + |
| 9 | +ArghDA is a lightweight proof-workspace manager. Initial target: |
| 10 | +Agda. It exists to close a specific user-experience gap that |
| 11 | +`agda-mode` and `agda-language-server` do not: making the |
| 12 | +_lifecycle_ of a proof (inbox → working → proven | rejected) a |
| 13 | +first-class, visible object, with a thin linter that catches the |
| 14 | +silent-failure class _before_ the typechecker. |
| 15 | + |
| 16 | +Scope discipline: small tool, narrow job. Not a proof assistant, |
| 17 | +not a replacement for `agda-mode`, not a tactic framework. |
| 18 | + |
| 19 | +== Motivation |
| 20 | + |
| 21 | +From echo-types sessions (see `docs/buchholz-plan.adoc`), the |
| 22 | +operator repeatedly hit a specific failure mode: |
| 23 | + |
| 24 | +* a proof file exists, looks like it typechecked, but is missing |
| 25 | + `--safe` / `--without-K`, or is not imported from `All.agda`, |
| 26 | + or is not pinned in `Smoke.agda` |
| 27 | +* the "did it really work?" question is answered only by running |
| 28 | + the whole suite, which is slow and emotionally punishing |
| 29 | +* there is no visible pipeline: no inbox, no "in-progress", no |
| 30 | + "proven" tag; just a flat directory of `.agda` files |
| 31 | + |
| 32 | +The existing ecosystem (see ArghDA scout report: `coq-lsp`, |
| 33 | +`VSCoq`, `Alectryon`, `LeanInk`, `Mathlib stats`, `Isabelle/jEdit |
| 34 | +PIDE`, `agda-language-server`, `agda-unused`) gives excellent |
| 35 | +per-file UX and some library-level dependency views, but nobody |
| 36 | +treats proofs as _items flowing through pipeline stages_. That is |
| 37 | +the novel contribution. |
| 38 | + |
| 39 | +== Split |
| 40 | + |
| 41 | +Two layers, developed and published as two packages: |
| 42 | + |
| 43 | +`arghda-core`:: Language-agnostic engine. Rust. No UI. Exposes a |
| 44 | +CLI, a library API, and a JSON event stream. Runs in CI, standalone, |
| 45 | +or embedded. |
| 46 | + |
| 47 | +`arghda-panll`:: PanLL presentation layer. Gossamer webview + |
| 48 | +ReScript panel consuming `arghda-core`'s event stream. Lives in the |
| 49 | +PanLL surface, probably adjacent to Pane-W. |
| 50 | + |
| 51 | +Rationale for the split: |
| 52 | + |
| 53 | +* Proof triage is useful outside PanLL (students, CI, large collabs). |
| 54 | +* Testing `arghda-core` doesn't require booting PanLL. |
| 55 | +* PanLL gains a polished dependency rather than an internal subsystem. |
| 56 | +* Matches PanLL's existing pattern with `panll_beam`. |
| 57 | + |
| 58 | +== State machine (core) |
| 59 | + |
| 60 | +Four states, implemented as four directories under a workspace root: |
| 61 | + |
| 62 | +[source] |
| 63 | +---- |
| 64 | +<workspace>/ |
| 65 | + inbox/ -- new or returning proof files |
| 66 | + working/ -- operator has picked this up |
| 67 | + proven/ -- typecheck + lint clean |
| 68 | + rejected/ -- typecheck failed, or lint-block, or manual reject |
| 69 | +---- |
| 70 | + |
| 71 | +Transitions are _file moves_. This keeps the ground truth on disk, |
| 72 | +makes recovery obvious (`mv` it back), and survives ArghDA crashing. |
| 73 | + |
| 74 | +Transition rules: |
| 75 | + |
| 76 | +[cols="1,2,3"] |
| 77 | +|=== |
| 78 | +| From | To | Trigger |
| 79 | + |
| 80 | +| inbox | working | operator claim (explicit) |
| 81 | +| working | proven | typecheck clean AND lint clean |
| 82 | +| working | rejected | typecheck failed OR lint-hard-block |
| 83 | +| rejected | inbox | manual re-queue |
| 84 | +| proven | inbox | upstream change invalidates (see DAG below) |
| 85 | +|=== |
| 86 | + |
| 87 | +Every transition emits a JSON event to a rolling log under |
| 88 | +`<workspace>/.arghda/events.jsonl`. |
| 89 | + |
| 90 | +== Linter rules (Agda, v0) |
| 91 | + |
| 92 | +The linter targets the _silent-failure_ class — cases where Agda |
| 93 | +appears to succeed but the file is not actually in the verified |
| 94 | +suite. Rules, each emitting a structured diagnostic: |
| 95 | + |
| 96 | +* `missing-safe-pragma` — file lacks `{-# OPTIONS --safe --without-K #-}` |
| 97 | +* `missing-without-k` — `--safe` without `--without-K` (or vice versa) |
| 98 | +* `orphan-module` — module not imported (transitively) from `All.agda` |
| 99 | +* `unpinned-headline` — module exports a theorem matching a |
| 100 | + headline-naming pattern (operator-configurable regex) but the name |
| 101 | + is not pinned in `Smoke.agda` via a `using` clause |
| 102 | +* `unjustified-postulate` — `postulate` block without an adjacent |
| 103 | + comment line starting with `-- JUSTIFY:` |
| 104 | +* `unused-import` — shell out to `agda-unused` and re-emit findings |
| 105 | +* `tab-mix` — file mixes tabs and spaces at line start |
| 106 | + |
| 107 | +Severity: `hard-block` (forbids `working → proven`) vs `warn` |
| 108 | +(surfaced but non-blocking). Default: `missing-safe-pragma`, |
| 109 | +`orphan-module`, `unjustified-postulate` are hard-block; the rest |
| 110 | +are warn. |
| 111 | + |
| 112 | +Non-goals for v0: semantic linting, redundancy detection, |
| 113 | +style-guide enforcement beyond whitespace. |
| 114 | + |
| 115 | +== DAG JSON schema (v0) |
| 116 | + |
| 117 | +Emitted by `arghda-core dag` as a single JSON document. Consumable by |
| 118 | +the PanLL panel, a static HTML viewer, or third-party tools. |
| 119 | + |
| 120 | +[source,json] |
| 121 | +---- |
| 122 | +{ |
| 123 | + "version": "0.1", |
| 124 | + "workspace": "/path/to/workspace", |
| 125 | + "generated_at": "ISO-8601", |
| 126 | + "nodes": [ |
| 127 | + { |
| 128 | + "id": "Ordinal.Closure", |
| 129 | + "file": "proofs/agda/Ordinal/Closure.agda", |
| 130 | + "status": "proven", |
| 131 | + "lint": { "hard_block": [], "warn": [] }, |
| 132 | + "headlines": ["C-monotone"] |
| 133 | + } |
| 134 | + ], |
| 135 | + "edges": [ |
| 136 | + { "from": "Ordinal.Closure", "to": "Ordinal.Base", "kind": "imports" } |
| 137 | + ], |
| 138 | + "blocked": [ |
| 139 | + { |
| 140 | + "node": "Ordinal.Buchholz.Psi", |
| 141 | + "blocked_by": ["Ordinal.Buchholz.Closure"], |
| 142 | + "reason": "prereq not proven" |
| 143 | + } |
| 144 | + ] |
| 145 | +} |
| 146 | +---- |
| 147 | + |
| 148 | +Renderer is a separate concern; the panel consumes this verbatim. |
| 149 | + |
| 150 | +== Integration points |
| 151 | + |
| 152 | +* **LSP** (coq-lsp pattern) — per-file editor feedback. |
| 153 | +* **PIDE async model** (Isabelle) — asynchronous document state |
| 154 | + for the "checking now" sub-state within `working`. |
| 155 | +* **`agda --only-scope-checking`** — cheap pre-pass for quick |
| 156 | + syntactic / import validation before full typecheck. |
| 157 | +* **`agda-unused`** — shell out; re-emit diagnostics in the |
| 158 | + ArghDA diagnostic format. |
| 159 | +* **Alectryon-style rendering** (future) — static HTML render of |
| 160 | + proof states as a by-product of `proven` transition. |
| 161 | + |
| 162 | +== Non-goals (MVP) |
| 163 | + |
| 164 | +* Tactic language. |
| 165 | +* Writing proofs for the operator. |
| 166 | +* IDE replacement. |
| 167 | +* Multi-prover support (Coq/Lean/Isabelle postponed to v0.2). |
| 168 | +* Distributed execution. A single workspace, a single machine. |
| 169 | + |
| 170 | +== First sprint (v0.1) |
| 171 | + |
| 172 | +Ordered by unblock-value: |
| 173 | + |
| 174 | +. `arghda-core` Rust crate skeleton; `Workspace::open(path)` API. |
| 175 | +. Filesystem watcher over `inbox/` and `working/` using `notify`. |
| 176 | +. Two linter rules: `missing-safe-pragma`, `orphan-module`. |
| 177 | +. `arghda-core check <file>` command: runs agda, captures exit code, |
| 178 | + emits a `LintReport` JSON. |
| 179 | +. `arghda-core promote <file>` / `arghda-core reject <file>`: |
| 180 | + the state-machine CLI. |
| 181 | +. `arghda-core dag` emitting the schema above for the current |
| 182 | + workspace. |
| 183 | +. Minimal end-to-end smoke test over `echo-types` itself as the |
| 184 | + first real workspace. |
| 185 | + |
| 186 | +PanLL panel (`arghda-panll`) is v0.2 and deliberately not in this |
| 187 | +sprint. The CLI + JSON is the interface contract; the panel is a |
| 188 | +consumer. |
| 189 | + |
| 190 | +== Open questions |
| 191 | + |
| 192 | +* Language choice for `arghda-core`: Rust (matches PanLL stack) vs |
| 193 | + Zig (matches Gossamer / Burble). Default: Rust, pending operator |
| 194 | + pushback. |
| 195 | +* Should the `proven` state record a content hash so upstream |
| 196 | + changes can invalidate it automatically? Default: yes, SHA-256 |
| 197 | + of the file plus imports. |
| 198 | +* Naming convention for "headlines" — regex default |
| 199 | + `^[a-z][A-Za-z0-9-]*$` with export-only filter, operator-overridable |
| 200 | + per-workspace in `.arghda/config.toml`. |
| 201 | + |
| 202 | +== Acceptance criteria for v0.1 |
| 203 | + |
| 204 | +* End-to-end demo: take `echo-types` at its current commit, place |
| 205 | + all `*.agda` files into `inbox/`, run `arghda-core` against it, |
| 206 | + observe that every file moves to `proven/` with no hard-blocks, |
| 207 | + and that the emitted DAG matches the actual import graph. |
| 208 | +* Introduce one synthetic bug (delete `--safe` from a file): observe |
| 209 | + that file lands in `rejected/` with the correct diagnostic. |
| 210 | +* Introduce one synthetic orphan (new `.agda` not imported from |
| 211 | + `All.agda`): observe `orphan-module` hard-block. |
| 212 | + |
| 213 | +== References |
| 214 | + |
| 215 | +* ArghDA scout report (this session): existing proof-workspace |
| 216 | + landscape across Coq/Rocq, Lean 4, Isabelle, Agda. |
| 217 | +* `docs/buchholz-plan.adoc` — the motivating proof pipeline that |
| 218 | + surfaced the silent-failure gap. |
0 commit comments