Skip to content

Commit 68d6b1c

Browse files
hyperpolymathclaude
andcommitted
docs(cartridges): deep-annotate 6 lead cartridges (18 per-dir READMEs)
Adds per-directory README.adoc inside abi/, ffi/, and adapter/ for the six lead cartridges on the D→C path: database-mcp, git-mcp, fleet-mcp, hypatia-mcp, reposystem-mcp, verisimdb-mcp. Each README covers the CRG v2.0 deep-annotation checklist for that layer: purpose, files (with one-line role each), invariants (quoting or near-quoting source — mutex discipline, state-machine transitions, bounds checks, conservation proofs, enum hardening), test/proof surface (inline Zig test count, Idris2 .ipkg if present), and read-first line ranges so a reviewer can orient without source archaeology. Honest-by-design: hypatia-mcp, reposystem-mcp, and verisimdb-mcp FFI layers are labelled "(stubs)" in their README titles and carry an explicit "Promotion gate" section stating that C-promotion requires swapping stubs for real backends. This prevents the cartridge-fleet readiness grade from silently overstating what's implemented. database-mcp and fleet-mcp are the substantive ones (live SQLite + three curl-backed query protocols; six-gate quality tracker with mandatory subset enforcement). git-mcp sits between — full state-machine implementation, no external backend yet. This closes one of the two concrete D→C requirements for the cartridge-fleet row in docs/READINESS.md. The other (active home-context dogfooding with no known failures for four weeks) accumulates in docs/practice/DOGFOOD-LOG.adoc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1d12ea4 commit 68d6b1c

18 files changed

Lines changed: 838 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= database-mcp / abi — Idris2 ABI layer
4+
:orientation: deep
5+
6+
== Purpose
7+
8+
Encodes the database **connection state machine** and the **MCP tool catalogue**
9+
for the database-mcp cartridge. The state machine (`Disconnected → Connected →
10+
Querying → Connected → Disconnected`, plus `Querying → Error → Disconnected`)
11+
is the source of truth that the Zig FFI layer mirrors — the ABI proves the
12+
transition graph is well-formed at the type level via the `ValidTransition`
13+
indexed type, and the FFI re-implements the same graph as a runtime predicate.
14+
15+
== Files
16+
17+
[cols="1,4"]
18+
|===
19+
| File | Role
20+
21+
| `database-mcp.ipkg` | Idris2 package descriptor. Type-check entry point (`idris2 --check database-mcp.ipkg`).
22+
| `DatabaseMcp/SafeDatabase.idr` | Whole cartridge in one module. Defines `ConnState`, `ValidTransition` (6 constructors), `canTransition` runtime predicate, `DatabaseBackend` (5 backends + `Custom`), `QuerySafety`, `SafeQuery`, `QueryResult`, `Connection` record, `IsConnected` proof, the 7 `McpTool` constructors, and two C-ABI exports (`db_can_transition`, `db_tool_requires_connection`).
23+
|===
24+
25+
== Invariants
26+
27+
* `%default total` at module top (line 19) — every definition must be total.
28+
* `Disconnected → Querying` is **not** in `ValidTransition`; entering a query
29+
requires passing through `Connected`. The FFI enforces the same by switching
30+
on the current state before every transition.
31+
* `requiresConnection` marks `ToolConnect` and `ToolListDatabases` as
32+
callable without an active connection; every other tool requires one.
33+
* `backendToInt` and `connStateToInt` are the authoritative encoding the
34+
FFI must agree with byte-for-byte.
35+
36+
== Test/proof surface
37+
38+
Type-check only — `idris2 --check DatabaseMcp/SafeDatabase.idr` via the
39+
`.ipkg`. No inline `test` blocks (that is the FFI's job — see `../ffi/`).
40+
Proofs here are structural: `ValidTransition` is a GADT with exactly six
41+
inhabitants.
42+
43+
== Read-first
44+
45+
. `SafeDatabase.idr` lines 22–58 — state machine (`ConnState`, `ValidTransition`, `canTransition`).
46+
. Lines 64–82 — backend enum and its C-ABI encoding.
47+
. Lines 129–161 — `Connection` record, `IsConnected` proof, and the seven `McpTool` constructors.
48+
. Lines 167–196 — the two exported FFI primitives.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= database-mcp / adapter — REST/gRPC/GraphQL bridge
4+
:orientation: deep
5+
6+
== Purpose
7+
8+
Exposes the FFI layer over three protocols so non-Zig callers can drive the
9+
cartridge. The adapter is **stateless** — it does not hold its own connection
10+
or slot state; all state lives behind the FFI mutex in `../ffi/`. The adapter
11+
simply parses an incoming request, routes to a tool name, calls the matching
12+
FFI export, and serialises the response.
13+
14+
== Files
15+
16+
[cols="1,4"]
17+
|===
18+
| File | Role
19+
20+
| `build.zig` | Zig build graph for the adapter binary.
21+
| `database_adapter.zig` | Three-protocol dispatcher. Listens on three ports, maps tool names to FFI calls, returns JSON over HTTP.
22+
| `SIDELINED-database_adapter.v.adoc` | Historical V-lang predecessor. Retained for lineage per the 2026-04-10 V-ban transition rule ("V sources MOVE, don't delete"). Not compiled.
23+
|===
24+
25+
== Invariants
26+
27+
* **Stateless.** No global mutable state beyond protocol listeners. The
28+
adapter never caches slot indices; the caller must carry them.
29+
* **One tool call per HTTP request.** Pipelining or keep-alive batching is
30+
out of scope for this layer.
31+
* **JSON content type.** Responses are always `Content-Type:
32+
application/json`; errors are `{"error": "<code>", "message": "<text>"}`.
33+
* **Port binding.** Each protocol runs on a dedicated port so a single
34+
cartridge can simultaneously serve all three without header-based
35+
multiplexing.
36+
37+
== Test/proof surface
38+
39+
No inline tests in the adapter (FFI tests cover correctness; the adapter is
40+
thin scaffolding). Adapter-level integration is exercised by the
41+
cartridge-matrix tests run from the repo root via `zig build test` in the
42+
main tree.
43+
44+
== Read-first
45+
46+
. `database_adapter.zig` top of file — port constants and protocol enum.
47+
. The `dispatch` function — canonical tool-name → FFI-call mapping.
48+
. The three protocol routers (REST, gRPC-compat, GraphQL) — each is a thin
49+
wrapper around `dispatch`.
50+
. `main` — listener thread spawn.
51+
52+
Skip `SIDELINED-database_adapter.v.adoc` for current work; it documents the
53+
pre-sidelining design only.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= database-mcp / ffi — Zig FFI layer
4+
:orientation: deep
5+
6+
== Purpose
7+
8+
The Zig implementation of the connection state machine from `../abi/`, plus
9+
live SQL execution against SQLite (linked via `@cImport("sqlite3.h")`) and
10+
live VQL / KQL / GQL execution against VeriSimDB / QuandleDB / LithoGlyph via
11+
child `curl` processes. Exposes the four standard cartridge symbols
12+
(`boj_cartridge_init`, `boj_cartridge_deinit`, `boj_cartridge_name`,
13+
`boj_cartridge_version`) and ~20 database-specific `db_*` exports.
14+
15+
== Files
16+
17+
[cols="1,4"]
18+
|===
19+
| File | Role
20+
21+
| `build.zig` | Zig build graph — produces the loadable shared library plus the unit-test binary.
22+
| `database_ffi.zig` | All of it. ~1200 lines. State machine, 16-slot connection pool, SQLite integration, VQL/KQL/GQL curl execution, 24 inline tests.
23+
| `zig-out/` | Build artefacts (gitignored).
24+
|===
25+
26+
== Invariants
27+
28+
* **Mutex discipline.** All reads and writes to the global `connections`
29+
array are protected by `var mutex: std.Thread.Mutex`. Every C-ABI export
30+
opens with `mutex.lock(); defer mutex.unlock();`. Pure helpers
31+
(`isValidTransition`, `appendJsonEscaped`) do not need the lock.
32+
* **Two-phase pattern** for blocking I/O. `db_execute_vql` / `_kql` / `_gql`
33+
release the mutex during the `curl` call and re-acquire it afterwards,
34+
then **re-validate slot state** (`active && state == .querying`) before
35+
writing the result back. See lines 515–587 for the canonical pattern.
36+
* **Bounds checks before dereference.** All `*_ptr` / `*_len` pairs reject
37+
zero-length and oversize inputs (error codes `-6`, `-8`) before any
38+
`@memcpy`. Pointer safety is documented in-line with `SAFETY:` comments.
39+
* **Enum hardening.** All public exports use `std.meta.intToEnum` rather
40+
than `@enumFromInt` on `c_int` arguments, so an invalid enum returns `-1`
41+
instead of panicking.
42+
* **State-machine mirror.** `isValidTransition` (lines 81–88) is the exact
43+
graph from `SafeDatabase.idr`. Changes there must land in both files.
44+
* **Slot capacity** is `MAX_CONNECTIONS = 16`. Exhaustion returns `-1`.
45+
* **Error codes**: `-1` invalid slot / exhausted, `-2` invalid transition,
46+
`-3` no sqlite handle (wrong backend), `-4` sqlite3_exec failed,
47+
`-5` output buffer too small, `-6` URL/path empty or too long, `-7` curl
48+
failed, `-8` zero-length input/output.
49+
50+
== Test/proof surface
51+
52+
24 inline `test "..."` blocks in `database_ffi.zig` (lines 878–1189). Coverage:
53+
54+
* State machine (lines 878–931): connect, disconnect, double-close, query
55+
lifecycle, error recovery, transition validation.
56+
* SQLite live execution (lines 933–1045): in-memory DB open, execute
57+
`CREATE`/`INSERT`/`SELECT`, multiple rows, invalid SQL → error state,
58+
non-sqlite slot rejection.
59+
* VeriSimDB / QuandleDB / LithoGlyph connection lifecycle (lines 1047–1169):
60+
URL storage, empty/oversize rejection, query lifecycle via manual state
61+
transitions (no live server in test).
62+
* Wrong-backend rejection (lines 1171–1188).
63+
64+
Run with `cd ffi && zig build test`.
65+
66+
== Read-first
67+
68+
. Lines 14–57 — types and the `ConnectionSlot` struct; note `url_buf[512]` for the HTTP backends.
69+
. Lines 59–88 — `connections` array, `mutex` declaration, `isValidTransition`.
70+
. Lines 95–113 — the simplest export (`db_connect`); read before the sqlite/verisimdb variants.
71+
. Lines 324–401 — `db_execute_sql` — full state-machine-guarded sqlite exec with JSON serialisation.
72+
. Lines 515–641 — `db_execute_vql` + `runCurlPost` — the two-phase lock pattern for blocking I/O.
73+
. Lines 842–872 — the standard cartridge-interface exports required by the BoJ loader.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= fleet-mcp / abi — Idris2 ABI layer
4+
:orientation: deep
5+
6+
== Purpose
7+
8+
Encodes the **six quality gates** (Rhodibot, Echidnabot, Sustainabot,
9+
Panicbot, Glambot, Seambot) and a `FleetCertified` indexed type that is
10+
inhabited only when every gate has passed. `deriveStatus` folds a list of
11+
gate results into one of four `RepoStatus` values so the runtime can publish
12+
a repo's health in one byte.
13+
14+
== Files
15+
16+
[cols="1,4"]
17+
|===
18+
| File | Role
19+
20+
| `fleet-mcp.ipkg` | Idris2 package descriptor. `modules = FleetMcp.SafeFleet`, `depends = base, contrib`.
21+
| `FleetMcp/SafeFleet.idr` | `BotGate` enum, `RepoStatus` enum, the indexed type `FleetCertified` with single constructor `FullyVerified`, record `GateScanResult`, helper predicates `hasMandatoryGates` and `hasAllGates`, and `deriveStatus`.
22+
|===
23+
24+
== Invariants
25+
26+
* **Mandatory subset** = [`Rhodibot`, `Echidnabot`, `Panicbot`] (line 115).
27+
A release requires *all three* to pass.
28+
* **Status derivation** (lines 131–135):
29+
* all six pass → `Healthy`
30+
* mandatory three pass but not all six → `Degraded`
31+
* any gate passed → `Scanning`
32+
* none passed → `Unscanned`
33+
* **Single constructor** for `FleetCertified`: `FullyVerified` exists only
34+
when all six gates have produced a pass result. You cannot fabricate a
35+
`FleetCertified` by pattern-matching anything else.
36+
* Gate-integer encoding excludes `-1` via `mapMaybe` (lines 168, 175).
37+
38+
== Test/proof surface
39+
40+
Type-check only via `.ipkg`. No inline `test` blocks.
41+
42+
== Read-first
43+
44+
. Lines 25–60 — `BotGate` enum and its integer encoding.
45+
. Lines 100–127 — `FleetCertified` type, `hasMandatoryGates`, `hasAllGates`.
46+
. Lines 143–158 — `GateScanResult`, `passedGates`, and `deriveStatus`.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= fleet-mcp / adapter — REST/gRPC/GraphQL bridge
4+
:orientation: deep
5+
6+
== Purpose
7+
8+
Exposes the fleet-mcp FFI over three protocols: REST on **9235**, gRPC-compat
9+
on **9236**, GraphQL on **9237**. Stateless; all gate state lives behind the
10+
FFI mutex in `../ffi/`.
11+
12+
== Files
13+
14+
[cols="1,4"]
15+
|===
16+
| File | Role
17+
18+
| `build.zig` | Zig build graph for the adapter binary.
19+
| `fleet_adapter.zig` | `dispatch` routes tool names to FFI calls. Tools: `fleet_record_gate`, `fleet_bot_status`, `fleet_gate_score`, `fleet_has_mandatory`, `fleet_fleet_status`.
20+
| `SIDELINED-fleet_adapter.v.adoc` | Archived V-lang predecessor. Not built.
21+
|===
22+
23+
== Invariants
24+
25+
* **Stateless**; adapter holds no gate state of its own.
26+
* One gate-query per HTTP request; no pipelining.
27+
28+
== Test/proof surface
29+
30+
No inline tests; correctness lives in the FFI.
31+
32+
== Read-first
33+
34+
. Lines 27–44 — `dispatch` routing.
35+
. Lines 47–78 — protocol routers (REST/gRPC/GraphQL).
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= fleet-mcp / ffi — Zig FFI layer
4+
:orientation: deep
5+
6+
== Purpose
7+
8+
Runtime gate tracker. Mutex-guarded arrays hold per-gate pass/fail booleans
9+
and per-gate scores (0–100). Exposes the ABI's three predicates
10+
(`fleet_has_mandatory`, `fleet_has_all`, derived `fleet_status`) over the
11+
C-ABI so the BoJ server can ask "is this repo release-ready?" with one call.
12+
13+
== Files
14+
15+
[cols="1,4"]
16+
|===
17+
| File | Role
18+
19+
| `build.zig` | Zig build graph (shared library + test binary).
20+
| `fleet_ffi.zig` | Gate-state arrays, mutex, the C-ABI exports `fleet_record_gate`, `fleet_has_mandatory`, `fleet_has_all`, `fleet_status`, `fleet_gate_score`, and the four `boj_cartridge_*` symbols.
21+
| `zig-out/` | Build artefacts (gitignored).
22+
|===
23+
24+
== Invariants
25+
26+
* **Mutex discipline** on every gate read/write (lines 45, 53, 64, 72, 82, 102).
27+
* **Mandatory** = indices 0, 1, 3 (Rhodibot, Echidnabot, Panicbot). The
28+
check lives at line 68 — changing mandatory composition means changing
29+
those three index constants.
30+
* **Deadlock avoidance.** `fleet_status` inlines the mandatory/all checks
31+
(lines 85–98) rather than calling `fleet_has_mandatory` / `fleet_has_all`
32+
which would re-acquire the mutex.
33+
* Gate indices arriving from callers use the 1–6 range from the ABI;
34+
internal arrays are 0–5.
35+
36+
== Test/proof surface
37+
38+
4 inline `test "..."` blocks (lines 143–185):
39+
40+
* initial state — no gates recorded, status is `Unscanned`.
41+
* mandatory gates insufficient until Panicbot is added (tests the 0/1/3
42+
triple specifically).
43+
* all six gates → `Healthy`.
44+
* failed gate prevents `Healthy` even when mandatory still satisfied.
45+
46+
Run with `cd ffi && zig build test`.
47+
48+
== Read-first
49+
50+
. Lines 37–49 — gate-result arrays, scores, and the mutex.
51+
. Lines 64–79 — `fleet_has_mandatory` and `fleet_has_all`.
52+
. Lines 82–99 — `fleet_status` with its inline (non-re-locking) logic.
53+
. Lines 143–185 — tests.

cartridges/git-mcp/abi/README.adoc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= git-mcp / abi — Idris2 ABI layer
4+
:orientation: deep
5+
6+
== Purpose
7+
8+
Encodes the forge-session state machine
9+
(`Unauthenticated → Authenticated → RepoSelected → Operating → RepoSelected`)
10+
as an Idris2 indexed type plus a parallel runtime predicate. The ABI is the
11+
source-of-truth spec; the FFI implements the same graph in Zig. Tools are
12+
tagged with their state precondition so the FFI can refuse an operation that
13+
would require a `RepoSelected` session when no repo has been selected.
14+
15+
== Files
16+
17+
[cols="1,4"]
18+
|===
19+
| File | Role
20+
21+
| `git-mcp.ipkg` | Idris2 package descriptor. `modules = GitMcp.SafeGit`, `depends = base, contrib`. Type-check with `idris2 --check git-mcp.ipkg`.
22+
| `GitMcp/SafeGit.idr` | 5-state `GitState`, `ValidTransition` type proof, `ForgeSession` record with state tag, `HasRepo` proof, the `McpTool` enum, `requiresRepo` predicate, and C-ABI exports `git_can_transition`, `git_tool_requires_repo`.
23+
|===
24+
25+
== Invariants
26+
27+
* Linear session pipeline: the state machine cannot jump — e.g. a tool that
28+
needs `RepoSelected` cannot be called from `Authenticated` without going
29+
through `git_select_repo`.
30+
* Read-only ops (`ToolStatus`, `ToolLog`, `ToolDiff`) do **not** require a
31+
selected repo; mutating ops (`ToolCreatePR`, `ToolPush`, `ToolCreateBranch`)
32+
do. See `requiresRepo` predicate.
33+
* Module commentary at line 26 pins the intended progression:
34+
`Unauthenticated -> Authenticated -> RepoSelected -> Operating -> RepoSelected`.
35+
36+
== Test/proof surface
37+
38+
Type-check only (`idris2 --check`). No inline `test` blocks.
39+
40+
== Read-first
41+
42+
. Lines 22–63 — `GitState`, `ValidTransition` constructors, and `canTransition`.
43+
. Lines 89–104 — `ForgeSession` record and `HasRepo` proof.
44+
. Lines 113–143 — `McpTool` constructors and `requiresRepo` predicate.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
= git-mcp / adapter — REST/gRPC/GraphQL bridge
4+
:orientation: deep
5+
6+
== Purpose
7+
8+
Exposes the git-mcp FFI over three protocols: REST on **9250**, gRPC-compat on
9+
**9251**, GraphQL on **9252**. Stateless — the session state lives behind the
10+
FFI mutex in `../ffi/`.
11+
12+
== Files
13+
14+
[cols="1,4"]
15+
|===
16+
| File | Role
17+
18+
| `build.zig` | Zig build graph for the adapter binary.
19+
| `git_adapter.zig` | Three listener threads spawned from `main`. `dispatchRest`, `dispatchGrpc`, and `dispatchGraphql` map a path or body shape to a tool name, then call the shared `dispatch` stub that forwards to the FFI. Tool set: `git_authenticate`, `git_select_repo`, `git_status`, `git_log`, `git_diff`, `git_create_branch`, `git_push`.
20+
| `SIDELINED-git_adapter.v.adoc` | Archived V-lang predecessor. Kept for lineage; not built.
21+
|===
22+
23+
== Invariants
24+
25+
* **Stateless** — the adapter carries no session data.
26+
* **Port binding** — three sibling ports, no header-multiplexing.
27+
* Responses always `application/json`; 200 success, 404 unknown tool.
28+
29+
== Test/proof surface
30+
31+
No inline tests. Adapter is integration scaffolding.
32+
33+
== Read-first
34+
35+
. Lines 27–51 — `dispatch` mapping (tool name → FFI call).
36+
. Lines 53–70 — REST/gRPC routing.
37+
. Lines 125–144 — listener loop and `main`.

0 commit comments

Comments
 (0)