CC Verifier Sidecar and Ready Gate — Observe Rollout (MC #105472)
CC Verifier Sidecar and Ready Gate — Observe Rollout (MC #105472)
Current state
The persistent CC verifier sidecar and exact-revision MC ready gate are installed in observe-only mode. Enforcement remains disabled; the pilot allowlist is empty and the enforcement watermark is unset. This page does not authorize pilot-enforce or enforce-new.
Accepted implementation
- WP1 verifier core:
044c0d30e7d7e973009991d91e42b8c2f77371ce - WP2 identity/handoff remediation:
ad28aa306350b44d568aa9c75f4502ec04634bfb - WP3 system source:
2cee8e17b1cbea58ba9320015e9c8cca793cb56d - Hook candidate:
c0871b8689da0063bd2d6cca26322f702a55c0d1 - Live system commit containing accepted WP3 bytes:
18e8ef5d257e34ebfdcb91ff806e7a377010a874 - Live hook commit:
6c52ef129dd68008551e355ef88c97b94fe2202b
The live commits do not share normal ancestry with the isolated review branches, so acceptance is based on direct byte equivalence of candidate-owned files, not inferred ancestry.
Independent evidence
- WP1 review:
/Users/makinja/system/evidence/105474/proveo-independent-review.md - WP2 review:
/Users/makinja/system/evidence/105479/proveo-review-identity-remediation/REPORT.md - Correct live Stop-hook pilot:
/Users/makinja/system/evidence/105479/live-pilot-105501/REPORT.md - Final WP3 exact-SHA review:
/Users/makinja/system/evidence/105472/proveo-wp3-system-2cee8e17/REPORT.md - Updated post-install acceptance:
/Users/makinja/system/evidence/105472/WP3-HOOK-ACCEPTANCE-UPDATE.md - Independent post-install review:
/Users/makinja/system/evidence/105472/proveo-wp3-postinstall/REPORT.md - Trusted exact-revision verdict:
/Users/makinja/system/evidence/105505/cc-verifier/2cee8e17b1cbea58ba9320015e9c8cca793cb56d/verdict.json
Final post-install validation passed 82/82 focused tests, 14/14 adjacent Stop-hook tests, and the shell integration smoke. The ready hook blocks only intentional verifier exit code 2 and fails open to the existing legacy gates on infrastructure errors.
Identity and trust boundaries
- Task identity must come from agreeing explicit trusted input, orchestrator-injected numeric
MC_TASK_ID, or demonstrably session-owned metadata. - Unsafe, conflicting, ambiguous, stale/global-marker-only, non-positive, or larger-than-
Number.MAX_SAFE_INTEGERIDs are rejected before MC database or revision resolution. - Verdicts are bound to an exact immutable revision. Any later commit invalidates PASS.
- Verification commands and evidence roots come from trusted policy; builder-provided commands and arbitrary evidence paths are never executed or trusted.
- Durable SQLite attestation detects accidental drift and one-artifact tampering, but is not a cryptographic boundary against a same-OS-user privileged process.
Promotion boundary
Promotion to an enforcing mode requires a separate explicit approval, a healthy attestation store, fresh attested PASS at the candidate revision, and review of observe telemetry. Historical ready_for_review backlog must never be enrolled. Rollback is configuration-only: set gate_rollout_mode to observe to remove blocking while preserving audit telemetry, or off to disable gate decisions.
title: CC Verifier Ready-Gate — Operator Runbook mc_task: 105505 parent_mc_task: 105472 status: observe (audit-only, no enforcement) project_path: /Users/makinja/system
CC Verifier Ready-Gate — Operator Runbook
Covers tools/cc-verifier-gate.ts (WP3 of MC #105472): the exact-revision check
that runs before mc.js ready. This is the operator guide for the rollout
lifecycle — activation, telemetry, invalidation, promotion, rollback, and
fail-open behavior. It does not restate the WP3 design (see the header
comment in tools/cc-verifier-gate.ts and specs/cc-verifier-sidecar-105472-plan.md).
1. Observe activation
The gate ships live with config/cc-verifier-pilot.json:
"gate_rollout_mode": "observe",
"gate_pilot_enforce_task_ids": [],
"gate_enforce_new_watermark_epoch_ms": null
In observe mode (decide() in tools/cc-verifier-gate.ts:363-365), every
failing finding is recorded to the audit log but the decision is always
{ action: 'audit', enforced: false } — it can never block mc.js ready.
observe is the only mode this pilot should run in until a human explicitly
promotes it (Section 4). No code change is required to activate observe —
it is the config default and the live-safe fallback if the config file is
missing or unparseable (loadGateConfig() falls back to
DEFAULT_GATE_CONFIG, which is also observe/empty/null).
To confirm the live config is still in the safe state:
node -e "const c=require('./config/cc-verifier-pilot.json'); console.log(c.gate_rollout_mode, c.gate_pilot_enforce_task_ids, c.gate_enforce_new_watermark_epoch_ms)"
# expected: observe [] null
2. Audit telemetry
Every checkReadiness() call appends one JSON line to the audit log
(~/system/state/cc-verifier-gate-audit.jsonl in production, overridable via
CC_VERIFIER_GATE_AUDIT_LOG for tests), regardless of mode or outcome:
{"ts":"...","component":"cc-verifier-gate","task_id":"...","dry_run":false,"finding":{...},"decision":{...}}
finding.reason carries the machine-readable failure code (e.g.
revision_mismatch, digest_mismatch, verdict_field_tampered,
stale_revision, no_verdict_found) so audit volume can be triaged by
failure class. Audit writes are best-effort and wrapped in try/catch
(appendAudit()) — a logging failure never changes the gate decision.
To review recent audit activity:
tail -n 50 ~/system/state/cc-verifier-gate-audit.jsonl | node -e "process.stdin.on('data',d=>d.toString().trim().split('\n').forEach(l=>{const e=JSON.parse(l);console.log(e.task_id, e.decision.mode, e.decision.action, e.finding.reason)}))"
Before promoting past observe, review the audit log for the candidate
task id(s) and confirm the only findings are expected ones (e.g. a
deliberately stale revision), not systemic issues like schema drift or a
misconfigured MC_DB_PATH.
3. Exact-revision invalidation
A verdict is only valid for the exact revision it was computed against.
evaluate() resolves the trusted current revision from --repo/--commit,
explicit --repo + live git rev-parse HEAD, or (default) the MC task's
tasks.project_path + live HEAD — never from the verdict itself
(resolveTrustedRevision(), tools/cc-verifier-gate.ts:162-206). If the
stored verdict's revision does not match the current HEAD, the finding is
revision_mismatch and ok:false.
If no verdict exists at the current revision but one exists at a prior
revision, the reason is stale_revision (as opposed to no_verdict_found,
which means the task was never verified at all) — this is the expected
signal after any new commit lands on a previously-passing task; a fresh
cc-verifier run is required before the gate can pass again at the new
revision.
Every evidence file hash and the top-level verdict field are also
independently recomputed from the checks array and re-read evidence
bytes — a hand-edited verdict.json (e.g. flipping PARTIAL to PASS,
or editing a stdout/stderr file after the fact) is caught as
verdict_field_tampered or digest_mismatch even when the stored
revision field is untouched.
4. Promotion prerequisites (pilot-enforce / enforce-new)
Both enforcing modes are opt-in per config field and require a human edit
to config/cc-verifier-pilot.json — there is no code path that
auto-promotes.
pilot-enforce — blocks only task ids explicitly listed in
gate_pilot_enforce_task_ids. Before adding a task id:
- Confirm the task has at least one
verdict_status: "PASS"audit entry at its current revision (Section 2). - Confirm no unexplained
digest_mismatch/verdict_field_tamperedentries exist for that task in the audit log. - Add the task id as a string to
gate_pilot_enforce_task_ids(e.g.["105505"]). Tasks not in the list continue to audit-only (decide()line 371:not in pilot allowlistfalls through toaction: 'audit').
enforce-new — blocks only tasks whose MC created_at is strictly
after gate_enforce_new_watermark_epoch_ms. Before setting the watermark:
- Run the pilot in
pilot-enforcefor a representative set of tasks first and confirm no false-positive blocks in the audit log. - Set
gate_enforce_new_watermark_epoch_msto an epoch-ms value at or after the promotion decision time — this must be a second, independent watermark from the WP2 event-ingestionactivation_watermark_epoch_ms; do not reuse or overwrite that field. - Any task created at or before the watermark, or when the watermark is
null/non-finite, is never eligible for blocking (decide()lines 374-381) — this is the live-safe default and must remain the fallback for any config read error.
Both modes still only ever block when finding.ok === false AND the
task is enrolled/eligible; a passing finding always resolves to
action: 'allow' regardless of mode (decide() line 359-361).
5. Rollback to off
To disable the gate entirely (including audit-only behavior), set:
"gate_rollout_mode": "off"
decide() line 355-357 short-circuits before evaluating the finding:
off always returns { action: 'allow', enforced: false }. Note
evaluate() still runs and is still written to the audit log by
checkReadiness() — off only changes the decision, not whether the
check executes or is recorded. To roll back from pilot-enforce or
enforce-new without fully disabling audit visibility, set
gate_rollout_mode back to observe instead — this preserves telemetry
while immediately removing all blocking.
Rollback is a config-only change; no deploy, restart, or code change is
required, since loadGateConfig() reads the file fresh on every
checkReadiness() call.
6. Fail-open incident handling
The gate is fail-open by design at every layer that is not the explicit enforcing-mode decision:
- Missing/unparseable config file —
loadGateConfig()catches the read/parse error and returnsDEFAULT_GATE_CONFIG(observe, empty allowlist, null watermark). - Unknown
gate_rollout_modevalue —loadGateConfig()ignores it (keeps the defaultobserve); if an unrecognized mode value somehow reachesdecide()anyway, the final fallback branch returns{ action: 'audit', enforced: false, reason: 'unknown_mode_fail_open_audit' }. - MC db missing/locked —
queryTaskRow()catches the error and returnsnull;resolveTrustedRevision()then returnsno_trusted_project_path, which is a normalok:falsefinding (still subject to the mode's decision logic — inobservethis only audits). - Audit log write failure —
appendAudit()swallows the error; it never affectsfinding/decision. - No commit/task/repo resolvable —
evaluate()returnsinvalid_task_idorrevision_undeterminable; these are ordinary findings, not exceptions, and flow through the same mode-gateddecide()path as any other failure.
If an incident is suspected (e.g. unexpected blocks reported by a builder), the immediate mitigation is:
node -e "const fs=require('fs');const p='config/cc-verifier-pilot.json';const c=JSON.parse(fs.readFileSync(p));c.gate_rollout_mode='observe';fs.writeFileSync(p, JSON.stringify(c,null,2))"
This reverts to audit-only without needing to identify root cause first. Then inspect the audit log (Section 2) for the affected task id(s) to determine whether the block was a correct enforcement (real tamper/stale revision) or a gate defect, before re-promoting.
7. Durable attestation store health
tools/cc-verifier.js (F1) writes one row per (task_id, revision) to a
SQLite store at ~/system/databases/cc-verifier-attestations.db
(overridable via CC_VERIFIER_ATTESTATION_DB for tests), recording the
SHA-256 of the exact verdict.json bytes it just persisted, the
verifier_identity, and written_at. This happens inside
writeAtomicJSON() immediately after the verdict file is written, so a
verdict and its attestation are never observed out of order by a reader.
The gate (tools/cc-verifier-gate.ts) reads this store read-only
(cc.getAttestation()) and requires all three to hold before treating a
verdict as genuine:
- an attestation row exists for
(task_id, revision)— otherwiseattestation_missing; - its
verdict_sha256matches the SHA-256 of the on-diskverdict.jsonbytes — otherwiseattestation_mismatch; - its
verifier_identitymatches the verdict's ownverifier_identityfield — otherwiseattestation_identity_mismatch.
cc-verifier.js's own idempotency cache (readAttestedVerdict()) applies
the same first two checks before it will reuse a cached verdict instead of
re-running checks — a verdict.json with no matching attestation is
treated as absent, not as a cache hit.
To check store health directly:
node -e "
const Database = require('better-sqlite3');
const db = new Database(process.env.CC_VERIFIER_ATTESTATION_DB
|| require('os').homedir() + '/system/databases/cc-verifier-attestations.db',
{ readonly: true, fileMustExist: true });
console.log(db.prepare('SELECT COUNT(*) AS n FROM attestations').get());
db.close();
"
If this fails (file missing, not a database, or table missing), the store
is unhealthy — see Section 7.2. writeAttestation() itself never throws
(all failures are caught and swallowed): a write failure does not crash
verify()/finalizeBlocked(), it only means that verdict will show as
attestation_missing to the gate until the store is repaired and a fresh
verify run is performed. This is deliberate fail-closed-for-the-gate,
fail-open-for-the-builder-pipeline behavior — verification work is never
blocked by attestation-store trouble, but the gate will not silently trust
an unattested verdict either.
7.1 Same-OS-user trust boundary
The attestation store raises the bar for forging a verdict; it does not
change the trust boundary. Both verdict.json and
cc-verifier-attestations.db are ordinary files writable by whichever OS
user runs cc-verifier.js/cc-verifier-gate.ts (no setuid, no separate
service account, no OS-level ACL beyond normal file permissions). Any
process running as that same user can, with better-sqlite3 and the
schema in Section 7, hand-craft an attestations row with a
verdict_sha256 that matches a hand-edited verdict.json — exactly as it
could previously hand-edit verdict.json alone. The store's value is
narrower than a same-user attacker: it converts a one-file edit into a
two-artifact forgery (matching JSON bytes and a matching DB row), which
is enough to catch accidental drift, partial edits, and non-adversarial
tooling bugs, but it is not a defense against a same-user, same-
privilege adversary with full filesystem access. Cross-user tampering
(a different OS user, or a user without write access to
~/system/databases/) is out of scope for this store and is not claimed
to be mitigated by it.
7.2 Integrity-mismatch recovery and reverification
attestation_missing, attestation_mismatch, and
attestation_identity_mismatch are ordinary ok:false findings — they
flow through the same mode-gated decide() path as revision_mismatch or
digest_mismatch (Section 3) and, in observe mode, only audit. They do
not indicate gate malfunction by themselves; they indicate the on-disk
verdict is not attested at the store's current state. Recovery is always
the same: re-run cc-verifier for the task at the current revision. There
is no repair path that edits or re-derives an attestation row in place —
getAttestation() never invents or fixes a row, and there is no supported
"resync" operation; a fresh, trusted verify pass is the only way to
re-establish attestation for a given (task_id, revision):
node tools/cc-verifier.js replay --task <id> --revision <sha>
If mismatches appear across many unrelated tasks at once (rather than one task after a targeted hand-edit), treat it as a store-health incident, not a per-task tamper event:
- Confirm the store file itself is intact — re-run the health check in
Section 7 read-only query. A
SqliteError/ENOENTthere (as opposed to individual row mismatches) points to filesystem/disk trouble, a moved or deleted database file, or a botched migration, not tampering. - Confirm
CC_VERIFIER_ATTESTATION_DBis not accidentally pointed at a test/tmp path in the production environment (it is test-override-only; production should always resolve to the~/system/databases/cc-verifier-attestations.dbdefault). - Once the store itself is confirmed healthy, mismatches are per-task and
require the standard recovery above (fresh
cc-verifierrun) rather than any store-wide remediation.
7.3 Hard prerequisite for promotion
Sections 4's pilot-enforce/enforce-new promotion steps assume the
attestation store is healthy. This is a hard, non-negotiable prerequisite,
not an additional nice-to-have check: evaluate() treats
attestation_missing/attestation_mismatch/attestation_identity_mismatch
as ordinary ok:false findings, and in an enforcing mode ok:false for an
enrolled/eligible task blocks mc.js ready (Section 4, last paragraph).
If the attestation store is unhealthy (missing, corrupt, or unreachable)
at the moment pilot-enforce/enforce-new is enabled, every verdict —
including genuinely correct, untampered ones — will read as
attestation_missing and be blocked. Before adding any task id to
gate_pilot_enforce_task_ids or setting
gate_enforce_new_watermark_epoch_ms:
- Run the Section 7 health check and confirm it returns a row count without error.
- Confirm the candidate task's own audit log entries (Section 2) show
PASSoutcomes with noattestation_*reasons at the current revision — a healthy store but a stale/never-attested verdict for this specific task still means "re-runcc-verifierfirst," not "promote anyway." - If the store is unhealthy, fix it (Section 7.2) and obtain a fresh
attested
PASSbefore promoting — never promote to an enforcing mode "around" a known-unhealthy store on the theory that observe-mode telemetry looked fine; observe mode never blocks, so it cannot surface this failure mode the way an enforcing mode will.
Current-session hard-enforcement gap and required remediation
Finding
The verifier decision logic, durable handoff, attestation, and observe-mode ready-gate work as reviewed. However, changing gate_rollout_mode alone does not guarantee enforcement over every already-running or non-Claude session.
At the time of diagnosis, three native Claude Code processes were active and all had started before the latest ~/.claude/settings.json installation. The active Bilko task #105568 had no matching cc.verification_requested event. Hook presence in an already-running process is therefore not assumed without an explicit runtime handshake.
The current exact-revision ready gate is invoked by a Claude Code PreToolUse hook. It is not yet called authoritatively inside the central mc.js ready state transition. Consequently, a stale native session, Pi/non-Claude caller, direct MC invocation, or completion path that does not traverse Claude's Bash hook can bypass the session-local gate.
Local diagnosis artifact:
/Users/makinja/system/evidence/105472/ENFORCEMENT-GAP-CURRENT-SESSIONS.md
Required hard-enforcement architecture
Corrected rollout statement
The installed system currently provides independently accepted observe-only verification and proven pilot-enforcement decision behavior. It must not be described as universal hard enforcement across all active sessions until the central MC transition remediation above is implemented and independently reviewed.
No comments to display
No comments to display