Skip to content

Commit 6a69554

Browse files
committed
refactor(daemon): fold the step-support fragment back into the engine adapter
A simplicity audit judged session-replay-runtime-step-support.ts a size-target fragment, not a concern boundary: four unrelated concerns, one consumer, and a header comment admitting it existed to satisfy the <300 LOC metric. Folded back; the previously-exported helpers are module-private again; the adapter's honest size is renegotiated from the plan metric (dispatch-narrowing stays extracted — it has one nameable job).
1 parent 2875c05 commit 6a69554

2 files changed

Lines changed: 150 additions & 174 deletions

File tree

src/daemon/handlers/session-replay-runtime-engine-adapter.ts

Lines changed: 150 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
import type { DaemonRequest, DaemonResponse, SessionAction } from '../types.ts';
1+
import type { DaemonInvokeFn, DaemonRequest, DaemonResponse, SessionAction } from '../types.ts';
2+
import type { SessionStore } from '../session-store.ts';
3+
import { errorResponse } from './response.ts';
4+
import { readReplaySelectorDisplayValue } from '../replay-selector-port.ts';
5+
import type { ResponseLevel } from '@agent-device/kernel/contracts';
6+
import type { SnapshotTimingSample } from '@agent-device/contracts/capture';
7+
import { withReplayFailureDiagnostics } from './session-replay-runtime-failure.ts';
8+
import type { ReplayCoordinator } from '../session-replay-coordinator.ts';
29
import { invokeReplayAction } from './session-replay-action-runtime.ts';
3-
import type { AdReplayStepFailure, AdReplayStepRuntime } from '@agent-device/ad-replay';
10+
import type {
11+
AdReplayStepFailure,
12+
AdReplayStepRuntime,
13+
ReplaySelectorPort,
14+
} from '@agent-device/ad-replay';
415
import { collectReplayActionArtifactPaths } from './session-replay-runtime-artifacts.ts';
516
import {
617
applyReplayDispatchGuard,
@@ -19,14 +30,6 @@ import {
1930
resolveTargetVerificationEntry,
2031
type TargetBindingDivergenceContext,
2132
} from './session-replay-target-verification.ts';
22-
import {
23-
asFailedReplayStepResponse,
24-
buildReplayActionFailure,
25-
describeReplayStepValue,
26-
readSessionSnapshotSampleCount,
27-
readSessionSnapshotSamplesSince,
28-
type ReplayStepContext,
29-
} from './session-replay-runtime-step-support.ts';
3033
import type { ReplayTestAttemptStepSink } from '@agent-device/replay-test';
3134

3235
/**
@@ -35,17 +38,16 @@ import type { ReplayTestAttemptStepSink } from '@agent-device/replay-test';
3538
* `createAdReplayStepRuntime`. See that file's `runReplayScriptFile` for the request-level
3639
* orchestration this adapter plugs into.
3740
*
38-
* #1555 structural-quality review ("shrink the runtime adapter toward the
39-
* plan's <300 LOC metric"): the wire-narrowing concern (guard threading,
40-
* `details` bag -> typed evidence, dispatch-failure classification) moved to
41-
* `session-replay-dispatch-narrowing.ts`; `ReplayStepContext` and the
42-
* failure-wrapping/diagnostics support helpers moved to
43-
* `session-replay-runtime-step-support.ts` (re-exporting `ReplayStepContext`
44-
* by name so this file's own importers see no path change). This file is
45-
* left with exactly the `createAdReplayStepRuntime` factory and the small
46-
* closures only it needs.
41+
* The wire-narrowing concern (guard threading, `details` bag -> typed
42+
* evidence, dispatch-failure classification) lives in
43+
* `session-replay-dispatch-narrowing.ts` — a real seam with one nameable job.
44+
* Everything else the factory's capabilities delegate to (the step context
45+
* shape, failure wrapping, progress display, diagnostics sampling) lives
46+
* below in this file: a briefly-extracted `-step-support` module was folded
47+
* back after review judged it a size-target fragment, not a concern boundary
48+
* — this adapter's honest size is ~430 lines, renegotiated from the plan's
49+
* <300 metric on #1478.
4750
*/
48-
export type { ReplayStepContext } from './session-replay-runtime-step-support.ts';
4951

5052
/**
5153
* #1478 P5 stage C2b (narrowed further by the #1555 review's neutral-outcomes
@@ -312,3 +314,131 @@ export function createAdReplayStepRuntime(params: {
312314
};
313315
return { runtime, readLastResponse: () => lastResponse };
314316
}
317+
318+
/**
319+
* Per-run invariants for a single replay step (ADR 0012 step 4 verify +
320+
* dispatch + guard). No `${VAR}` scope here (#1555 review P1, "move variable
321+
* semantics/planning behind the replay entrypoint") — the engine
322+
* (`runAdReplay`) builds and owns it; the adapter never resolves an action
323+
* or reads a scope value itself.
324+
*/
325+
export type ReplayStepContext = {
326+
replayReq: DaemonRequest;
327+
sessionName: string;
328+
sessionStore: SessionStore;
329+
logPath: string;
330+
resolved: string;
331+
actions: SessionAction[];
332+
actionLines: number[];
333+
actionSourcePaths: (string | undefined)[] | undefined;
334+
planDigest: string;
335+
actionTracePath: string | undefined;
336+
responseLevel: ResponseLevel | undefined;
337+
invoke: DaemonInvokeFn;
338+
signal: AbortSignal | undefined;
339+
/** #1478 P4b: the one locked gateway to this request's repair transaction. */
340+
coordinator: ReplayCoordinator;
341+
/** #1478 P5 stage C: the one selector-port instance this request threads through the divergence-report chain. */
342+
port: ReplaySelectorPort;
343+
};
344+
345+
/**
346+
* `runAdReplay` only ever calls `handleActionFailure` right after a step's
347+
* dispatch/build-failure capability reported `status: 'failed'`, and every
348+
* one of those capabilities records its response in the adapter's
349+
* `lastResponse` side-map before returning — so this narrowing cannot
350+
* actually fail in practice. The `COMMAND_FAILED` fallback exists only so
351+
* `buildReplayActionFailure` (which needs a real failed response to wrap)
352+
* stays total if that invariant is ever violated.
353+
*/
354+
function asFailedReplayStepResponse(
355+
response: DaemonResponse | undefined,
356+
): Extract<DaemonResponse, { ok: false }> {
357+
if (response && !response.ok) return response;
358+
return errorResponse(
359+
'COMMAND_FAILED',
360+
'replay step reported failure with no recorded response',
361+
) as Extract<DaemonResponse, { ok: false }>;
362+
}
363+
364+
async function buildReplayActionFailure(
365+
ctx: ReplayStepContext,
366+
req: DaemonRequest,
367+
action: SessionAction,
368+
index: number,
369+
response: Extract<DaemonResponse, { ok: false }>,
370+
artifactPaths: string[],
371+
snapshotDiagnosticSamples: SnapshotTimingSample[],
372+
scrubVars: TargetBindingDivergenceContext['scrubVars'],
373+
): Promise<DaemonResponse> {
374+
const heldResponse = (failure: DaemonResponse): DaemonResponse =>
375+
ctx.coordinator.markSessionHeldIfArmed(failure);
376+
if (isCompleteTargetBindingDivergenceResponse(response)) return heldResponse(response);
377+
return heldResponse(
378+
await withReplayFailureDiagnostics({
379+
response,
380+
action,
381+
index,
382+
replayPath: ctx.resolved,
383+
sourcePath: ctx.actionSourcePaths?.[index] ?? ctx.resolved,
384+
sourceLine: ctx.actionLines[index] ?? 1,
385+
artifactPaths,
386+
snapshotDiagnosticSamples,
387+
scrubVars,
388+
req,
389+
sessionName: ctx.sessionName,
390+
sessionStore: ctx.sessionStore,
391+
resumeStamper: ctx.coordinator.resumeStamper,
392+
logPath: ctx.logPath,
393+
planActions: ctx.actions,
394+
planDigest: ctx.planDigest,
395+
port: ctx.port,
396+
}),
397+
);
398+
}
399+
400+
/**
401+
* A replay-test progress step's display value: the recorded selector's
402+
* label/text/id term value when every alternative agrees on ONE value, else
403+
* `undefined`. Needs `readReplaySelectorDisplayValue`'s private selector AST
404+
* (`replay-selector-port.ts` deliberately keeps it daemon-only — see that
405+
* file's own comment), so this stays daemon-side and is handed to the engine
406+
* loop as the narrow `describeStepValue` capability.
407+
*/
408+
function describeReplayStepValue(action: SessionAction): string | undefined {
409+
const positionals = action.positionals ?? [];
410+
const selectorValue = readReplaySelectorDisplayValue(positionals[0]);
411+
if (selectorValue) return selectorValue;
412+
if (positionals.length === 0) return undefined;
413+
return positionals.join(' ');
414+
}
415+
416+
// ADR 0012 step 4: a target-binding divergence is already a complete, final
417+
// REPLAY_DIVERGENCE built from its own pre-action capture — distinguished from
418+
// an action-failure divergence by its non-`action-failure` kind. Pinned
419+
// daemon-side: it re-inspects the already-projected `DaemonResponse` wire
420+
// shape to decide whether the wire-level diagnostics-augmentation step
421+
// applies, which is daemon/wire authority, not target-binding classification
422+
// itself (that already happened, in `session-replay-target-classification.ts`'s
423+
// `classifyReplayTarget`, called from `classifyPreDispatchTarget`).
424+
function isCompleteTargetBindingDivergenceResponse(response: DaemonResponse): boolean {
425+
if (response.ok || response.error.code !== 'REPLAY_DIVERGENCE') return false;
426+
const divergence = response.error.details?.divergence;
427+
const kind =
428+
divergence && typeof divergence === 'object'
429+
? (divergence as Record<string, unknown>).kind
430+
: undefined;
431+
return typeof kind === 'string' && kind !== 'action-failure';
432+
}
433+
434+
function readSessionSnapshotSampleCount(sessionStore: SessionStore, sessionName: string): number {
435+
return sessionStore.get(sessionName)?.snapshotDiagnostics?.samples.length ?? 0;
436+
}
437+
438+
function readSessionSnapshotSamplesSince(
439+
sessionStore: SessionStore,
440+
sessionName: string,
441+
start: number,
442+
): SnapshotTimingSample[] {
443+
return sessionStore.get(sessionName)?.snapshotDiagnostics?.samples.slice(start) ?? [];
444+
}

src/daemon/handlers/session-replay-runtime-step-support.ts

Lines changed: 0 additions & 154 deletions
This file was deleted.

0 commit comments

Comments
 (0)