Skip to content

Research: what AXIOM should do next (post-2.0.0 / 2.1.0)

Date 2026-09-19. Inputs: (a) internal gap audit of the repo’s own docs, code and the three consumer integrations; (b) external landscape fetched from primary sources (MCP spec + SDK changelogs, harness hook docs, npm/pnpm posts, OWASP ACS, A2A, in-toto). Facts marked UNVERIFIED were not re-fetched. Nothing here is implemented yet; decisions go to PLAN.md §1.

AXIOM 2.x is architecturally sound — no competitor found does set-level digests, confirm-by-digest two-phase apply, a journal with scoped rollback and per-edit-set DSSE — but three things are now wrong or missing:

  1. The wire it speaks is dead. MCP spec 2026-07-28 removed sessions, initialize, SSE resumability and deprecated Roots/Sampling/Logging. TS SDK 1.30.0 (2026-07-27) is the last v1; v2.0.0 is a package split (@modelcontextprotocol/{core,server,client,node,hono}) with registerTool, zod ^4.2 and an opt-in 2026 wire. Conformance no longer scores the old spec. The design promised an mcp/src/adapter.ts seam for exactly this swap; it does not exist.
  2. Agents emit diffs; AXIOM only accepts whole files. Claude/Copilot Edit, Codex V4A (raw text in the hook payload), Aider/Roo SEARCH/REPLACE. Every MCP call carries full content and hits the 256 KiB / 4 MiB caps on real files. The red-team called this out and it was deferred.
  3. The gate is bypassable by design. Unknown tool → allow, internal error → allow. OWASP Agent Control Standard v0.1.0 (2026-09-01) lists default fail-open as its own issue #32; Copilot CLI already treats a hook crash as deny, so fail-closed costs nothing there.

Plus a release-hygiene problem discovered today: v2.1.0 was tagged and release.yml published only the two new packages; @codai/axiom-axm-lsp@2.1.0 on npm is uninstallable.

A. Internal gaps (from the repo’s own evidence)

Section titled “A. Internal gaps (from the repo’s own evidence)”
# Item Source
A1 Stryker ≥85 % blocked upstream (vitest-runner not Vitest-5 aware); measured 12.9 % with false survivors PLAN.md S-110, mutation.yml
A2 2.0.0 published manually → no provenance; trusted publishing = per-package UI step, still not done PLAN.md §8
A4 VS Code extension not on Marketplace PLAN.md S-205
A5 Conformance: 22/32 scenarios fail-by-design (prompts, logging, subscribe, sampling, elicitation, progress) PLAN.md S-206
A6 No adapter.ts; SDK 1.30 targets pre-2026-07-28 spec v2-architecture.md L386
A7 apply non-guarantees: backups pruned after 3 manifests → rollback impossible; no dir fsync / mode bits on Windows; ref never fetched by apply apply.md L63-78, L143
A9 Gate: unknown path key → allowed; Edit scans replacement only; shell tools out of scope; sub-dir cwd misses .git/** hooks.md L193-204
A10 Gate fails open on internal error hooks.md L43-50
A11 D-11 “<120 ms” met only in-process; e2e p95 191–342 ms hooks.md L177
A12 Signing: root identity not bound (cross-root replay); state.json unsigned; no rotation ceremony signing.md L189-196
A13 mode: pr never pushes/opens a PR apply.md L71
A15 repo.requireCompanion satisfied by existing files → singleton companions only fire on fresh clones integration/metu.md L34-38
A16 Design doc still says LSP = Langium (D-14 chose hand-written) v2-architecture.md L34
# Critique State
B1 patch artifact source (unified diff) — “10× smaller, what agents already produce” not done; sources = inline/cas/ref/template
B2 Chunked plan build for >1 MB payloads not done; hard 4 MiB cap
B3 CI axiom verify that fails a PR whose tree ≠ committed manifest — “the 5-minute on-ramp” not done; verify is bundle-structural only
B4 Pre-image / snapshot digest in the manifest, re-verified at apply partial: pre-image lives only inside one apply() call; a stored CheckReport is unbound from the tree it judged
B7 DSSE with a local key proves nothing; sign in CI with a separate key shipped with the limitation documented; nothing enforces separation
B8 Guard runner output is evidence, not proof — record raw stdout+exit findings merged; no evidence record
  • brivio: guard.external caps at 60 s; full suite ~75 guards, check-untracked-imports alone 64 s → only 7 guards run through AXIOM. Pre-existing red guard (check-vacuous-assertions, 4 debts) blocks every apply. npx in the hook = p50 7.8 s > 5 s timeout.
  • metu: content.noSecrets.card fires on the zero-UUID 00000000-0000-0000-0000-000000000000 (Luhn-valid) — first real profile run was a false positive that blocked apply. (app) route groups must be escaped \(app\) for picomatch. Singleton companions (A15).
  • codai: gate is opt-in (AXIOM_APPLY=1), never compared against the fs path over an eval arm; codai has no external-tool catalogue type, so AXIOM’s codai-tools.json must be spliced at runtime; codai types AXIOM results with local interfaces (field renames break it).
  • Stale “reserved / v2.1 / compile rejects” text in 7 shipped paths: schema/src/plan.ts:41, axm-lsp/src/vocabulary.ts:82 (LSP hover lies), docs/syntax_spec.md:47, docs/plan-format.md:243 (pr “reserved”), canon/src/pae.ts:8, plan/src/verify.ts:22 (signed: false literal type), docs/checks.md:6.
  • Drifted re-apply of createERR_EXISTS, contradicting design (“proceeds, note facts.drifted”).
  • ERR_UNSUPPORTED_OP overloaded for 3 meanings; ERR_JOURNAL_CORRUPT reused for trust-state corruption.
  • 7 error codes with no behavioural test: ERR_DIGEST_FORMAT, ERR_SIZE_MISMATCH, ERR_JOURNAL_CORRUPT (crash recovery of a corrupt journal never executed in CI), ERR_GIT_NOT_FOUND, ERR_GIT_FAILED; 2 never raised: ERR_SIGNATURE_MISSING, ERR_ROLLBACK.

B. External landscape (verified 2026-09-19 unless marked)

Section titled “B. External landscape (verified 2026-09-19 unless marked)”
  • Spec 2026-07-28 final: sessions + Mcp-Session-Id removed; initialize removed (per-request _meta.protocolVersion/clientCapabilities); server/discover MUST; tasks moved to extension io.modelcontextprotocol/tasks (tasks/get polling, tasks/update); Mcp-Method/Mcp-Name headers required on HTTP POST; ttlMs+cacheScope required on tools/list; Roots/Sampling/Logging/DCR deprecated; SSE resumability removed. Tool annotations unchanged (4 hints).
  • TS SDK 1.30.0 (2026-07-27) = last v1. v2.0.0 (2026-07-28): @modelcontextprotocol/{core,client,server,node,hono}, zod ^4.2 required, Node ≥20, .tool()registerTool, stdio at @modelcontextprotocol/server/stdio, experimental tasks API deleted, 2026 wire opt-in via createMcpHandler/serveStdio. Codemod: npx @modelcontextprotocol/codemod@latest v1-to-v2.
  • @modelcontextprotocol/conformance 0.1.16 supports --requirements 2026-07-28; tasks extension not scored. Registry still preview (v1.8.1).
  • Implication: migrate to SDK v2 behind the promised adapter.ts; AXIOM already avoids sessions and Roots (it has its own --root allowlist), so the removal hurts little. tasks extension is the natural home for guard.external > 60 s and for chunked plan builds.

Harness hooks (all fail OPEN on timeout: Claude 600 s, Copilot 30 s, VS Code 30 s, Gemini 60 s, Codex 600 s)

Section titled “Harness hooks (all fail OPEN on timeout: Claude 600 s, Copilot 30 s, VS Code 30 s, Gemini 60 s, Codex 600 s)”
  • Claude Code: hookSpecificOutput.permissionDecision allow|deny|ask|defer; top-level decision/reason deprecated; exit 2 = block.
  • Copilot CLI: camelCase events → {toolName,toolArgs}; PascalCase → snake_case {tool_name,tool_input} with a Claude tool-name map (create→Write, edit|str_replace_editor|apply_patch→Edit); output flat {permissionDecision,permissionDecisionReason,modifiedArgs}; crash/exit 2 = fail-closed deny; cloud agent maps ask→deny. Reads .github/hooks/*.json, ~/.copilot/hooks, .claude/settings*.json.
  • VS Code: tool_input camelCase (filePath); matchers ignored (fires for every tool).
  • Codex: hooks trusted by hash before running; apply_patchtool_name "apply_patch", tool_input.command = raw V4A text; ask unsupported (hook marked failed, call proceeds); mcp_tool handler type.
  • Cursor: no pre-write hook with content (afterFileEdit only). Gemini: non-JSON stdout → allow. Windsurf: tool_info.edits[], exit 2 only. Cline/Amp/OpenCode: in-process plugins only. No cross-vendor standard. ACP gates via session/request_permission + fs/write_text_file.
  • Implication: the gate must parse V4A to cover Codex, emit hookSpecificOutput (not the deprecated top-level shape) for Claude, and can safely default to fail-closed because the one harness that runs it on every tool (Copilot) already denies on crash.
  • gh-aw v0.88.7 “Safe Outputs” (buffer → scan → scoped write jobs); Codex execpolicy Starlark prefix_rule; Devin --sandbox fail-closed; OWASP Agent Control Standard v0.1.0 (2026-09-01) — Guardian allow/deny/modify/ask/defer, OPA/Cedar backends, default fail-open tracked as issue #32; Microsoft Agent Governance Toolkit (OPA/Cedar, Merkle audit); Amp server-side commit signing; gitsign 0.17.1. SLSA has no agent track.
  • Implication: none do content-addressed edit sets or 2PC; AXIOM’s differentiator holds. Map the gate’s verdict vocabulary onto OWASP ACS (allow/deny/modify/ask/defer) to be citable, and consider a Cedar/OPA fact provider rather than a fourth predicate language.
  • V4A: grammar in codex-rs/apply-patch/src/parser.rs; hosted apply_patch emits operation.{create_file|update_file|delete_file,path,diff} — no move op in the hosted schema. Matching ladder: exact → trim_endtrim → Unicode-punctuation normalisation; *** End of File marker; PreserveLineEndings mode.
  • Aider: default is SEARCH/REPLACE (diff edit format) for GPT-5.x / Claude 4.x; udiff only for gpt-4-turbo. Roo apply_patch = V4A headers + real unified hunks.
  • Gemini replace ladder ends in LLM self-correction; Claude Edit = exact unique match; ACP diff = full oldText/newText; jsdiff applyPatch fuzzFactor defaults 0.
  • Implication: a patch source needs three small parsers (unified, V4A, S/R) and one deterministic applier — exact match only (fuzz 0), against a pre-image digest the Plan declares. Fuzzy matching would break content-addressing.
  • npm trusted-publisher configs created after 2026-09-03 default stage-only; publishing needs --allow-publish (npm trust github --file <wf> --allow-publish). Malware scan delays availability 5–15 min. Bypass-2FA granular tokens for direct publish end ~Jan 2027. pull_request_target default-off enforced 2026-11-02.
  • Mini Shai-Hulud (TanStack, 2026-05-11) scraped the OIDC token from runner memory → valid provenance on malware; keyv worm (2026-08-04) forged Sigstore provenance. Provenance ≠ integrity.
  • pnpm 12.0 stable 2026-08-26 (Rust); minimumReleaseAge default 1440 min since v11; onlyBuiltDependenciesallowBuilds. Node 24 Active LTS, 22 Maintenance (EOL 2027-04-30), Node 26 LTS 2026-10-28.
  • Implication: enable trusted publishing with --allow-publish on all 9 packages (owner UI step); keep DSSE manifests as the integrity layer that provenance is not; bump engines to >=22.14 stays valid through April 2027, plan Node 24 floor for 3.0.
  • A2A 1.0 mandates RFC 8785 JCS for Agent Card signing → AXIOM’s choice is ecosystem-aligned. A2A/ACP artifacts carry no digests.
  • in-toto vetted predicates include SLSA VSA and Simple Verification Result; no AI/agent predicate exists. SLSA Source VSA subject: gitCommit MUST, gitTree/dirHash MAY. OmniBOR v0.2 mandates CRLF→LF (breaks byte-exact pre-image) — do not adopt.
  • Implication: publish AXIOM’s CheckReport/ApplyResult as an in-toto predicate (https://axiom.dev/attestation/apply/v1) with the manifest digest as subject; that is the artifact a CI verify action and a downstream SLSA VSA can consume.

C. Ranked proposal for 2.2.0 (owner picked “hardening + patch + verify-in-CI + SDK v2, fail-closed gate, three patch formats”)

Section titled “C. Ranked proposal for 2.2.0 (owner picked “hardening + patch + verify-in-CI + SDK v2, fail-closed gate, three patch formats”)”
# Story Why now Est.
1 Repair 2.1.0 on npm: publish the 7 missing packages from tag v2.1.0 (manual bootstrap, same as 2.0.0); then npm trust github … --allow-publish on all 9 axm-lsp@2.1.0 is uninstallable today 0.5
2 patch source ({type:"patch", format:"unified"|"v4a"|"search-replace", preImage:"sha256:…", body}) — three parsers, one exact applier in plan; compile resolves to content-addressed blobs so Manifest/checks/apply are unchanged B1; agents emit diffs; V4A arrives raw in Codex hooks 2
3 Pre-image binding in the manifest: ManifestBody.preImage[] (path → sha256 or “absent”) emitted by compile when a root is given; check records it; apply re-verifies against it (in addition to the in-transaction TOCTOU check) B4; ties a stored CheckReport to the tree it judged 1
4 axiom verify --tree <root> <manifest> + dragoscv/axiom-verify-action@v1 (composite action): fails a PR whose tree digest ≠ manifest; emits an in-toto statement axiom.dev/attestation/apply/v1 B3; the third-party on-ramp; consumes #3 1.5
5 Gate v2: default fail-closed for write-class tools, unknown tool → deny with reason, --fail-open opt-in; V4A parsing for Codex; Claude hookSpecificOutput shape; OWASP-ACS verdict vocabulary in the JSON; sub-dir cwd → root discovery via .axiom/ or .git/ A9/A10; OWASP ACS #32; Copilot already denies on crash 1
6 MCP SDK v2 behind adapter.ts: codemod, registerTool, serveStdio/createMcpHandler, 2026 wire opt-in flag, conformance --requirements 2026-07-28; move guard.external >60 s and chunked plan build (plan_begin/plan_add/plan_seal) onto the tasks extension A6/B2; SDK v1 is EOL; brivio’s 68 excluded guards 2
7 Idempotency + drift: drifted re-apply proceeds with facts.drifted; ERR_ROLLBACK raised on rollback failure; split ERR_UNSUPPORTED_OPERR_FACT_DISABLED, ERR_TRUST_STATE_CORRUPT; tests for the 7 untested codes; corrupt-journal recovery test D/E; crash recovery has never run in CI 1
8 Predicate quality: content.noSecrets — exclude all-zero / repeated-digit PANs and UUID shapes; repo.requireCompanion mustChange: true option (companion must be in the plan, not merely exist); picomatch (/) auto-escape for route groups metu false positive; A15 0.5
9 Signing hardening: bind root identity into the DSSE payload type; sign state.json; document CI-key ceremony with AXIOM_SIGNING_KEY from a GitHub secret only A12/B7 0.5
10 Doc drift sweep: 7 stale “reserved” strings; Langium → hand-written in design doc; archive MCP-ONLY-PUBLIC-SURFACE.md; signed type widened D; LSP hover currently wrong 0.5

Deferred with reason: A1 Stryker (upstream), A4 Marketplace (owner publisher account), A13 PR push (needs a token policy decision), Cedar/OPA provider (no consumer asked), codai default-on (needs the eval-arm comparison first).

D. Open decisions for the owner (→ PLAN.md D-17…)

Section titled “D. Open decisions for the owner (→ PLAN.md D-17…)”
  • D-17 patch matching: exact only (recommended) vs Codex-style whitespace ladder.
  • D-18 gate default: fail-closed for write tools (owner chose) — and what Bash/shell tools get: deny, allow, or scan for redirections.
  • D-19 SDK v2 wire: ship 2026 wire opt-in (--wire 2026) or default in 2.2.0.
  • D-20 verify --tree scope: whole root vs manifest paths only (recommended: manifest paths + preImage set).
  • D-21 in-toto predicate name/version and whether verify action uploads it as a GitHub attestation.
  • D-22 2.1.0 repair path: manual publish now vs re-tag as 2.1.1 after trusted publishing is enabled.