Skip to content

fix: guard capability drift, explain empty missions, make audit verification runnable - #99

Merged
jusso-dev merged 1 commit into
mainfrom
fix/capability-drift-missions-audit-runbook
Jul 30, 2026
Merged

fix: guard capability drift, explain empty missions, make audit verification runnable#99
jusso-dev merged 1 commit into
mainfrom
fix/capability-drift-missions-audit-runbook

Conversation

@jusso-dev

Copy link
Copy Markdown
Owner

Phase 5 of 6. Three unrelated carried-over defects.

1. Capability drift guard

bootstrap.ts hand-maintained administratorCapabilities as a literal duplicate of the authz list, and rewrites capability_assignments on every boot via excluded.capability_assignments — so any drift silently reverts the migration backfill too. That mechanism already ate agents.handoff earlier in this series.

It had drifted again, undetected: brolga.context.read was missing, added to authz with the Brolga connector and never mirrored here.

Starter actor rows moved into seed-data.ts as starterActorSeeds(email), following the existing bootstrap-connectors.ts split (script imports a testable module — bootstrap.ts is top-level-await and can't be imported directly). The administrator grant is now derived: [...starterRoleCapabilities.administrator]. No list to keep in sync.

starter-actors.test.ts is a real behavioural test, not a string match: administrator assignments must equal the authz capability set, no starter actor may hold an undeclared capability, agents must stay below administrator privilege. Verified it fails on reintroduced drift before landing.

2. Missions empty state

"Created via governed MCP tools or bootstrap" was accurate and useless. Now names muster_upsert_mission, states the caller needs workflows.manage plus that tool in its installation scopes, gives the provisioning command, and links to Guides — whose "Missions and Audit" entry is genuinely relevant, checked before linking. Still says explicitly that there is no UI create path by design (ADR 0005), so nobody files it as a missing feature.

3. Audit chain verification

Judgement: the strict failure needs no code fix, and making it pass would be wrong.

The events at and after sequence 136 are authentic. They were hashed with approvalId: undefined in metadata, which PostgreSQL JSONB drops on write. The only routes to a green strict result are rewriting stored hashes or rewriting metadata — both forbidden by packages/audit, and both would destroy the property the chain exists to provide. legacy-compatible-not-strict is the correct permanent outcome for any workspace holding pre-normalisation events. No audit row is touched; packages/audit is unchanged.

The real defect was operational. The verifier threw a raw unhandled Error when MUSTER_AUDIT_ORGANISATION_ID was unset — I hit this myself running it against the homelab. It now:

  • accepts --organisation=<uuid>
  • lists the organisations in the connected database when none is given
  • separates exit codes so an unset variable cannot page someone with "audit chain invalid": 64 usage, 69 unreachable database, 1 genuinely invalid chain
  • prints an operator-readable interpretation to stderr, leaving the JSON report on stdout unchanged

New runbook at docs/operations/audit-chain-verification.md, linked from the existing incident-recovery.md attestation steps rather than duplicating them.

The one thing worth watching, documented in the runbook: if a later run reports a legacy sequence that was not in the previous run, the writer is still emitting the old shape — that is a live regression to fix at the writer, not a historical artefact to accept.

🤖 Generated with Claude Code

Bootstrap's administrator grant was a hand-maintained array literal that had
already drifted from @muster/authz (brolga.context.read was missing), and
because bootstrap rewrites capability_assignments on every boot it silently
reverts any migration backfill. Starter actor rows now live in seed-data.ts
with the administrator grant derived from starterRoleCapabilities.administrator,
guarded by a behavioural test that fails on drift and on any undeclared
capability in a starter actor.

The Missions empty state stated a fact and left the operator stuck. It now
names the governed tool (muster_upsert_mission), the capability and scope its
caller needs, the operator command that provisions an MCP installation, and
links to Guides. No UI create path is introduced; ADR 0005 keeps mission
creation on the governed server path.

The audit verifier threw a raw error when MUSTER_AUDIT_ORGANISATION_ID was
unset. It now accepts --organisation, lists the organisations in the connected
database when none is supplied, prints an operator-readable interpretation
alongside the JSON report, and separates usage (64) and unreachable-database
(69) from a failed chain (1). No audit row is read differently or written;
docs/operations/audit-chain-verification.md documents how to run it and how to
interpret legacy-compatible-not-strict without ever repairing history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jusso-dev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e5cf502-0b51-4e3c-b293-d54aba73e616

📥 Commits

Reviewing files that changed from the base of the PR and between cdc2060 and 963809b.

📒 Files selected for processing (10)
  • apps/web/features/missions/missions-view.test.ts
  • apps/web/features/missions/missions-view.tsx
  • docs/operations/audit-chain-verification.md
  • docs/operations/incident-recovery.md
  • packages/database/src/audit-integrity-cli.test.ts
  • packages/database/src/audit-integrity-cli.ts
  • packages/database/src/bootstrap.ts
  • packages/database/src/seed-data.ts
  • packages/database/src/starter-actors.test.ts
  • packages/database/src/verify-audit-integrity.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jusso-dev
jusso-dev merged commit 763e9d4 into main Jul 30, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant