Skip to content

fix(trust): commit the decision record to what actually decided, record the holdout draw, and fix the attestation envelope's width and freshness - #9178

Merged
JSONbored merged 1 commit into
mainfrom
fix/commitments-replay-attestation-9124-9135-9140
Jul 27, 2026
Merged

fix(trust): commit the decision record to what actually decided, record the holdout draw, and fix the attestation envelope's width and freshness#9178
JSONbored merged 1 commit into
mainfrom
fix/commitments-replay-attestation-9124-9135-9140

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Three linked issues on the decision-record / decision-replay / attestation trust surface:

  • orb(trust): the decision record's commitments don't commit — modelId is always null, promptDigest ignores ten prompt suffixes, configDigest misses the calibrated close floor #9124DecisionRecord's three commitments (configDigest, promptDigest, modelId) did not commit to what actually decided the PR. configDigest now digests { policy: gate.replay?.policy ?? settings, untrustworthyRuleCodes } — the RESOLVED gateCheckPolicy(...) object evaluateGateCheck actually ran against (including the live-calibrated AI close-confidence floor and the cron-refreshed untrustworthy-rule-code set the precision breaker consults), not raw settings. The raw settings digest survives as a new, separate settingsDigest field. promptDigest is now sha256Hex of buildSystemPrompt's ACTUAL output (base template + whichever of its suffixes resolved for this call — grounding/enrichment/profile/security-focus/path-instructions/review.instructions/screenshot-evidence/inline/category/improvement-signal), computed in ai-review.ts and threaded onto the ai_consensus_defect/ai_review_split finding in ai-review-orchestration.ts. modelId is renamed modelIds: string[] | null and carries the real parsed-reviewer identities (parsedReviewModelIds), read straight off the finding at the decision-record call site instead of hardcoding null. ciState is populated from the live CI aggregate already in scope, instead of hardcoded null. DECISION_RECORD_SCHEMA_VERSION bumped "3""4", documented in the module header and each field's own doc comment.

  • orb(replay): an unrecorded Math.random() draw turns closes into holds — two identical records, different actions, and replay cannot see it #9135 — the randomized close-audit holdout's Math.random() draw was recorded nowhere, so two decisions with identical findings/policy/configDigest could diverge to action: "close" vs action: "hold" with no trace of why, and replay could not see it. The draw is now HMAC(instance secret, seed) (src/review/close-audit-holdout.ts) — the seed is the decision's own eventual record id (record:<repo>#<pr>@<head sha>), and the secret is a dedicated 256-bit value persisted in system_flags (mirroring orb-collector.ts's getOrCreateAnonSecret pattern, key-separated from every other secret in the codebase). This makes the draw reproducible from the record (for anyone holding the instance secret) without storing raw entropy, and unpredictable to a contributor without that secret trying to time a PR to dodge the holdout. The outcome ({ epsilonPct, draw, diverted }) is threaded into DecisionRecord.divertedByHoldout (public — rendered on the record's own face in the PR comment) and DecisionReplayInput.holdout (private), and replayDecision gained a new holdout_consistency stage that reports a divergence between the two rather than silently matching. The replay contract's module header now states explicitly what is (and, honestly, is not yet) captured: breaker-flag state, the live CI aggregate, and the global pause/freeze switches remain outside this input, documented as a narrower claim rather than an unqualified broad one.

  • calibration: the attestation envelope's reportData is 32 bytes for a 64-byte TEE field, and its binding has no freshness component — a valid report replays #9140packages/loopover-engine's attestation envelope (attestation-envelope.ts, schema seam for a future attested-evaluation epic, no hardware involved) had two gaps: reportData was a 32-byte SHA-256 digest for a TEE hardware field that is 64 bytes wide (SEV-SNP REPORT_DATA / TDX REPORTDATA), and the binding (corpusChecksum:headSha:baseSha) had no freshness component, so one genuine attested run's report could be replayed for any later run with the same corpus/shas. reportData is now sha256(binding) || sha256(runId) — 128 hex chars — with a new runId envelope field carrying the plaintext freshness token, published as a worked test vector in buildAttestationReportData's own doc comment. The three binding inputs are now constrained to their expected hex shapes (64-hex checksum, 40/64-hex git shas), closing the colon-injection ambiguity the old raw string-join allowed. The base64 validator (attestationReport) now rejects a length that isn't a multiple of 4.

#9141 (research-only — "do NOT start implementation") — posted the requested written scope decision as an issue comment (in the #8136/#8137 two-spike format: cost analysis, comparison against anchoring+complete-records, explicit answer on the partial-attestation question) and closed the issue as answered, matching the precedent set by #8136/#8137 themselves. Verdict: not worth pursuing live-path attestation now — a KBS-outage fail-open/fail-closed dilemma is a worse availability tradeoff than the marginal proof it would buy over the deterministic replay harness (#8832/#8838) already in place, and the anchoring + complete-records work (this same batch, and the parallel #9120/#9122/#9123/#9134 PR already merged to main) closes the practical gap a network verifier cares about more cheaply. apps/loopover-ui/content/docs/verify-this-review.mdx documents this boundary permanently in its existing "what this proves — and what it does not" callout.

Rebase note: this branch was rebased twice against a fast-moving main (most recently onto 70b60e729) to reconcile with the parallel #9120/#9122/#9123/#9134 PR, which also touched decision-record.ts (revisioned persist ids, full-digest rendering) and processors.ts (unconditional record-building context). Both sets of changes are compatible and merged cleanly; agent-action-executor.ts's new DecisionRecordContext.modelId was renamed to modelIds to match, with settingsDigest wired through for its call sites too (their configDigest already IS the raw settings digest, so the same value is honest there).

Scope

Validation

  • git diff --check
  • npm run actionlint — not run this pass (no workflow files touched).
  • npm run typecheck — clean, re-run after each rebase.
  • npm run test:coverage (full/unsharded) — not run this pass, per explicit instruction to skip the full local gate and let CI run it. Instead ran targeted vitest run across every touched/added test file plus every test file the parallel fix(trust): unauthenticate ledger verify, catch tail truncation, publish records, record every close (#9120 #9122 #9123 #9134) #9172 PR touched (to verify the rebase): ai-review.test.ts, close-audit-holdout.test.ts, crypto.test.ts, decision-record.test.ts, decision-replay.test.ts, gate-check-policy.test.ts, queue-2.test.ts, backfill-decision-labels-core.test.ts, agent-action-executor.test.ts, agent-approval-queue.test.ts, public-decision-ledger-routes.test.ts, held-lock-registry.test.ts, surface-disposition-reconciler.test.ts — 872 passing in the final combined run, plus a scoped --coverage pass showing 100% branch coverage on decision-record.ts, close-audit-holdout.ts, and decision-replay.ts specifically. packages/loopover-engine's own npm run test (builds + node:test) passes all 734 tests including the new/updated attestation-envelope suite.
  • npm run test:workers — not run this pass (no Worker-specific surface touched; see note above).
  • npm run build:mcp / npm run test:mcp-pack — not run this pass (no packages/loopover-mcp changes).
  • npm run ui:openapi:check — not run; no API/OpenAPI schema changed (DecisionRecord is not exposed through any route).
  • npm run ui:lint / npm run ui:typecheck / npm run ui:build — not run; only change under apps/loopover-ui is a prose addition to one existing .mdx doc page (no component/route/nav change).
  • npm audit --audit-level=moderate — not run this pass, per the same skip-the-full-gate instruction.
  • New/changed behavior has unit tests for new branches, fallback paths, and the sanitizer/normalization boundaries touched (see per-issue test notes below).

Per-issue regression tests added:

If any required check was skipped, explain why:

  • Explicitly instructed to skip the full local gate (npm run test:ci, npm audit) and let GitHub Actions CI run it; targeted checks above (typecheck + scoped test runs + scoped coverage) were run instead as the fast local signal.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, PATs, private keys, trust scores, or private rankings anywhere. The new close-audit-holdout secret is a freshly generated 256-bit value, never logged, never rendered in any public surface — only its HMAC output (a derived, non-reversible draw value) is persisted.
  • Public GitHub text stays sanitized — the rendered decision-record comment gains one bounded note line (divertedByHoldout) and a model-id join, no new private terms.
  • N/A — no auth/cookie/CORS/GitHub App/Cloudflare/session changes.
  • N/A — no API/OpenAPI/MCP surface changed.
  • N/A — no UI data-fetching changes.
  • UI Evidence — not applicable. The only change under apps/loopover-ui is a prose addition to one existing .mdx doc page's existing <Callout> (no new component, layout, or visual difference to screenshot).
  • Public docs updated: verify-this-review.mdx's existing "what this proves — and what it does not" callout now states the live-path-attestation boundary permanently (per Research: the attested-evaluation epic covers offline backtests only — decide whether the LIVE decision path needs attestation, and in what order #9141's own written decision). No CHANGELOG.md edit.

Notes

…rd the holdout draw, and fix the attestation envelope's width and freshness

Three linked issues on the decision-record/replay/attestation trust surface:

- #9124: configDigest hashed raw settings instead of the resolved gate
  policy (missing the live-calibrated close-confidence floor and the
  untrustworthy-rule-code set); promptDigest hashed the base template
  instead of the actual sent system prompt; modelId was hardcoded null.
  configDigest now digests {resolved policy, untrustworthy rule codes}
  with the raw settings digest kept separately as settingsDigest;
  promptDigest is sha256 of buildSystemPrompt's real output, threaded
  through ai-review-orchestration.ts's finding construction; modelIds
  carries the real parsed-reviewer set. ciState is populated from the
  live CI aggregate instead of hardcoded null. Schema bumped to v4.

- #9135: the close-audit holdout's Math.random() draw was unrecorded,
  so two decisions with identical findings/policy could silently
  diverge to close vs hold with no trace. The draw is now
  HMAC(instance secret, seed) — reproducible from the record without
  storing raw entropy, unpredictable without the secret — and the
  outcome is threaded into DecisionRecord.divertedByHoldout (public)
  and DecisionReplayInput.holdout (private), with a new replay stage
  that reports the divergence if the two ever disagree instead of
  silently matching.

- #9140: the attestation envelope's reportData was 32 bytes for TEE
  hardware's 64-byte REPORT_DATA/REPORTDATA field, with no freshness
  binding (a genuine attested run's report could be replayed for any
  later run with the same corpus/shas). reportData is now
  sha256(binding) || sha256(runId), with the binding inputs constrained
  to hex shapes to close the colon-injection ambiguity, and the base64
  validator now rejects lengths that aren't multiples of 4.

#9141 (research-only, no implementation): posted a written scope
decision on the issue and closed it as answered — attesting the live
gate-decision path is not worth pursuing now, given the fail-open/
fail-closed dilemma a KBS outage would force and that the anchoring +
complete-records work already closes the practical gap more cheaply.
verify-this-review.mdx documents the boundary permanently.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui 2ac63b7 Commit Preview URL

Branch Preview URL
Jul 27 2026, 05:48 AM

@github-actions

Copy link
Copy Markdown
Contributor

Logic backtest

Replayed 0 historical case(s) for linked_issue_scope_mismatch through the base (5f4edf9) and head (2ac63b7) versions of its detection logic (corpus checksum 4f53cda18c2b).

Backtest comparison: linked_issue_scope_mismatch

Verdict: unchanged — no comparable axis moved.

Advisory only — this check never blocks merge (#8105).

@JSONbored JSONbored self-assigned this Jul 27, 2026
@JSONbored
JSONbored merged commit 0eadcbd into main Jul 27, 2026
7 of 9 checks passed
@JSONbored
JSONbored deleted the fix/commitments-replay-attestation-9124-9135-9140 branch July 27, 2026 05:49
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

❌ 7 Tests Failed:

Tests completed Failed Passed Skipped
22469 7 22462 21
View the top 3 failed test(s) by shortest run time
test/unit/backfill.test.ts > GitHub backfill > refreshes installation health from live GitHub App metadata
Stack Traces | 0.026s run time
AssertionError: expected [ { installationId: 123, …(19) } ] to deeply equal ArrayContaining{…}

- Expected
+ Received

- ArrayContaining [
-   ObjectContaining {
+ [
+   {
      "accountLogin": "JSONbored",
+     "authMode": "local",
+     "checkedAt": "2026-07-27T05:50:00.482Z",
+     "errorSummary": undefined,
+     "eventRemediation": [
+       {
+         "action": "No change needed.",
+         "event": "issues",
+         "ok": true,
+       },
+       {
+         "action": "No change needed.",
+         "event": "issue_comment",
+         "ok": true,
+       },
+       {
+         "action": "No change needed.",
+         "event": "pull_request",
+         "ok": true,
+       },
+       {
+         "action": "Subscribe to the pull_request_review webhook event.",
+         "event": "pull_request_review",
+         "ok": false,
+       },
+       {
+         "action": "No change needed.",
+         "event": "repository",
+         "ok": true,
+       },
+       {
+         "action": "Subscribe to the check_run webhook event.",
+         "event": "check_run",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the check_suite webhook event.",
+         "event": "check_suite",
+         "ok": false,
+       },
+     ],
+     "events": [
+       "issues",
+       "issue_comment",
+       "pull_request",
+       "repository",
+       "installation_repositories",
+     ],
      "installationId": 123,
-     "missingEvents": [],
+     "installedReposCount": 0,
+     "missingEvents": [
+       "pull_request_review",
+       "check_run",
+       "check_suite",
+     ],
      "missingPermissions": [],
-     "status": "healthy",
+     "optionalPermissions": {
+       "checks": "write",
+     },
+     "optionalVisibleEvents": [
+       "installation_target",
+       "installation_repositories",
+     ],
+     "permissionRemediation": [
+       {
+         "action": "No change needed.",
+         "currentAccess": "read",
+         "ok": true,
+         "permission": "metadata",
+         "requiredAccess": "read",
+       },
+       {
+         "action": "No change needed.",
+         "currentAccess": "write",
+         "ok": true,
+         "permission": "pull_requests",
+         "requiredAccess": "read",
+       },
+       {
+         "action": "No change needed.",
+         "currentAccess": "write",
+         "ok": true,
+         "permission": "issues",
+         "requiredAccess": "write",
+       },
+     ],
+     "permissions": {
+       "checks": "write",
+       "issues": "write",
+       "metadata": "read",
+       "pull_requests": "write",
+     },
+     "registeredInstalledCount": 0,
+     "repairSteps": [
+       "Update the GitHub App permissions and subscribed events.",
+       "Approve the changed permissions or reinstall the app on the target account.",
+       "Run refresh-installation-health after GitHub sends the updated installation payload.",
+       "Recheck /v1/readiness and this installation health endpoint.",
+     ],
+     "repositorySelection": "selected",
+     "requiredEvents": [
+       "issues",
+       "issue_comment",
+       "pull_request",
+       "pull_request_review",
+       "repository",
+       "check_run",
+       "check_suite",
+     ],
+     "requiredPermissions": {
+       "issues": "write",
+       "metadata": "read",
+       "pull_requests": "read",
+     },
+     "status": "needs_attention",
    },
  ]

 ❯ test/unit/backfill.test.ts:1369:37
test/unit/setup-wizard-docs-parity.test.ts > self-host GitHub App manifest <-> docs parity (#2542) > the docs page's events sentence names every buildManifest default_event, and only those
Stack Traces | 0.0284s run time
AssertionError: expected [ 'check run', 'check suite', …(5) ] to deeply equal [ 'check run', 'check suite', …(10) ]

- Expected
+ Received

  [
    "check run",
    "check suite",
-   "deployment status",
-   "issue comment",
    "issues",
    "pull request",
    "pull request review",
-   "pull request review thread",
    "push",
-   "repository",
    "status",
-   "workflow run",
  ]

 ❯ test/unit/setup-wizard-docs-parity.test.ts:65:31
test/unit/backfill.test.ts > GitHub backfill > reports installation health from stored permissions and events
Stack Traces | 0.0968s run time
AssertionError: expected { installationId: 123, …(19) } to match object { status: 'needs_attention', …(3) }
(22 matching properties omitted from actual)

- Expected
+ Received

@@ -1,10 +1,13 @@
  {
    "missingEvents": [
      "issues",
      "issue_comment",
+     "pull_request_review",
      "repository",
+     "check_run",
+     "check_suite",
    ],
    "missingPermissions": [
      "pull_requests",
      "issues",
    ],

 ❯ test/unit/backfill.test.ts:686:37
test/unit/backfill.test.ts > GitHub backfill > uses installation source for queued segment jobs and sparse live installation fallback metadata
Stack Traces | 0.102s run time
AssertionError: expected [ { installationId: 123, …(19) } ] to deeply equal ArrayContaining{…}

- Expected
+ Received

- ArrayContaining [
-   ObjectContaining {
+ [
+   {
      "accountLogin": "JSONbored",
+     "authMode": "local",
+     "checkedAt": "2026-07-27T05:50:03.888Z",
+     "errorSummary": undefined,
+     "eventRemediation": [
+       {
+         "action": "Subscribe to the issues webhook event.",
+         "event": "issues",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the issue_comment webhook event.",
+         "event": "issue_comment",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the pull_request webhook event.",
+         "event": "pull_request",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the pull_request_review webhook event.",
+         "event": "pull_request_review",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the repository webhook event.",
+         "event": "repository",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the check_run webhook event.",
+         "event": "check_run",
+         "ok": false,
+       },
+       {
+         "action": "Subscribe to the check_suite webhook event.",
+         "event": "check_suite",
+         "ok": false,
+       },
+     ],
      "events": [],
      "installationId": 123,
+     "installedReposCount": 1,
      "missingEvents": [
        "issues",
        "issue_comment",
        "pull_request",
+       "pull_request_review",
        "repository",
+       "check_run",
+       "check_suite",
      ],
      "missingPermissions": [
        "metadata",
        "pull_requests",
        "issues",
      ],
+     "optionalPermissions": {
+       "checks": "write",
+     },
+     "optionalVisibleEvents": [
+       "installation_target",
+       "installation_repositories",
+     ],
+     "permissionRemediation": [
+       {
+         "action": "Set repository permission metadata to read.",
+         "currentAccess": "missing",
+         "ok": false,
+         "permission": "metadata",
+         "requiredAccess": "read",
+       },
+       {
+         "action": "Set repository permission pull_requests to read.",
+         "currentAccess": "missing",
+         "ok": false,
+         "permission": "pull_requests",
+         "requiredAccess": "read",
+       },
+       {
+         "action": "Set repository permission issues to write.",
+         "currentAccess": "missing",
+         "ok": false,
+         "permission": "issues",
+         "requiredAccess": "write",
+       },
+     ],
      "permissions": {},
+     "registeredInstalledCount": 1,
+     "repairSteps": [
+       "Update the GitHub App permissions and subscribed events.",
+       "Approve the changed permissions or reinstall the app on the target account.",
+       "Run refresh-installation-health after GitHub sends the updated installation payload.",
+       "Recheck /v1/readiness and this installation health endpoint.",
+     ],
      "repositorySelection": "selected",
+     "requiredEvents": [
+       "issues",
+       "issue_comment",
+       "pull_request",
+       "pull_request_review",
+       "repository",
+       "check_run",
+       "check_suite",
+     ],
+     "requiredPermissions": {
+       "issues": "write",
+       "metadata": "read",
+       "pull_requests": "read",
+     },
      "status": "needs_attention",
    },
  ]

 ❯ test/unit/backfill.test.ts:4724:34
test/unit/queue-4.test.ts > queue processors > debounces noisy PR events without publishing public surfaces
Stack Traces | 0.118s run time
AssertionError: expected 3 to be +0 // Object.is equality

- Expected
+ Received

- 0
+ 3

 ❯ test/unit/queue-4.test.ts:2745:25
test/integration/api.test.ts > api routes > serves installation repair diagnostics and refreshes installation health
Stack Traces | 0.214s run time
AssertionError: expected { …(12) } to match object { refreshed: true, …(2) }
(37 matching properties omitted from actual)

- Expected
+ Received

@@ -1,10 +1,14 @@
  {
    "installation": {
-     "missingEvents": [],
+     "missingEvents": [
+       "pull_request_review",
+       "check_run",
+       "check_suite",
+     ],
      "missingPermissions": [],
-     "status": "healthy",
+     "status": "needs_attention",
    },
    "refreshed": true,
    "requiredPermissions": {
      "checks": "write",
      "issues": "write",

 ❯ test/integration/api.test.ts:2364:35
test/unit/check-branding-drift-script.test.ts > check-branding-drift script (real repo state) > the committed baseline matches the real current repo state (regression guard)
Stack Traces | 0.347s run time
Error: Command failed: .../hostedtoolcache/node/22.23.1.../x64/bin/node --experimental-strip-types scripts/check-branding-drift.ts
Branding-drift check found 1 issue(s):
src/review/predicted-gate-agreement.ts: "gittensory" mentions increased from 0 to 1 -- looks like new branding drift, not an intentional historical reference. If it genuinely belongs (e.g. a permanent Sentry ticket ID or a stable comment-marker already posted to live PRs), run `npm run branding-drift:update` and commit the regenerated baseline.

 ❯ test/unit/check-branding-drift-script.test.ts:143:20

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { status: 1, signal: null, output: [ null, '', 'Branding-drift check found 1 issue(s):\nsrc/review/predicted-gate-agreement.ts: "gittensory" mentions increased from 0 to 1 -- looks like new branding drift, not an intentional historical reference. If it genuinely belongs (e.g. a permanent Sentry ticket ID or a stable comment-marker already posted to live PRs), run `npm run branding-drift:update` and commit the regenerated baseline.\n' ], pid: 20550, stdout: '', stderr: 'Branding-drift check found 1 issue(s):\nsrc/review/predicted-gate-agreement.ts: "gittensory" mentions increased from 0 to 1 -- looks like new branding drift, not an intentional historical reference. If it genuinely belongs (e.g. a permanent Sentry ticket ID or a stable comment-marker already posted to live PRs), run `npm run branding-drift:update` and commit the regenerated baseline.\n' }

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment