Skip to content

brivio ← AXIOM integration

Status: wired and proven end-to-end in dry-run (2026-09-18). ADR: brivio/docs/adr/0200-axiom-write-gate.md. The file list below is the 2.x layout brivio was wired with; on 3.x the two policy files are still read (with a deprecation warning) and axiom setup migrate --profile folds them into one .axiom/axiom.json (profiles.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 (the 3.0 noun form is axiom hook gate, byte-identical) 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.

brivio’s full suite (~75 guards) takes far longer than a synchronous check should; check-untracked-imports alone is 64 s. The brivio profile therefore runs the 7 change-set-relevant guards synchronously and leaves the full suite to a task (below). 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).

.axiom/profiles/brivio-full.json extends brivio and overrides brivio.guards with all guards (args: []), timeoutMs: 900000 (the 15 min cap) and env.AXIOM_GUARD_ADAPTER_TIMEOUT_MS=880000 (the adapter’s internal cap). Run it as a task so the client’s per-call timeout is irrelevant (the task tools are in the full toolset — start the server with --tools full):

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.

.vscode/mcp.json server axiom: npx -y @codai/axiom-mcp mcp --root ${workspaceFolder} — npx is fine for a long-lived server; add --tools full for the task tools above.


See also

  • Checks — the guard.external contract the adapter implements
  • MCP tools — axiom_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