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).
What was added to brivio
Section titled “What was added to brivio”| 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. |
Why only 7 guards in guard.external
Section titled “Why only 7 guards in guard.external”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).
Full suite as a task
Section titled “Full suite as a task”.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 childProof 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 s2. 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 deleted3. apply --dry-run exit 1 status=failed message="pre-apply checks: fail" (nothing written)4. NEGATIVE compile (overwrite pnpm-lock.yaml) exit 05. 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:131packages/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.
MCP server
Section titled “MCP server”.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