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.
Summary
Section titled “Summary”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:
- 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}) withregisterTool, zod ^4.2 and an opt-in 2026 wire. Conformance no longer scores the old spec. The design promised anmcp/src/adapter.tsseam for exactly this swap; it does not exist. - 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. - 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)”Deferred or non-guaranteed, named in docs
Section titled “Deferred or non-guaranteed, named in docs”| # | 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 |
Red-team critiques not addressed
Section titled “Red-team critiques not addressed”| # | 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 |
Friction seen in the three integrations
Section titled “Friction seen in the three integrations”- brivio:
guard.externalcaps at 60 s; full suite ~75 guards,check-untracked-importsalone 64 s → only 7 guards run through AXIOM. Pre-existing red guard (check-vacuous-assertions, 4 debts) blocks every apply.npxin the hook = p50 7.8 s > 5 s timeout. - metu:
content.noSecrets.cardfires on the zero-UUID00000000-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’scodai-tools.jsonmust be spliced at runtime; codai types AXIOM results with local interfaces (field renames break it).
Code-level drift and untested codes
Section titled “Code-level drift and untested codes”- 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: falseliteral type),docs/checks.md:6. - Drifted re-apply of
create→ERR_EXISTS, contradicting design (“proceeds, notefacts.drifted”). ERR_UNSUPPORTED_OPoverloaded for 3 meanings;ERR_JOURNAL_CORRUPTreused 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-Idremoved;initializeremoved (per-request_meta.protocolVersion/clientCapabilities);server/discoverMUST; tasks moved to extensionio.modelcontextprotocol/tasks(tasks/getpolling,tasks/update);Mcp-Method/Mcp-Nameheaders required on HTTP POST;ttlMs+cacheScoperequired ontools/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 viacreateMcpHandler/serveStdio. Codemod:npx @modelcontextprotocol/codemod@latest v1-to-v2. @modelcontextprotocol/conformance0.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--rootallowlist), so the removal hurts little.tasksextension is the natural home forguard.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-leveldecision/reasondeprecated; 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 mapsask→deny. Reads.github/hooks/*.json,~/.copilot/hooks,.claude/settings*.json. - VS Code:
tool_inputcamelCase (filePath); matchers ignored (fires for every tool). - Codex: hooks trusted by hash before running;
apply_patch→tool_name "apply_patch",tool_input.command= raw V4A text;askunsupported (hook marked failed, call proceeds);mcp_toolhandler type. - Cursor: no pre-write hook with content (
afterFileEditonly). 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 viasession/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.
Competitors / adjacent
Section titled “Competitors / adjacent”- gh-aw v0.88.7 “Safe Outputs” (buffer → scan → scoped write jobs); Codex
execpolicyStarlarkprefix_rule; Devin--sandboxfail-closed; OWASP Agent Control Standard v0.1.0 (2026-09-01) — Guardianallow/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.
Patch formats agents emit
Section titled “Patch formats agents emit”- V4A: grammar in
codex-rs/apply-patch/src/parser.rs; hostedapply_patchemitsoperation.{create_file|update_file|delete_file,path,diff}— no move op in the hosted schema. Matching ladder: exact →trim_end→trim→ Unicode-punctuation normalisation;*** End of Filemarker;PreserveLineEndingsmode. - Aider: default is SEARCH/REPLACE (
diffedit format) for GPT-5.x / Claude 4.x; udiff only for gpt-4-turbo. Rooapply_patch= V4A headers + real unified hunks. - Gemini
replaceladder ends in LLM self-correction; ClaudeEdit= exact unique match; ACP diff = fulloldText/newText; jsdiffapplyPatchfuzzFactordefaults 0. - Implication: a
patchsource 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.
Supply chain
Section titled “Supply chain”- 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_targetdefault-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);
minimumReleaseAgedefault 1440 min since v11;onlyBuiltDependencies→allowBuilds. Node 24 Active LTS, 22 Maintenance (EOL 2027-04-30), Node 26 LTS 2026-10-28. - Implication: enable trusted publishing with
--allow-publishon all 9 packages (owner UI step); keep DSSE manifests as the integrity layer that provenance is not; bumpenginesto>=22.14stays valid through April 2027, plan Node 24 floor for 3.0.
Canonicalisation
Section titled “Canonicalisation”- 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:
gitCommitMUST,gitTree/dirHashMAY. OmniBOR v0.2 mandates CRLF→LF (breaks byte-exact pre-image) — do not adopt. - Implication: publish AXIOM’s
CheckReport/ApplyResultas an in-toto predicate (https://axiom.dev/attestation/apply/v1) with the manifest digest as subject; that is the artifact a CIverifyaction 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_OP → ERR_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 --treescope: whole root vs manifest paths only (recommended: manifest paths +preImageset). - D-21 in-toto predicate name/version and whether
verifyaction 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.