refactor(daemon): serialize replay transactions behind a locked coordinator (#1478 P4b) - #1535
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Blocking P4b ownership gap at exact head Please thread the existing request coordinator, or a narrower bound resume-stamp capability, through both divergence paths. Add a focused structural/counterfactual test that fails if either path can construct a coordinator or directly receive Separate blockers/evidence: this stack legitimately depends on open #1532, so retarget/rebase onto latest main after P4a merges. iOS smoke failed in unrelated |
8405c75 to
efa133e
Compare
Fixed: the divergence-report chain no longer manufactures a second coordinatorConfirmed the finding: The capability threadedAdded export type ReplayResumeStamper = Readonly<{
sessionExists(): boolean;
stampCorrectiveWatermark(params: {
resume: ReplayDivergenceResume;
repairHint: ReplayRepairHint;
failedIndex: number;
actions: SessionAction[];
}): void;
}>;
The deleted second-construction path
export function buildAndPersistReplayDivergenceResume(params: {
readonly failedIndex: number;
readonly actions: SessionAction[];
readonly planDigest: string;
readonly repairHint: ReplayRepairHint;
readonly resumeStamper: ReplayResumeStamper;
}): ReplayDivergenceResume {
const resume = buildReplayDivergenceResume({ ..., sessionExists: params.resumeStamper.sessionExists() });
params.resumeStamper.stampCorrectiveWatermark({ resume, ... });
return resume;
}
Exact allowed-importer set (decided and enforced by the new test)
Ownership test:
|
|
Re-reviewed exact head Not ready to label yet:
Code review is clean; |
cb35d97 to
9a381ba
Compare
|
Re-reviewed at |
…inator Adds session-replay-coordinator.ts, a ReplayCoordinator scoped to one locked native .ad replay request, and routes every repair-transaction write session-replay-runtime.ts and session-replay-resume.ts perform through it: arm, demote-for-rerun, mark-complete, hold-on-divergence stamping, the pendingRecordAndHeal corrective watermark (set + clear), and reap-tombstone clearing. Neither file imports session-replay-transaction.ts (P4a's ReplaySessionTransaction) or writes session.pendingRecordAndHeal directly anymore. Adds a minimal immutable ReplaySessionView (repairBoundary, pendingRecordAndHeal) so the three readers this slice touches (preflightReplayAgainstActiveRepair, isRepairArmedTerminalClose, the entry-index resolution in prepareReplayPlan) stop taking mutable SessionState. Close-time sequencing (session-close.ts's platform-close receipt, session-close-script.ts's commit/abort) stays a direct ReplaySessionTransaction caller by design: commit/abort happen at teardown, ordered against platform close and lease release, not during a replay request. Updates the R7 session-state ownership registry: pendingRecordAndHeal moves from session-replay-resume.ts to session-replay-coordinator.ts. The daemon-modularity baseline (writer-owned fields / owner claims) is unchanged. Refs #1478 Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…dinator buildAndPersistReplayDivergenceResume (session-replay-resume.ts) constructed a SECOND ReplayCoordinator from a bare SessionStore + session name, reachable from both divergence paths (session-replay-target-verification.ts and the action-failure chain through session-replay-runtime-failure.ts / session-replay-divergence.ts). That let a lower handler manufacture repair authority by naming a session instead of using the request's own locked coordinator. Adds ReplayResumeStamper: a narrow capability bound to the coordinator runReplayScriptFile already created, exposing only sessionExists() and stampCorrectiveWatermark(). Threads it through ReplayStepContext and the failure-wrapper params into both chains. buildAndPersistReplayDivergenceResume now takes the stamper and holds no SessionStore or coordinator-construction ability at all. Adds src/daemon/__tests__/replay-coordinator-ownership.test.ts, an oxc-parser AST structural test (same approach as scripts/layering/session-state.ts) asserting: createReplayCoordinator has exactly one production call site (session-replay-runtime.ts); none of the five divergence-chain files import the coordinator factory or session-replay-transaction.ts; session-replay-resume.ts holds no session-store.ts import at all; and the other four hold SessionStore only as a type. Verified the test fails on a planted violation of each of the two structurally-distinct invariants (coordinator-construction, SessionStore value-import) and passes once removed. Refs #1478 Co-Authored-By: Claude <noreply@anthropic.com>
9a381ba to
11946b1
Compare
|
Re-reviewed exact head |
|
Targeted live Arm + diverge (held through the coordinator): Corrective press into the held session, then resume: The healed sibling ( Boundary-sliced (R6), the corrective press serialized with its verified target-v1 annotation, the excluded diverged step absent, the synthetic finalize One observation, not a finding: the explicit Environment note for reproducers: run every command of the flow with an explicit shared Generated by Claude Code |
|
Re-reviewed at #1545 reproduces on |
|
#1478 P4b: all use of the P4a replay projection is serialized behind one daemon-owned, request-scoped coordinator.
What this actually is (13 files)
src/daemon/session-replay-coordinator.ts—ReplayCoordinator, created exactly once per locked native.adreplay request inrunReplayScriptFile. It owns every repair-transaction write on the replay path: per-step arming (boundary stamps once, explicit<out>wins over the healed sibling),--fromdemotion, completion, divergence-hold stamping (markSessionHeldIfArmed), tombstone clear, and thependingRecordAndHealcorrective watermark (set + clear, moved bodily fromsession-replay-resume.ts; the R7 ownership row moved with it).ReplayResumeStamper— the narrow bound capability (sessionExists(),stampCorrectiveWatermark()) threaded from that single coordinator throughReplayStepContextand the failure-wrapper params into both divergence routes (target-verification and the action-failure chain).buildAndPersistReplayDivergenceResumetakes the stamper and can no longer construct a coordinator or receiveSessionStore+ session name — the review's blocking finding, closed atcb35d978.ReplaySessionView— minimal immutable read projection (repairBoundary,pendingRecordAndHeal) for the read sites this slice touches.src/daemon/__tests__/replay-coordinator-ownership.test.ts— the structural test the review asked for: oxc-parser AST scan assertingcreateReplayCoordinatorhas exactly one production call site and that none of the five divergence-chain files imports the factory,session-replay-transaction.ts, orSessionStore(value-level). Plant-proven in both directions.Deliberate boundary: close-time sequencing (
session-close.tsreceipts,session-close-script.tscommit/abort) stays a directsession-replay-transaction.tscaller — teardown is a different capability with its own ordering, not part of the locked replay request. General lifecycle read-only callers (store, recorder, writer, idle-reap) likewise stay direct readers.Validation
check:layering(R7/R10 baselines unchanged by the rebase; the ownership row move was in the original commit), fallow zero at stack scope, full suite green.range-diff).replay --save-scriptrepair evidence at exact head11946b1e5— see the evidence comment below: full arm → diverge (held) → corrective →--fromresume → commit loop on a real emulator, healed.adwith target-v1 annotation andheal-completesentinel.