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.
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 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”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 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.
Next (owner)
Section titled “Next (owner)”- Review + commit the staged brivio files (explicit paths; shared clone).
- Fix the 4 vacuous assertions, re-run step 2.
- After
@codai/axiom-mcp@2.0.0is on npm, switch.github/hooks/axiom-gate.jsonto the globalaxiombin (done 2026-09-19) and add.vscode/mcp.jsonserveraxiom(npx -y @codai/axiom-mcp@2 mcp --root ${workspaceFolder}— fine for a long-lived server).
See also