Skip to content

refactor(move): relocate replay resume and failure responses - #3030

Merged
thymikee merged 2 commits into
codex/2663-replay-adapter-leavesfrom
codex/2663-replay-resume-failure
Sep 28, 2026
Merged

thymikee merged 2 commits into
codex/2663-replay-adapter-leavesfrom
codex/2663-replay-resume-failure

Conversation

@thymikee

@thymikee thymikee commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Move replay resume decisions and failure-response construction unchanged into @agent-device/replay-port; daemon callers use package exports.
  • Move nine pure unit cases beside their sources while keeping daemon coordinator and runtime integration cases in place. Update the structural ownership test to inspect the new paths.
  • Declare the ad-replay and session-journal dependencies and the latter's command-registry build flag declaration. This stack layer touches 14 files; rename-aware diff is 246 additions and 220 deletions.

Part of #2663. This PR depends on #3028; the remaining daemon replay tree is being migrated in subsequent stack layers.

Validation

  • Exact head 710c9c44d1ee1b74a80252fc16367c1abd823b54: pnpm check:affected --run passed (613 related files, 5,419 tests; wire compatibility and provider integration passed).
  • pnpm check:quick, pnpm check:layering, pnpm check:fallow --base origin/main, and focused 28 tests passed.
  • GitHub Coverage and Integration Tests remain CI-owned. Live .ad and Maestro characterization will run on the completed package migration commit.

@thymikee
thymikee added this pull request to stack #3020 September 28, 2026 12:19
@thymikee thymikee changed the title codex/2663 replay resume failure refactor(move): relocate replay resume and failure responses Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.87 MB 4.87 MB 0 B
Package (unpacked) 4.87 MB 4.87 MB 0 B
Package (download) 1.46 MB 1.46 MB +2 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 22.5 ms 22.3 ms -0.2 ms
CLI --help 63.9 ms 65.7 ms +1.7 ms

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-3030/

Built to branch gh-pages at 2026-09-28 17:00 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/replay-port/src/daemon-port/__tests__/session-replay-runtime-failure-response.test.ts">

<violation number="1" location="packages/replay-port/src/daemon-port/__tests__/session-replay-runtime-failure-response.test.ts:47">
P3: The `reas<var:MODE>` assertion looks like garbage text without context. It is a real guard — the detail key `reason` ends in the substring `on`, which is MODE's scrub value, so if `scrubReplayVarValues` were ever applied to the details object's keys, `reason` would become `reas<var:MODE>` — but the connection is invisible and silently decays if anyone changes MODE's value in the fixture. Add a comment explaining the guard (and that it depends on `reason` containing the scrub value `on`).</violation>
</file>

<file name="src/daemon/__tests__/replay-coordinator-ownership.test.ts">

<violation number="1" location="src/daemon/__tests__/replay-coordinator-ownership.test.ts:35">
P3: `resolveRelativeTarget` returns null for any specifier that doesn't normalize under `src/` (line 75), so the chain checks don't understand the package-rooted files added here. For the moved files, the `the divergence-report chain rejects unresolved dynamic imports` assertion (via `unresolvedDynamicImportSites`) classes any legitimate dynamic import — an in-package one (`./replay-session-binding.ts`) or a workspace one (`@agent-device/...`) — as "unresolved" and fails; today the files happen to contain none, so the test passes only vacuously. Conversely, the coordinator-factory/SessionStore checks can only fire on cross-tree relative specifiers, so workspace-alias imports are invisible to them. Resolve package-rooted targets into a non-null `target` so the dynamic-import guard discriminates real unresolved specifiers from in-repo ones, while keeping `src/`-rooted resolution unchanged so the cross-tree SessionStore/coordinator checks keep working.</violation>

<violation number="2" location="src/daemon/__tests__/replay-coordinator-ownership.test.ts:35">
P2: These two chain files moved out of `src`, but `listProductionSourceFiles()` walks only the `src` root (line 54), so the `createReplayCoordinator has exactly one production call site` and "must not be re-exported" assertions no longer scan them — they were covered while located at `src/daemon/replay/internal/`. The chain-level "never imports the coordinator factory" test can't compensate: it only matches direct named value imports (`importsValueBinding` requires an `ImportSpecifier` bound to `createReplayCoordinator`), so a namespace import, `export *` re-export, or dynamic import of the coordinator from either moved file passes every assertion in this file. The files this diff deliberately places in the protected chain are now the only chain members outside the single-construction-site guarantee. Add `packages/replay-port/src/daemon-port` to the `roots` in `listProductionSourceFiles` so they stay under the same construction-site and re-export scans as before the move.</violation>
</file>

<file name="packages/replay-port/src/daemon-port/__tests__/session-replay-resume.test.ts">

<violation number="1" location="packages/replay-port/src/daemon-port/__tests__/session-replay-resume.test.ts:27">
P3: These mid-file comment blocks narrate implementation decisions and review history (#1262 "re-review", ADR 0012 rationales) between tests, which AGENTS.md's comment rule explicitly discourages ("No control-flow narration or review history in comments; encode invariants in names/types/boundaries/tests"). The tests already encode each boundary case in its name and assertions; keep at most a one-line note where the boundary is non-obvious (e.g., the empty-tail-without-session rule).</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

/** The divergence-report chain: never a second `ReplayCoordinator`, never a bare `SessionStore`. */
const DIVERGENCE_CHAIN_FILES = [
'src/daemon/replay/internal/session-replay-resume.ts',
'packages/replay-port/src/daemon-port/session-replay-resume.ts',

@cubic-dev-ai cubic-dev-ai Bot Sep 28, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: These two chain files moved out of src, but listProductionSourceFiles() walks only the src root (line 54), so the createReplayCoordinator has exactly one production call site and "must not be re-exported" assertions no longer scan them — they were covered while located at src/daemon/replay/internal/. The chain-level "never imports the coordinator factory" test can't compensate: it only matches direct named value imports (importsValueBinding requires an ImportSpecifier bound to createReplayCoordinator), so a namespace import, export * re-export, or dynamic import of the coordinator from either moved file passes every assertion in this file. The files this diff deliberately places in the protected chain are now the only chain members outside the single-construction-site guarantee. Add packages/replay-port/src/daemon-port to the roots in listProductionSourceFiles so they stay under the same construction-site and re-export scans as before the move.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/daemon/__tests__/replay-coordinator-ownership.test.ts, line 35:

<comment>These two chain files moved out of `src`, but `listProductionSourceFiles()` walks only the `src` root (line 54), so the `createReplayCoordinator has exactly one production call site` and "must not be re-exported" assertions no longer scan them — they were covered while located at `src/daemon/replay/internal/`. The chain-level "never imports the coordinator factory" test can't compensate: it only matches direct named value imports (`importsValueBinding` requires an `ImportSpecifier` bound to `createReplayCoordinator`), so a namespace import, `export *` re-export, or dynamic import of the coordinator from either moved file passes every assertion in this file. The files this diff deliberately places in the protected chain are now the only chain members outside the single-construction-site guarantee. Add `packages/replay-port/src/daemon-port` to the `roots` in `listProductionSourceFiles` so they stay under the same construction-site and re-export scans as before the move.</comment>

<file context>
@@ -32,11 +32,11 @@ const RUNTIME_FILE = 'src/daemon/handlers/session-replay-command.ts';
 /** The divergence-report chain: never a second `ReplayCoordinator`, never a bare `SessionStore`. */
 const DIVERGENCE_CHAIN_FILES = [
-  'src/daemon/replay/internal/session-replay-resume.ts',
+  'packages/replay-port/src/daemon-port/session-replay-resume.ts',
   'src/daemon/replay/internal/session-replay-divergence.ts',
   'src/daemon/replay/internal/session-replay-target-verification.ts',
</file context>
Fix with cubic

artifactPaths: [artifactPath],
});
expect(response.error.details).toHaveProperty('reason');
expect(response.error.details).not.toHaveProperty('reas<var:MODE>');

@cubic-dev-ai cubic-dev-ai Bot Sep 28, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The reas<var:MODE> assertion looks like garbage text without context. It is a real guard — the detail key reason ends in the substring on, which is MODE's scrub value, so if scrubReplayVarValues were ever applied to the details object's keys, reason would become reas<var:MODE> — but the connection is invisible and silently decays if anyone changes MODE's value in the fixture. Add a comment explaining the guard (and that it depends on reason containing the scrub value on).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/replay-port/src/daemon-port/__tests__/session-replay-runtime-failure-response.test.ts, line 47:

<comment>The `reas<var:MODE>` assertion looks like garbage text without context. It is a real guard — the detail key `reason` ends in the substring `on`, which is MODE's scrub value, so if `scrubReplayVarValues` were ever applied to the details object's keys, `reason` would become `reas<var:MODE>` — but the connection is invisible and silently decays if anyone changes MODE's value in the fixture. Add a comment explaining the guard (and that it depends on `reason` containing the scrub value `on`).</comment>

<file context>
@@ -0,0 +1,48 @@
+    artifactPaths: [artifactPath],
+  });
+  expect(response.error.details).toHaveProperty('reason');
+  expect(response.error.details).not.toHaveProperty('reas<var:MODE>');
+});
</file context>
Suggested change
expect(response.error.details).not.toHaveProperty('reas<var:MODE>');
// Guard: details keys never pass through scrubReplayVarValues. The key
// 'reason' ends with MODE's scrub value ('on'), so key-level scrubbing would
// surface here as a 'reas<var:MODE>' property; keep that only if MODE's value stays 'on'.
expect(response.error.details).not.toHaveProperty('reas<var:MODE>');
Fix with cubic

/** The divergence-report chain: never a second `ReplayCoordinator`, never a bare `SessionStore`. */
const DIVERGENCE_CHAIN_FILES = [
'src/daemon/replay/internal/session-replay-resume.ts',
'packages/replay-port/src/daemon-port/session-replay-resume.ts',

@cubic-dev-ai cubic-dev-ai Bot Sep 28, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: resolveRelativeTarget returns null for any specifier that doesn't normalize under src/ (line 75), so the chain checks don't understand the package-rooted files added here. For the moved files, the the divergence-report chain rejects unresolved dynamic imports assertion (via unresolvedDynamicImportSites) classes any legitimate dynamic import — an in-package one (./replay-session-binding.ts) or a workspace one (@agent-device/...) — as "unresolved" and fails; today the files happen to contain none, so the test passes only vacuously. Conversely, the coordinator-factory/SessionStore checks can only fire on cross-tree relative specifiers, so workspace-alias imports are invisible to them. Resolve package-rooted targets into a non-null target so the dynamic-import guard discriminates real unresolved specifiers from in-repo ones, while keeping src/-rooted resolution unchanged so the cross-tree SessionStore/coordinator checks keep working.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/daemon/__tests__/replay-coordinator-ownership.test.ts, line 35:

<comment>`resolveRelativeTarget` returns null for any specifier that doesn't normalize under `src/` (line 75), so the chain checks don't understand the package-rooted files added here. For the moved files, the `the divergence-report chain rejects unresolved dynamic imports` assertion (via `unresolvedDynamicImportSites`) classes any legitimate dynamic import — an in-package one (`./replay-session-binding.ts`) or a workspace one (`@agent-device/...`) — as "unresolved" and fails; today the files happen to contain none, so the test passes only vacuously. Conversely, the coordinator-factory/SessionStore checks can only fire on cross-tree relative specifiers, so workspace-alias imports are invisible to them. Resolve package-rooted targets into a non-null `target` so the dynamic-import guard discriminates real unresolved specifiers from in-repo ones, while keeping `src/`-rooted resolution unchanged so the cross-tree SessionStore/coordinator checks keep working.</comment>

<file context>
@@ -32,11 +32,11 @@ const RUNTIME_FILE = 'src/daemon/handlers/session-replay-command.ts';
 /** The divergence-report chain: never a second `ReplayCoordinator`, never a bare `SessionStore`. */
 const DIVERGENCE_CHAIN_FILES = [
-  'src/daemon/replay/internal/session-replay-resume.ts',
+  'packages/replay-port/src/daemon-port/session-replay-resume.ts',
   'src/daemon/replay/internal/session-replay-divergence.ts',
   'src/daemon/replay/internal/session-replay-target-verification.ts',
</file context>
Fix with cubic

});
assert.deepEqual(resume, { allowed: true, from: 2, planDigest: 'abc123' });
});
// --- repairHint 'record-and-heal' shifts `from` to failedIndex + 1 (ADR 0012

@cubic-dev-ai cubic-dev-ai Bot Sep 28, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: These mid-file comment blocks narrate implementation decisions and review history (#1262 "re-review", ADR 0012 rationales) between tests, which AGENTS.md's comment rule explicitly discourages ("No control-flow narration or review history in comments; encode invariants in names/types/boundaries/tests"). The tests already encode each boundary case in its name and assertions; keep at most a one-line note where the boundary is non-obvious (e.g., the empty-tail-without-session rule).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/replay-port/src/daemon-port/__tests__/session-replay-resume.test.ts, line 27:

<comment>These mid-file comment blocks narrate implementation decisions and review history (#1262 "re-review", ADR 0012 rationales) between tests, which AGENTS.md's comment rule explicitly discourages ("No control-flow narration or review history in comments; encode invariants in names/types/boundaries/tests"). The tests already encode each boundary case in its name and assertions; keep at most a one-line note where the boundary is non-obvious (e.g., the empty-tail-without-session rule).</comment>

<file context>
@@ -0,0 +1,172 @@
+  });
+  assert.deepEqual(resume, { allowed: true, from: 2, planDigest: 'abc123' });
+});
+// --- repairHint 'record-and-heal' shifts `from` to failedIndex + 1 (ADR 0012
+// decision 6, R2): the agent already performed the diverged step manually, so
+// resuming AT it would re-diverge on the exact same step. ---
</file context>
Fix with cubic

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 710c9c4. This looks ready for human review: it's a pure move of the replay resume and failure-response modules into packages/replay-port, with no behavior change. CI is green: Integration Tests, Typecheck, Bundle Size, and Repo Guards all pass, and Coverage (job 108923784522) now passes too, exercising the moved code through the unit run. Two Smoke Tests checks (job 108923785116, job 108923784771) are still pending rather than failed, and since this diff only touches packages/replay-port and daemon import paths, there's no reason to expect them to be affected — but if either comes back red touching replay-port resolution, that's worth a follow-up look. I didn't rerun pnpm check:affected myself; this relies on the PR body's reported pass at this head plus the current green checks. No live device validation was done or needed here, since there's no device-facing behavior change. Not blocking: listProductionSourceFiles() in src/daemon/tests/replay-coordinator-ownership.test.ts:54 only walks roots=['src'], so now that session-replay-resume.ts and session-replay-runtime-failure-response.ts live under packages/replay-port/src/daemon-port/, they've dropped out of the single-call-site and no-re-export scan for createReplayCoordinator — worth extending the walked roots to include packages/replay-port/src/daemon-port to restore that coverage, but it can be taken or left.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 28, 2026
@thymikee
thymikee force-pushed the codex/2663-replay-resume-failure branch from 710c9c4 to 25c07af Compare September 28, 2026 16:35
@thymikee
thymikee merged commit 2138bec into main Sep 28, 2026
29 of 40 checks passed
@thymikee
thymikee deleted the codex/2663-replay-resume-failure branch September 28, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant