Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions fallow-baselines/health.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@
"count": 1
}
},
"src/daemon/handlers/__tests__/session-test-runner.test.ts": {
"src/daemon/replay/internal/__tests__/session-test-runner.test.ts": {
"crap_high": {
"count": 1
},
Expand Down Expand Up @@ -559,7 +559,7 @@
"src/daemon/client/daemon-client.ts:high impact",
"src/cli/parser/args.ts:high impact",
"src/daemon/lease-context.ts:high impact",
"src/daemon/handlers/session-replay-heal.ts:high impact",
"src/daemon/replay/internal/session-replay-heal.ts:high impact",
"src/utils/output.ts:high impact",
"src/daemon/context.ts:high impact",
"src/daemon/handlers/session.ts:complexity",
Expand Down Expand Up @@ -590,7 +590,7 @@
"packages/platform-android/src/settings.ts:complexity",
"src/snapshot/snapshot-presentation/text-surface.ts:high impact",
"src/daemon/handlers/session-test-sharding.ts:high impact",
"src/daemon/handlers/session-replay-runtime.ts:complexity",
"src/daemon/replay/internal/native-command.ts:complexity",
"packages/platform-apple/src/core/debug-symbols/utils.ts:high impact",
"packages/platform-linux/src/snapshot.ts:high impact",
"src/core/interaction-targeting.ts:high impact",
Expand All @@ -605,7 +605,7 @@
"src/utils/keyed-lock.ts:high impact",
"src/daemon/request-progress-protocol.ts:high impact",
"src/cli/replay-test/reporters/format.ts:high impact",
"src/daemon/handlers/session-test-infrastructure.ts:high impact",
"src/daemon/replay/internal/session-test-infrastructure.ts:high impact",
"src/daemon/handlers/session-test-artifacts.ts:high impact",
"packages/platform-android/src/app-parsers.ts:high impact",
"src/daemon/server/http-server.ts:complexity",
Expand Down
4 changes: 3 additions & 1 deletion oxlint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ export default defineConfig({
},
},
{
files: ['src/daemon/handlers/__tests__/session-replay-target-verification-runtime.test.ts'],
files: [
'src/daemon/replay/internal/__tests__/session-replay-target-verification-runtime.test.ts',
],
rules: {
'unicorn/prefer-string-raw': 'off',
},
Expand Down
6 changes: 3 additions & 3 deletions packages/ad-replay/src/internal/__tests__/step-loop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import type { TargetAnnotationV1 } from '@agent-device/contracts/replay';
* terminal-close suppression) is engine-private — never re-exported by the
* façade (`packages/ad-replay/src/index.ts`) — so these tests exercise it
* only through `runAdReplay` itself, the same way the daemon's own
* `session-replay-runtime.ts` (`runReplayScriptSource`) does. The equivalent
* daemon-level assertions (full `SessionStore`/`runReplayScriptSource` round
* `src/daemon/replay/internal/native-command.ts` (`runReplayCommand`) does. The equivalent
* daemon-level assertions (full `SessionStore`/`runReplayCommand` round
* trip, including the `--keep-session` live-session postcondition) live in
* `src/daemon/handlers/__tests__/session-replay-runtime-keep-session.test.ts`
* `src/daemon/replay/internal/__tests__/session-replay-runtime-keep-session.test.ts`
* (renamed from `session-replay-terminal-lifecycle.test.ts` by the #1555
* structural-quality review — see that file's own header for the rationale);
* this file covers the SAME suppression decision at the cheaper,
Expand Down
2 changes: 1 addition & 1 deletion packages/ad-replay/src/internal/inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export type AdReplayDigestFlags = Readonly<{ platform?: string; target?: string
* `AppError('INVALID_ARGS', …)` for the one source format `.ad` replay no
* longer accepts — a legacy JSON replay payload — matching the daemon's
* prior explicit rejection exactly. Callers do not need to check for this
* case separately: `runReplayScriptSource`'s top-level catch (`asAppError`)
* case separately: `runReplayCommand`'s top-level catch (`asAppError`)
* maps a thrown `AppError` straight to the same `errorResponse` the old
* explicit branch built, so this is not a behavior change, only where the
* check lives.
Expand Down
6 changes: 3 additions & 3 deletions packages/ad-replay/src/internal/step-loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {

/**
* #1478 P5 stage C2b: the `.ad` step-loop ENGINE policy, split out of
* `session-replay-runtime.ts`'s `executeReplayActions` /
* `src/daemon/replay/internal/native-command.ts`'s replay orchestration /
* `resolveReplayStepResponse` / `buildReplayActionFailure`. Everything that
* touches a real device, a snapshot, `SessionStore`, or the P4b repair
* coordinator is daemon authority and stays behind the narrow
Expand All @@ -36,7 +36,7 @@ import type {
*
* #1554 fold-in (rebase onto main's `replay --keep-session`): main grew a
* terminal-close-suppression decision independently, daemon-side, as
* `session-replay-terminal-lifecycle.ts`'s `resolveSuppressedTerminalCloseIndex`
* `runAdReplay`'s `resolveSuppressedTerminalCloseIndex`
* / `countExecutedReplayActions`, generalizing the repair-only physical-last-
* index check this module already had (`isRepairArmedTerminalCloseAction`) to
* "terminal among EXECUTABLE actions" and adding `--keep-session` as a second
Expand Down Expand Up @@ -71,7 +71,7 @@ import type {
*
* #1478 P5 follow-up (one daemon-owned artifact ledger): artifact-path
* accumulation used to be DOUBLE-WRITTEN — `dispatchStep` added each step's
* entries to the daemon's own `Set` (`runReplayScriptSource`'s, read by its
* entries to the daemon's own `Set` (`runReplayCommand`'s, read by its
* catch block so a mid-loop throw still reports what was collected) AND
* returned them for this loop to add to a second `Set` of its own. Two
* mutable collections, kept in sync by hand, with no single owner. The
Expand Down
2 changes: 1 addition & 1 deletion packages/ad-replay/src/internal/target-verification.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* #1478 P5 stage C2a: the target-verification ENGINE policy — moved verbatim
* out of `src/daemon/handlers/session-replay-target-verification.ts`, which
* out of `src/daemon/replay/internal/session-replay-target-verification.ts`, which
* keeps the DAEMON-AUTHORITY half (capture, `SessionStore`, resume stamping,
* wire projection into `DaemonResponse`). This module decides, over already-
* available plain values, whether/how a recorded target-binding annotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
// `packages/ad-script/src/internal/__tests__/target-annotation-serde.test.ts`.
// This file covers the local-identity + ancestry-prefix matching primitives.
// The record/replay-shared CLASSIFICATION core built on top of them
// (`classifyTargetBindingMatch`) is engine-owned policy and stays in
// `@agent-device/ad-replay`'s `target-identity.ts` (#1478 P5 review).
// (`classifyTargetBindingMatch`) is shared script vocabulary and stays in
// `@agent-device/ad-script`'s `target-annotation-classification.ts` (#1478 P5 review).

// ---------------------------------------------------------------------------
// Leaf-anchored ancestry prefix matching: root-side truncation + inserted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* than script vocabulary. In practice its only real consumers were the
* daemon's RECORD-time self-check (`src/daemon/session-target-evidence.ts`)
* and its REPLAY-time classification wrapper
* (`src/daemon/handlers/session-replay-target-classification.ts`) — both
* (`src/daemon/replay/internal/session-replay-target-classification.ts`) — both
* daemon files, neither reachable through `inspectAdReplay`/`runAdReplay`.
* It interprets `TargetAnnotationV1` evidence semantics shared beyond the
* engine (record-time AND replay-time both need the SAME verdict by
Expand Down
6 changes: 3 additions & 3 deletions packages/ad-script/src/internal/target-annotation-identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* prefix matching over versioned `.ad` target-binding evidence, plus the
* bounded diagnostic diffs built on top of it. Both the writer (over
* `SnapshotNode`-derived values, `src/daemon/session-target-evidence.ts`) and
* replay-time verification (`src/daemon/handlers/session-replay-target-classification.ts` and
* replay-time verification (`src/daemon/replay/internal/session-replay-target-classification.ts` and
* `src/commands/interaction/runtime/selector-wait.ts`) share this verbatim so both
* sides compute the SAME identity/ancestry match by construction (#1478 P5
* review, "genuinely shared recording vocabulary" relocated to its owner).
Expand Down Expand Up @@ -50,7 +50,7 @@ type IdentityTreeNode = Pick<RawSnapshotNode, 'type' | 'identifier' | 'label'>;
* normalized (NFC, label whitespace collapse, `normalizeType` role) AND
* 256-byte field-capped, on every path. Shared by the record-time writer
* (`src/daemon/session-target-evidence.ts`), replay-time verification
* (`src/daemon/handlers/session-replay-target-verification.ts`), and the
* (`src/daemon/replay/internal/session-replay-target-verification.ts`), and the
* dispatch-side post-resolution guard
* (`src/commands/interaction/runtime/resolution.ts`), so all three compute
* a node's identity with byte-identical semantics.
Expand Down Expand Up @@ -199,7 +199,7 @@ export function matchesAncestryPrefix(
// Diagnostic diffs (decision 3): bounded, best-effort mismatch descriptions
// shared by the record-time classification core and replay-time verification
// (#1478 P5 stage C2a) — moved here verbatim from
// `src/daemon/handlers/session-replay-target-classification.ts` so both
// `src/daemon/replay/internal/session-replay-target-classification.ts` so both
// callers depend on one definition instead of two copies.
// ---------------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/src/replay-divergence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export type ReplayDivergenceOverflow = {
/**
* ADR 0012 decision 6, R3: the daemon-computed repair routing hint. Always
* defined (never absent/null) — the mapping in
* `src/daemon/handlers/session-replay-repair-hint.ts` is total, defaulting
* `src/daemon/replay/internal/session-replay-repair-hint.ts` is total, defaulting
* to `manual` whenever no safer routing can be proven. A small fixed token,
* so it is carried at every response level (including `--level digest`) and
* every projection (text, JSON, client `AppError`, MCP `structuredContent`).
Expand Down
6 changes: 4 additions & 2 deletions packages/contracts/src/target-annotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// action.
//
// Shared vocabulary, not a replay internal — the daemon writes it (8 modules), `replay/` parses and
// verifies it, and `commands/` reads it back. It was declared in `replay/target-identity.ts`
// verifies it, and `commands/` reads it back. It was previously declared in
// `packages/ad-replay/src/internal/target-identity.ts`
// alongside the parsing logic, which meant `SessionAction` could not be stated without depending on
// the replay zone. The parsing and classification logic stays there; only the shape moved.
// the replay zone. The parsing and classification logic now lives in `@agent-device/ad-script`;
// only the shape moved.

export type TargetAncestryEntry = { role: string; label?: string };
export type TargetScrollRegion = { role: string; id?: string; label?: string };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { ReplayTestAttemptOutcome } from '../session-test-types.ts';
// What the scheduler owes its host around cancellation (#1478 P3b): cancel exactly once when
// an attempt times out, and always release when it settles. How the daemon then maps that onto
// its request registry is the adapter's contract, pinned in
// `src/daemon/handlers/__tests__/session-replay-cancellation.test.ts`.
// `src/daemon/replay/internal/__tests__/session-test-suite-command-cancellation.test.ts`.
const cancellations: Array<{ attemptId: string; canceled: number; released: number }> = [];

function trackCancellation() {
Expand Down
8 changes: 4 additions & 4 deletions scripts/__tests__/test-file-size-ratchet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const PINNED_TEST_FILE_LINES: Readonly<Record<string, number>> = Object.freeze({
'src/__tests__/remote-connection.test.ts': 2973,
'src/daemon/handlers/__tests__/snapshot-handler.test.ts': 2138,
'src/commands/interaction/runtime/settle.test.ts': 2359,
'src/daemon/handlers/__tests__/session-replay-runtime-maestro.test.ts': 1963,
'src/daemon/replay/internal/__tests__/session-replay-runtime-maestro.test.ts': 1963,
'packages/platform-apple/src/runner/__tests__/runner-session.test.ts': 1957,
'src/utils/__tests__/daemon-client.test.ts': 1873,
'src/utils/__tests__/output.test.ts': 1861,
Expand All @@ -51,10 +51,10 @@ const PINNED_TEST_FILE_LINES: Readonly<Record<string, number>> = Object.freeze({
'src/daemon/handlers/__tests__/find.test.ts': 1199,
'packages/platform-apple/src/core/__tests__/perf.test.ts': 1222,
'src/mcp/__tests__/command-tools.test.ts': 1216,
'src/daemon/handlers/__tests__/session-replay-divergence.test.ts': 1136,
'src/daemon/replay/internal/__tests__/session-replay-divergence.test.ts': 1100,
'packages/platform-apple/src/core/__tests__/apps.test.ts': 1146,
'src/daemon/handlers/__tests__/session-replay-repair-transaction.test.ts': 1202,
'src/daemon/handlers/__tests__/session-replay-target-verification-runtime.test.ts': 1182,
'src/daemon/replay/internal/__tests__/session-replay-repair-transaction.test.ts': 1202,
'src/daemon/replay/internal/__tests__/session-replay-target-verification-runtime.test.ts': 1182,
'src/__tests__/client-metro.test.ts': 1105,
'src/__tests__/cli-network.test.ts': 1092,
'packages/platform-android/src/__tests__/snapshot-helper.test.ts': 1002,
Expand Down
14 changes: 14 additions & 0 deletions scripts/layering/architecture-ownership.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ test('architecture ownership roots resolve to tracked owners', () => {
}
});

test('daemon replay façade pins its named command surface', () => {
const tracked = new Set(listTrackedTypeScriptFiles(repoRoot));

for (const declaration of ARCHITECTURE_OWNERSHIP.facades) {
assert.ok(tracked.has(declaration.root), `${declaration.name} root is not tracked`);
const source = fs.readFileSync(path.join(repoRoot, declaration.root), 'utf8');
assert.deepEqual(
readNamedExports(source),
declaration.exports,
`${declaration.name} façade exports drifted`,
);
}
});

test('vocabulary roots are exported contract facades', () => {
const manifest = JSON.parse(
fs.readFileSync(path.join(repoRoot, 'packages/contracts/package.json'), 'utf8'),
Expand Down
24 changes: 24 additions & 0 deletions scripts/layering/architecture-ownership.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
type FacadeDeclaration = Readonly<{
root: string;
exports: readonly string[];
}>;

export type LogicalModulePolicy = Readonly<{
name: string;
roots: readonly string[];
forbiddenTargetRoots: readonly string[];
facade?: FacadeDeclaration;
}>;

const DAEMON_REPLAY_FACADE = {
root: 'src/daemon/replay/index.ts',
exports: ['ReplaySession', 'ReplayTestVideoOwner', 'runReplayCommand', 'runReplayTestCommand'],
} as const;

export const LOGICAL_MODULE_POLICIES = [
{
name: 'ad-replay',
Expand All @@ -27,10 +38,23 @@ export const LOGICAL_MODULE_POLICIES = [
'packages/ad-replay/',
],
},
{
name: 'daemon-replay',
roots: ['src/daemon/replay/'],
forbiddenTargetRoots: [
'src/daemon/handlers/session-close.ts',
'src/daemon/handlers/record-runtime.ts',
'src/daemon/session-store.ts',
],
facade: DAEMON_REPLAY_FACADE,
},
] as const satisfies readonly LogicalModulePolicy[];

export const ARCHITECTURE_OWNERSHIP = {
logicalModules: LOGICAL_MODULE_POLICIES,
facades: LOGICAL_MODULE_POLICIES.flatMap((module) =>
module.facade ? [{ name: module.name, ...module.facade }] : [],
),
vocabulary: [
{
name: 'client-contract',
Expand Down
46 changes: 43 additions & 3 deletions scripts/layering/daemon-modularity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ test('replay-test rejects request-global and engine-internal imports', () => {
'packages/replay-test/src/internal/scheduler.ts',
[
"import { emitRequestProgress } from '../../../../src/request/progress.ts';",
"import { readReplayScriptMetadata } from '../../../../src/daemon/handlers/session-replay-runtime.ts';",
"import { readReplayScriptMetadata } from '../../../../src/daemon/replay/internal/native-command.ts';",
"import { parseMaestroProgram } from '../../../../src/compat/maestro/program-ir-parser.ts';",
].join('\n'),
],
['src/request/progress.ts', 'export function emitRequestProgress() {}'],
[
'src/daemon/handlers/session-replay-runtime.ts',
'src/daemon/replay/internal/native-command.ts',
'export function readReplayScriptMetadata() {}',
],
['src/compat/maestro/program-ir-parser.ts', 'export function parseMaestroProgram() {}'],
Expand All @@ -140,7 +140,7 @@ test('replay-test rejects request-global and engine-internal imports', () => {
violations.map(({ message }) => message.replace(/;.*/, '')),
[
'replay-test must not import src/request/progress.ts',
'replay-test must not import src/daemon/handlers/session-replay-runtime.ts',
"packages/replay-test/src/internal/scheduler.ts must not import daemon-replay's internal tree (src/daemon/replay/internal/native-command.ts)",
'replay-test must not import src/compat/maestro/program-ir-parser.ts',
],
);
Expand Down Expand Up @@ -179,6 +179,46 @@ test('internal trees reject deep imports globally, including from daemon', () =>
assert.match(violations[0]!.message, /must not import maestro's internal tree/);
});

test('daemon replay rejects handler, owner, session-store, and engine deep edges', () => {
const edges = resolveImportEdges(
new Map([
[
'src/daemon/handlers/session.ts',
"import { runReplayCommand } from '../replay/internal/native-command.ts';",
],
[
'src/daemon/replay/internal/test-command.ts',
"import { handleCloseCommand } from '../../handlers/session-close.ts';",
],
[
'src/daemon/replay/internal/command-types.ts',
"import { SessionStore } from '../../session-store.ts';",
],
[
'packages/ad-replay/src/internal/step-loop.ts',
"import { runReplayCommand } from '../../../../src/daemon/replay/internal/native-command.ts';",
],
['src/daemon/replay/internal/native-command.ts', 'export function runReplayCommand() {}'],
['src/daemon/handlers/session-close.ts', 'export function handleCloseCommand() {}'],
['src/daemon/session-store.ts', 'export class SessionStore {}'],
]),
);

const violations = checkDaemonModularityRatchets(
[...baselineEdges(), ...edges],
baselineTypeCycleMembers(),
);
assert.deepEqual(
violations.map(({ message }) => message.replace(/;.*/, '')),
[
"src/daemon/handlers/session.ts must not import daemon-replay's internal tree (src/daemon/replay/internal/native-command.ts)",
'daemon-replay must not import src/daemon/handlers/session-close.ts',
'daemon-replay must not import src/daemon/session-store.ts',
"packages/ad-replay/src/internal/step-loop.ts must not import daemon-replay's internal tree (src/daemon/replay/internal/native-command.ts)",
],
);
});

test('R9 records zone ceilings and keeps engine files outside the largest component', () => {
// One commands file and one engine file traded for two provider-webdriver ones, so the
// total stays at the baseline and only the per-zone claims are on trial.
Expand Down
2 changes: 1 addition & 1 deletion scripts/layering/daemon-modularity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const TYPE_CYCLE_BASELINE = Object.values(LARGEST_TYPE_CYCLE_ZONE_CEILING
const ENGINE_FILE_PREFIXES = [
'packages/ad-replay/src/',
'packages/maestro/src/',
'src/daemon/handlers/session-replay',
'src/daemon/replay/internal/',
'packages/replay-test/src/',
] as const;

Expand Down
16 changes: 16 additions & 0 deletions scripts/layering/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ test('parseImports distinguishes value, type-only, dynamic, and value re-export
);
});

test('parseImports detects multiline dynamic imports', () => {
const edges = parseImports(['void import(', " '../multiline.ts'", ');'].join('\n'));

assert.deepEqual(edges, [
{ spec: '../multiline.ts', dynamic: true, typeOnly: false, line: 1, symbols: [] },
]);
});

test('parseImports resolves constant-template dynamic imports', () => {
const edges = parseImports('void import(`../template.ts`);');

assert.deepEqual(edges, [
{ spec: '../template.ts', dynamic: true, typeOnly: false, line: 1, symbols: [] },
]);
});

test('parseImports retains named source symbols without changing edge-kind detection', () => {
const edges = parseImports(
[
Expand Down
Loading
Loading