Skip to content

Add capability reliability ledger (#186) - #326

Draft
Joncallim wants to merge 2 commits into
mainfrom
claude/forge-issue-review-arch-37iv2q
Draft

Add capability reliability ledger (#186)#326
Joncallim wants to merge 2 commits into
mainfrom
claude/forge-issue-review-arch-37iv2q

Conversation

@Joncallim

@Joncallim Joncallim commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Source Issue

Closes #186
Parent Epic: #184

Agent Run

Runtime: claude-code
Run ID: n/a

Summary

This PR now contains both the architecture and the full implementation of #186 — Add capability reliability ledger, per docs/architecture/issue-186-capability-reliability-ledger.md and ADR 0012.

Two new immutable tables — capability_attempts and capability_attempt_adjudications — record comparable evidence for how reliably a specific capability has performed in a specific project/scope/runtime/policy cohort, without collapsing materially different work into one score and without letting a worker grade its own output.

Contracts (lib/reliability/contracts.ts): capability-key grammar (workpackage:<role>/<capability> or operation:<id>@<version>), domain-separated SHA-256 cohort/scope/runtime/policy fingerprints matching the ADR 0011 construction, closed verification_mode enum, and the ReliabilitySummary shape.

Metrics (lib/reliability/metrics.ts): computeReliability is a pure function — no clock, no I/O. Below a minimum sample size it reports insufficient_evidence with null rates; if any in-window attempt's linked outcome has changed since ingest it reports evidence_drift and withholds all rates; critical failures are always counted regardless of state. No materialized cache in v1 — deliberately, see §6.3 of the design doc.

Persistence (db/migrations/0031_capability_reliability_ledger.sql): both tables are append-only via BEFORE UPDATE/DELETE reject triggers; adjudications enforce gapless per-attempt sequencing via a BEFORE INSERT guard; every text column is closed by a CHECK — there is no free-text column anywhere in the ledger, so a model's prose, a file path, or a secret cannot enter it even by accident.

Ingest (worker/reliability/ledger.ts + context.ts): idempotent on (execution_outcome_id, capability_key), wired best-effort into the three existing work-package outcome boundaries (admission block, completion, failure), the ADR 0011 operation-finalize path (after its transaction commits, never inside it), and review-gate decisions as append-only adjudications. independent_agent verification is refused at ingest since #188 has no producer for it yet — the honest current state (completed, but nobody independent checked) is reported as an explicit unverifiedCompletion rate instead.

Read path (worker/reliability/reader.ts) and CLI (scripts/inspect-capability-reliability.tsnpm run protocol:inspect-capability-reliability): read-only cohort lookup with drift detection against the linked execution_outcomes row. No dashboard, no HTTP route in this slice — that's #191.

CI: extends the closed application-ACL inventory so the ordinary app role gets exactly SELECT, INSERT on both new tables (no UPDATE, no DELETE), and updates the four pinned migration-count/timestamp literals for migration 0031 (31→32 migrations, new max timestamp).

One non-additive change: upsertExecutionOutcome now returns the stored row id so ingest can link to it. Existing callers that ignored the previous void return are unaffected.

Acceptance Criteria Validation

Full mapping in §14 of the design doc. Two are explicitly partial rather than claimed complete:

  • Two attempts with materially different capability scopes are not silently combined — cohort fingerprint over project/capability/scope/runtime/policy
  • Runtime/model or policy-version changes are visible and can trigger requalification — component fingerprints stored alongside the cohort; a change produces a new cohort automatically
  • Every ledger entry links to a canonical outcome and verification/evidence state — execution_outcome_id NOT NULL
  • Reprocessing an attempt is idempotent — unique index + ON CONFLICT DO NOTHING, proven in both the mocked and gated-postgres suites
  • Metrics can be recomputed deterministically from stored attempts — pure function, no cache
  • A critical failure remains visible regardless of the aggregate pass percentage
  • Missing independent verification is not counted as a verified pass — self_reported/human_review never count toward independentlyVerifiedPass
  • [~] Human rejection, rollback, and override events affect the reliability view — human decisions are fully wired from decideReviewGate; rollback/override have storage, metrics, and tests but no producer until [FEATURE] Add evidence-based earned autonomy policy engine #189/[FEATURE] Add Project Sentinel detection and escalation flow #190 exist to emit them
  • [~] "historical data" — covered as attempts predating the table being readable as missing evidence, not as backfilled rows (consistent with ADR 0010)

Tests / Verification

  • npm run test:unit:zero-skip — full suite green except one pre-existing, unrelated flake (epic-172-s6-runner-failure-injection.test.ts, a process-kill/deadline test that fails identically on main before this change — confirmed via git stash).
  • New suites: capability-reliability-contracts.test.ts, capability-reliability-metrics.test.ts (20 tests, pure-function/fingerprint coverage), capability-reliability-ledger.test.ts (mocked-DB ingest: idempotency target, independent_agent refusal, flag-off, missing/overflow classification, multiplicity), capability-reliability-schema.test.ts (migration text assertions + CI ACL gate assertion), capability-reliability-ledger.postgres.test.ts (gated proof of append-only triggers, gapless sequencing, duplicate-insert rejection, and role privileges — mirrors operation-ledger.postgres.test.ts, requires FORGE_RELIABILITY_LEDGER_REQUIRE_POSTGRES_TEST=1 plus DATABASE_URL/admin URL to run; skips cleanly without them).
  • Updated review-gates.test.ts's pinned dbSelect call count (6→8) to account for the two new best-effort adjudication lookups.
  • npx tsc --noEmit and npx eslint . both clean.
  • No UI change, so no browser verification was applicable.

Risks / Follow-up

Issue #186 is the next unblocked child of Epic #184: #201 (deterministic
operation catalog) and #185 (canonical execution outcomes) have both landed,
and the Epic's own recommended order puts the reliability ledger next.

This is an architecture-only handoff for a Backend specialist. No
implementation, migration, or schema change is included.

Key decisions:

- Reliability is stored as individual append-only capability attempts keyed to
  a cohort fingerprint over project, capability, scope, runtime/model, and
  policy. Requalification is implicit: a material change produces a different
  cohort rather than silently reusing an old sample count.
- Attempts are immutable. Verification results, human decisions, rollback,
  override, and detected evidence drift are appended as separate adjudication
  rows so later evidence never rewrites what was recorded at the time.
- verification_mode decides what counts as verified. self_reported and
  human_review never contribute to the independently verified pass rate, and
  independent_agent is refused at ingest until #188 can produce it. The gap is
  reported as an explicit unverified-completion rate instead of being folded
  into a pass rate.
- The ledger has no free-text column at all. Every text column is a closed
  enum, a 64-hex fingerprint, or the bounded capability-key grammar, so prose,
  paths, and credentials cannot enter it even by accident.
- Metrics are a pure function of stored evidence with no materialized cache.
  Insufficient samples and drifted evidence fail closed to explicit states, and
  critical failures are always reported regardless of the aggregate.

The document also pins the four migration-count gates and the closed
application-ACL inventory an implementer must update alongside migration 0031,
and lists the stop conditions where they must escalate instead of improvising.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TP6Smtka3KKMZKKv6G5cJz
Implements the architecture in docs/architecture/issue-186-capability-
reliability-ledger.md and ADR 0012. Adds two new immutable tables,
capability_attempts and capability_attempt_adjudications, that record
comparable evidence for how reliably a specific capability has performed in a
specific project/scope/runtime/policy cohort -- without collapsing materially
different work into one score and without letting a worker grade its own
output.

- lib/reliability/contracts.ts: the versioned contract -- capability-key
  grammar, cohort/scope/runtime/policy fingerprints (domain-separated SHA-256,
  matching the ADR 0011 operation-fingerprint construction), verification
  modes, and the ReliabilitySummary shape.
- lib/reliability/metrics.ts: computeReliability, a pure function with no
  clock or I/O. Below a minimum sample size it reports insufficient_evidence
  with null rates; if any in-window attempt's linked outcome has changed since
  ingest it reports evidence_drift and withholds all rates; critical failures
  are always counted regardless of state.
- db/migrations/0031_capability_reliability_ledger.sql: both tables are
  append-only via BEFORE UPDATE/DELETE reject triggers, adjudications enforce
  gapless per-attempt sequencing via a BEFORE INSERT guard, and every text
  column is closed by a CHECK -- no free-text column exists in the ledger.
- worker/reliability/ledger.ts + context.ts: idempotent ingest keyed on
  (execution_outcome_id, capability_key), wired best-effort into the three
  work-package outcome boundaries, the ADR 0011 operation-finalize path (after
  its transaction commits, never inside it), and review-gate decisions as
  append-only adjudications. independent_agent verification is refused at
  ingest since #188 has no producer for it yet.
- worker/reliability/reader.ts + scripts/inspect-capability-reliability.ts:
  read-only cohort lookup and a CLI inspection command; no dashboard or HTTP
  route in this slice.
- CI: extends the closed application-ACL inventory so the ordinary app role
  gets exactly SELECT/INSERT on both new tables, and updates the four pinned
  migration-count/timestamp literals for migration 0031.

upsertExecutionOutcome now returns the stored row id so ingest can link to it;
existing callers that ignored the previous void return are unaffected.

Rollback and override adjudications have storage, metrics, and tests but no
producer until #189/#190 exist to emit them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TP6Smtka3KKMZKKv6G5cJz
@Joncallim Joncallim changed the title Architecture: capability reliability ledger (#186) Add capability reliability ledger (#186) Aug 8, 2026
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.

[FEATURE] Add capability reliability ledger

2 participants