인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
디자인 및 창작 · SKILL.md

verification-arbiter

verification-arbiter

Use when bob needs an independent verdict on an evidence bundle as part of the INTEGRATED → VERIFIED transition. Invoked as a subprocess (not a Claude Code Agent). Reads a frozen evidence bundle + frozen test plan, runs a cold-context Claude to score coverage against the plan and self-check the bundle hash, and emits one JSON verdict on stdout. Bob persists…

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: verification-arbiter description: Use when bob needs an independent verdict on an evidence bundle as part of the INTEGRATED → VERIFIED transition. Invoked as a subprocess (not a Claude Code Agent). Reads a frozen evidence bundle + frozen test plan, runs a cold-context Claude to score coverage against the plan and self-check the bundle hash, and emits one JSON verdict on stdout. Bob persists the verdict; the arbiter never writes to .ledger/ (CB4 boundary per design §5.6). disambiguation: Scores an EVIDENCE BUNDLE against a frozen test plan for INTEGRATED to VERIFIED. Rendered-UI geometry is visual-arbiter; drift classification is design-drift-arbiter.

verification-arbiter

Independent verifier invoked by bob. NOT a peer to bob — bob initiates, controls inputs, and consumes outputs.

When to invoke

From bob's Step 4.5 (Phase 2B wiring — live since S048 (#116)): after trusted_runner produces a sanitized evidence bundle and bob has opened a verification request in .ledger/requests/verification/<request_id>.request.yaml. Arbiter runs in parallel with audit_spawn.py; both verdicts are required for a VERIFIED transition.

Invocation

Subprocess, not an Agent. Bob spawns it as:

python3 skills/_meta/verification_arbiter_spawn.py \
  <bundle_path> <bundle_hash> <request_id> <attempt_id> \
  <prior_state_version> <plan_path> <plan_hash> \
  <inventory_hash> <runner_version> <rubric_version>

All 10 arguments are positional and required. Bob captures stdout; the arbiter writes nothing to disk.

Input tuple (8 fields echoed back verbatim)

FieldFormatPurpose
request_id32-hexCausal linkage to the open verification request
attempt_idnon-empty stringDistinguishes retries against the same component
prior_state_versionnon-empty stringPins verdict to ledger state at request-open time
bundle_hash64-hex (sha256)Content hash of the evidence bundle being verified
plan_hash64-hex (sha256)Test plan version the arbiter scored coverage against
inventory_hash64-hex (sha256)env-adoption inventory at verification time
runner_versionnon-empty stringtrusted_runner version that produced the bundle
rubric_versionnon-empty stringArbiter's evaluation rubric version (see below)

Bob's full 8-field tuple-match is the outer gate on VERIFIED per design §5.3; this skill only enforces echo-back.

Output (stdout only)

One JSON object matching verdict_schema.json. Top-level verdict is one of:

  • VERIFIED — full coverage, no blocker concerns, bundle self-hash matches input
  • VERIFIED_WITH_CONCERNS — full coverage, one or more warning-severity concerns
  • REJECTED — uncovered required requirements OR self_hash_check.matches_input == false OR any blocker concern
  • AUDIT_UNAVAILABLE — reserved for the spawn script. The cold-context model MUST NOT emit this verdict; doing so triggers exit 4 and a rejection.

Exit codes

CodeMeaning
0Valid schema-compliant verdict emitted on stdout; all 8 tuple fields echoed correctly
3Environmental / usage error (wrong argv count, bundle_hash not 64-hex, unreadable input file)
4AUDIT_UNAVAILABLE — subprocess crashed, timed out, produced non-JSON stdout, schema-invalid verdict, tuple mismatch, or model-emitted AUDIT_UNAVAILABLE. Bob MUST escalate; never auto-approve.

Design §5.6: arbiter never writes .ledger/. On any failure it exits with code 4 and a single-line stderr diagnostic; bob is sole ledger writer.

Rubric (v1.2.0)

Source of truth: this SKILL.md file itself, hashed. Bump the rubric version (semver) when changes to this file affect verdict semantics. The rubric_version bob passes (10th positional arg) is the CUTOVER KEY: bob's R6 (claims.assert_verified_preconditions) requires citation-corroboration only when the verdict's rubric_version >= 1.2.0 (older verdicts skip it deterministically — no silent-disable gap). Current rubric covers:

  1. Self-hash check. The model MUST recompute the bundle hash from on-disk bytes using the canonical JSON form (sort_keys=True, separators=(",", ":"), ensure_ascii=False, exclude the bundle_hash field itself — imported from skills/_meta/trusted_runner.py:canonical_bundle_bytes / bundle_hash_hex). Result populates self_hash_check.bundle_recomputed_hash + matches_input. Mismatch forces REJECTED.

  2. Coverage scoring. For each requirement in the frozen plan (test_plan_schema.json — REQ-NNN entries with test_types, required_tier, skip_if_tier_below):

    • Covered. At least one matching test in the bundle passed or was intentionally xfailed.
    • Uncovered. No matching test — listed in coverage.uncovered[].
    • Skipped with reason. Test exists in bundle but was skipped due to tier shortfall or declared skip_if_tier_below; listed in coverage.skipped_with_reason[] with requirement_id, reason, and tier_required when applicable. A skip with declared reason is NOT a coverage gap (design §1 goal 5).
  3. Plan-bundle shape checks. Plan must be parseable against test_plan_schema.json; bundle must be parseable and contain test results. Parse failure on either → REJECTED with a blocker concern.

  4. Tier awareness. If the current inventory tier (from inventory_hash-referenced manifest) is below a requirement's required_tier AND the requirement declares skip_if_tier_below, skip declarations are valid. If not declared, the missing coverage is a blocker concern.

  5. Security verdict (NEW in v1.1.0 — S038 Batch E, 2026-05-25). Bundle MUST include a security_status field populated from the relevant gate outputs. The arbiter scores it into the verdict's security_verdict arm with values:

    • passed — G_SECURE and G_SECRETS_SCAN (when configured) both PASS, OR the plan does not declare security_required: true. No blocker raised.
    • advisory — gates ran but in advisory mode AND produced findings. Not a blocker, but emit a concern of severity medium so bob's report surfaces it.
    • not_applicable — the frozen plan explicitly sets security_required: false. Skip-with-reason recorded.
    • failed — G_SECURE --sast-mode strict OR G_SECRETS_SCAN --secrets-mode strict returned exit 2. Forces REJECTED with a blocker.
    • unavailable — gates not configured for the project (no SAST runner, no scanner) AND security_required: true in plan. Emit a concern of severity high and AUDIT_UNAVAILABLE (exit 4), forcing bob to escalate. Never silently auto-approve.

    Plan flag. test_plan_schema.json v1.1 (S038 Batch E) adds optional security_required: bool at plan-level (default false for backwards compat). When true, missing security_status field in the bundle = unavailable.

    Companion skills: sast-tooling (G_SECURE), secret-scanning (G_SECRETS_SCAN), dep-currency-check (G_DEP_CURRENCY — referenced separately by bob, not part of arbiter rubric since dep-check runs at design-time not test-time).

  6. Evidence-map citations (NEW in v1.2.0 — S048 / #116, the non-LLM verification arm). The verdict MUST carry a top-level evidence_map: {<REQ-ID>: [<nodeid>, ...]} — for each REQ-ID scored as covered, the verbatim passing-test nodeid(s) (outcome == "passed") from the bundle's results[].tests[] cited as satisfying it. This is a REQUIRED top-level key (in verdict_schema.json + verification_arbiter_spawn.REQUIRED_TOP_KEYS — without it there, the arbiter's own hand-rolled validate_verdict would reject its output and bob would halt). Bob's deterministic arm (deterministic_arm.corroborate_citations, invoked by R6) verifies every cited nodeid EXISTS in the bundle AND passed, and REJECTS the INTEGRATED→VERIFIED transition if any cited nodeid is absent or non-passing (an invented/misattributed-evidence tell — the correlated-hallucination signal). Empty {} is valid (degraded/returncode-only or jest bundle with no per-test records, or coverage by skips-with-reason only); on such bundles citation-corroboration is recorded unavailable and does NOT veto. Honest residual: catches invented evidence, NOT irrelevant-but-real citations (a real passing test cited for the wrong criterion). The deeper semantic-test-adequacy residual (tests that pass but encode the wrong oracle) is deferred to #151.

    This couples with R6's deterministic-evidence conjunct: VERIFIED now requires audit_arm passes ∧ arbiter_arm passes ∧ deterministic_evidence == GREEN ∧ citations_corroborated (a flat conjunction, NOT a quorum — the deterministic arm can only VETO). The deterministic GREEN/RED/INDETERMINATE verdict is derived by R6 from the hash-addressed bundle itself (deterministic_arm.classify_bundle_evidence), never from a producer-written archive boolean and never from gate-runs.jsonl.

Env vars

VariableDefaultPurpose
AUDIT_CLAUDE_BINclaudeCold-context Claude CLI binary path (shared with audit_spawn.py)
AUDIT_CLAUDE_MODELclaude-opus-4-6[1m]Model ID for the cold-context verifier
ARBITER_FRESHNESS_WINDOW_S1800Seconds after opened_at before a pending verification request is considered abandoned (design §9.5). Consumed by Phase 2B's recover_verification_requests() sweeper in claims.py.

Timeout for the inner claude -p subprocess is DEFAULT_TIMEOUT_S = 180 (matches audit_spawn.py). Design §9.5 rationale: the 1800s freshness window is 10× the inner timeout, leaving headroom for bob restart + arbiter retry.

Relationship to audit_spawn.py

Per design §5.6, the two run in parallel after a bundle is produced; bob consumes both verdicts before applying VERIFIED. They are complementary, not redundant:

  • audit_spawn.py — metacognitive audit (Claude + Codex, disagreement detection). Unchanged by Phase 2A.
  • verification_arbiter_spawn.py — single-verifier coverage + self-hash check against the frozen test plan. New in Phase 2A-1.

Either arm REJECTED blocks VERIFIED. Either arm AUDIT_UNAVAILABLE forces escalation (no auto-approve).

Scope boundaries

In scopeOut of scope
Scoring bundle coverage against frozen planCreating or amending the test plan (test-architect owns that; §5.5)
Recomputing bundle_hash from on-disk bytesWriting .ledger/verdicts/*.verdict.yaml (bob owns; §5.6)
Echoing the 8-field tupleDeciding INTEGRATED → VERIFIED (bob owns; §5.3 outer tuple-match gate)
Emitting AUDIT_UNAVAILABLE via exit 4Model-emitted AUDIT_UNAVAILABLE (rejected by spawn script)
Reading inventory via its hashLive env-adoption probe (inventory is a frozen input)

References

  • Design doc: docs/plans/2026-04-21-tester-split-design.md (§5.1 role, §5.3 tuple, §5.6 CB4 boundary, §5.7 atomic writes, §9.5 freshness window)
  • Handoff: docs/plans/2026-04-21-tester-split-phase2-handoff.md
  • Schemas: skills/_meta/verdict_schema.json, skills/_meta/test_plan_schema.json
  • Binary: skills/_meta/verification_arbiter_spawn.py
  • Tests: skills/_meta/tests/test_verification_arbiter_spawn.py (12 tests, all passing)
  • Sibling subprocess (runs in parallel): ~/.claude/skills/_meta/audit_spawn.py
저장한 Skills