Skip to content

orb(replay): an unrecorded Math.random() draw turns closes into holds — two identical records, different actions, and replay cannot see it #9135

Description

@JSONbored

Problem

#8838 claims the model call is "the sole recorded nondeterministic input" to a decision. It is not: an
unrecorded Math.random() draw can convert an auto-close into a hold, and the decision record's pinned
action hides the divergence.

maybeApplyCloseAuditHoldout (src/review/close-audit-holdout.ts:78):

const draw = (input.rng ?? Math.random)();
if (draw >= epsilonPct / 100) return input.planned;

It is applied to the final post-breaker plan at src/queue/processors.ts:3351, i.e. after the gate
and before the record is built at :3426. The draw is recorded nowhere — not in DecisionRecord (no
field for it) and not in DecisionReplayInput (src/review/decision-replay.ts:26-33 holds only
findings, policy, policyCloseKind, evaluated).

Consequently two records with identical configDigest, identical reasonCode, and byte-identical
replay_json
can carry action: "close" and action: "hold". replayDecision reports
pinnedAction: record.action (:92) and re-derives nothing about it, so the divergence is invisible by
construction.

Live, not latent

decision_audit_holdout audit events on edge-nl-01 (2026-07-26): 7. The ε-holdout is running in
production.

Other unrecorded decision-time inputs

The same gap covers everything the plan consumes but nothing records: readUntrustworthyRuleCodes(env)
(live DB, processors.ts:3335), applyPrecisionBreakers breaker state (live DB), the live CI aggregate,
and isGlobalAgentPause / isGlobalAgentFrozen (env).

Impact

Stated plainly: replay today verifies that the pure evaluator still returns the same conclusion from
inputs recorded by that same evaluator call. It cannot verify why the bot did what it did — which is
the only question a contributor or an external evaluator actually asks. "Deterministic replay" is
accurate for the gate-evaluation stage and inaccurate for the action stage.

Two contributors, same repo, same head state, same clause — one closed, one held, purely on an RNG draw,
with identical records. Contributor A asks why they were closed and B was not, and the trust artifact
cannot answer.

Dedup

Adjacent to #9028 (Replay v2: re-query action-match mode + wall-clock capture), which proposes
replaying the action and capturing wall-clock for staleness rules. It does not address an unrecorded RNG
draw — no amount of re-query can reconstruct it; the seed must be persisted at decision time.

Requirements

  • Persist the holdout draw in the replay input, and add a divertedByHoldout boolean to DecisionRecord
    so a hold that was really a close is legible on its face.
  • Derive the draw from a recorded seed — e.g. HMAC(instanceSecret, recordId) — rather than
    Math.random(), so it is reproducible from the record without storing raw entropy and remains
    unpredictable to a contributor trying to dodge the holdout.
  • Record the other decision-time inputs named above, or state explicitly in the replay contract that they
    are outside it. An honest narrower claim beats an unqualified broad one.
  • Update trust: deterministic replay harness with the model call as the sole recorded nondeterministic input #8838's claim wherever it is published.

Test Coverage Requirements

99%+ patch coverage, branch-counted. Both arms of the holdout, plus a test that a replayed diverted
decision reports the divergence rather than silently matching.

Links & Resources

maintainer-only — replay/trust contract.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions