Skip to content

brivio ← AXIOM integration (S-306)

Status 2026-09-18: wired and proven end-to-end in dry-run; files staged in E:\gh\brivio (not committed — owner commits). ADR: brivio/docs/adr/0200-axiom-write-gate.md.

File Purpose
scripts/axiom-guard-adapter.mjs guard.external adapter: reads the bundle from stdin, runs scripts/run-guards.mjs --quiet <names> (spawn, args array, no shell), maps FAILED <guard> headers / FAIL lines to AXIOM findings brivio.<guard>, prints {ok, findings}; always exit 0, 55 s internal cap.
.axiom/profiles/brivio.json Profile brivio extends default: repo.noOverwriteOf (.github/**, lockfiles, drizzle/**/meta/**), repo.requireCompanion transcribed from .copilot-ripple.json (gateway route → mcp tools + CLI + TS/PHP SDK + OpenAPI; openapi.ts → JSON; actions → test; db schema → migration), content.noSecrets, guard.external running the 7 change-set-relevant guards (see below).
.axiom/gate-profile.json PreToolUse gate: deny .git/**, .axiom/**, lockfiles, .env*, node_modules, drizzle/**/meta/**; noSecrets.
.github/hooks/axiom-gate.json Copilot CLI hook registration → axiom gate --stdin via the global bin (npm install -g @codai/axiom-mcp; p50 157 ms). Not npx: p50 7.8 s warm, over the 5 s fail-open timeout.
.gitignore .axiom/* runtime state ignored; profiles/ and gate-profile.json tracked.

Until 2.1.x guard.external capped timeoutMs at 60 000 and brivio’s full suite (~75 guards) takes well over that on this machine; check-untracked-imports alone is 64 s. The brivio profile therefore runs the 7 change-set-relevant guards synchronously. Measured per guard (host under load):

guard wall
org-context 3.9 s
audit-coverage 1.4 s
hardcoded-strings 1.2 s
egress-guard 8.1 s
sdk-coverage 0.3 s
route-boundaries 0.8 s
vacuous-assertions 9.7 s
untracked-imports 64.1 s (excluded)

The full suite still runs in brivio’s own pre-commit / CI; AXIOM runs the subset that speaks to a change-set (tenant scoping, audit, i18n, egress, SDK ripple, route boundaries, test quality).

Full suite as a task (2.2.0, S-406 / D-24)

Section titled “Full suite as a task (2.2.0, S-406 / D-24)”

.axiom/profiles/brivio-full.json extends brivio and overrides brivio.guards with all guards (args: []), timeoutMs: 900000 (the new 15 min cap) and env.AXIOM_GUARD_ADAPTER_TIMEOUT_MS=880000 (the adapter’s internal cap, previously hard-wired to 55 s). Run it as a task so the client’s per-call timeout is irrelevant:

axiom_check_start { bundle, root: "E:/gh/brivio", profile: "brivio-full" } → { taskId, status: "working", pollIntervalMs: 2000 }
axiom_task_get { taskId } (every pollIntervalMs) → … → { status: "completed", result: CheckReport }
axiom_task_cancel{ taskId } kills run-guards.mjs and every child

Proof run (VERIFIED 2026-09-20, .copilot-tmp/brivio-task-e2e.mjs, SDK-v2 stdio client against the built CLI, --allow-guards): axiom_check_start answered working at once; axiom_task_get every 2 s reached completed after 580 s — guard provider ok (579 611 ms), verdict: fail with 10 findings, all genuine brivio debt (bundle-dynamic-requires usage error, drag-alternative 18/23, icu-messages 2 throwing, orphan-actions 11, vacuous-assertions 6, …). One is environmental: nav-orphans spawns rg and the scrubbed guard env carries no WinGet Links dir on PATH — add env.PATH to the profile or make the guard resolve rg itself.

End-to-end proof (VERIFIED 2026-09-18, brivio tree untouched — dry-run only)

Section titled “End-to-end proof (VERIFIED 2026-09-18, brivio tree untouched — dry-run only)”

Commands run from E:\gh\axiom with the built CLI (.copilot-tmp/brivio-e2e.ps1):

1. compile smoke plan (docs/axiom-smoke.md) exit 0 0.5 s
2. check --root E:\gh\brivio --profile brivio --allow-guards exit 1 8.8 s
verdict=fail providers manifest=ok content=ok repo=ok guard=ok
- brivio.vacuous-assertions [error] vacuous-assertions: 4 assertion(s)
that pass with the guarded code deleted
3. apply --dry-run exit 1
status=failed message="pre-apply checks: fail" (nothing written)
4. NEGATIVE compile (overwrite pnpm-lock.yaml) exit 0
5. NEGATIVE check exit 1
- repo.noOverwriteOf [error] overwrite of protected existing file (pnpm-lock.yaml)
6. GATE: Copilot create_file → E:\gh\brivio\.env exit 2
stderr: AXIOM GATE DENY path.deny: path matches a denied glob (.env)
stdout: {"hookSpecificOutput":{"hookEventName":"PreToolUse",
"permissionDecision":"deny", ...}}

Step 2 is a real finding, not an integration bug. brivio’s own check-vacuous-assertions.mjs currently fails on 4 pre-existing assertions:

  • apps/web/src/lib/auth/__tests__/shared-device.test.ts:131
  • packages/notifications/src/dispatch-upsert-wiring.test.ts:42, :43, :51

AXIOM correctly refuses to apply any change-set while the repo’s own gate is red — that is the fail-closed behaviour the design asks for. Fix those four assertions in brivio (or drop vacuous-assertions from the profile’s args until they are fixed) and step 2/3 go green; the guard runner and the other six guards all returned ok.

  1. Review + commit the staged brivio files (explicit paths; shared clone).
  2. Fix the 4 vacuous assertions, re-run step 2.
  3. After @codai/axiom-mcp@2.0.0 is on npm, switch .github/hooks/axiom-gate.json to the global axiom bin (done 2026-09-19) and add .vscode/mcp.json server axiom (npx -y @codai/axiom-mcp@2 mcp --root ${workspaceFolder} — fine for a long-lived server).

See also

  • Checks — the guard.external contract the adapter implements
  • MCP toolsaxiom_check_start / axiom_task_get for the full suite
  • Hooks — the gate profile and Copilot hook shape
  • metu — the sibling integration without a guard runner