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
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 (`@agent-device/selectors/target-evidence`)
* and its REPLAY-time classification wrapper
* (`src/daemon/replay/internal/session-replay-target-classification.ts`) — both
* (`packages/replay-port/src/daemon-port/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
4 changes: 2 additions & 2 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, `@agent-device/selectors/target-evidence`) and
* replay-time verification (`src/daemon/replay/internal/session-replay-target-classification.ts` and
* replay-time verification (`packages/replay-port/src/daemon-port/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 @@ -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/replay/internal/session-replay-target-classification.ts` so both
// `packages/replay-port/src/daemon-port/target-classification.ts` so both
// callers depend on one definition instead of two copies.
// ---------------------------------------------------------------------------

Expand Down
17 changes: 13 additions & 4 deletions packages/replay-port/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
"description": "Daemon-side replay port: the adapter over the ad-replay, ad-script and replay-test engines.",
"dependencies": {
"@agent-device/ad-script": "workspace:*",
"@agent-device/capture-kit": "workspace:*",
"@agent-device/contracts": "workspace:*",
"@agent-device/kernel": "workspace:*",
"@agent-device/selectors": "workspace:*"
},
"exports": {
"./target-token": {
"types": "./src/daemon-port/target-token.ts",
"default": "./src/daemon-port/target-token.ts"
},
"./repair-hint": {
"types": "./src/daemon-port/repair-hint.ts",
"default": "./src/daemon-port/repair-hint.ts"
Expand All @@ -27,6 +24,18 @@
"./session-replay-vars": {
"types": "./src/session-replay-vars.ts",
"default": "./src/session-replay-vars.ts"
},
"./target-classification": {
"types": "./src/daemon-port/target-classification.ts",
"default": "./src/daemon-port/target-classification.ts"
},
"./target-classification-fixtures": {
"types": "./src/target-classification-fixtures.ts",
"default": "./src/target-classification-fixtures.ts"
},
"./target-token": {
"types": "./src/daemon-port/target-token.ts",
"default": "./src/daemon-port/target-token.ts"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { TargetAnnotationV1 } from '@agent-device/contracts/replay';
import { computeTargetEvidence } from '@agent-device/selectors/target-evidence';
import { buildSelectorChainForNode, resolveRecordedTarget } from '@agent-device/selectors';
import { resolvePressRecordingTarget } from '@agent-device/selectors/press-retarget';
import { classifyReplayTarget } from '../session-replay-target-classification.ts';
import { classifyReplayTarget } from '../target-classification.ts';
import { resolveUnverifiedWrapperControl } from '@agent-device/selectors/interaction-targeting';
import {
ELEMENT14_DISTINCT_SUBTREE_NODES,
Expand All @@ -15,7 +15,7 @@ import {
bottomTabsRealCaptureFixture,
recordArticleEvidence,
toSnapshotNodes,
} from './session-replay-target-classification-fixtures.ts';
} from '@agent-device/replay-port/target-classification-fixtures';

/** Verified outcomes carry the verified member + matchCount (for the post-resolution guard). */
function assertVerified(
Expand All @@ -30,8 +30,8 @@ function assertVerified(

const PLATFORM = 'ios' as const;

test('classifyReplayTarget: real-capture fixture verifies by @ref when the tree is unchanged', () => {
const recorded = recordArticleEvidence();
test('classifyReplayTarget: real-capture fixture verifies by @ref when the tree is unchanged', async () => {
const recorded = await recordArticleEvidence();
const replayNodes = bottomTabsRealCaptureFixture();
const winner = replayNodes.find((node) => node.label === 'Article, unselected');
assert.ok(winner);
Expand All @@ -47,8 +47,8 @@ test('classifyReplayTarget: real-capture fixture verifies by @ref when the tree
assertVerified(result, { winnerRef: winner.ref, matchCount: 1 });
});

test('classifyReplayTarget: real-capture fixture — a relabeled node is identity-mismatch (path 3)', () => {
const recorded = recordArticleEvidence();
test('classifyReplayTarget: real-capture fixture — a relabeled node is identity-mismatch (path 3)', async () => {
const recorded = await recordArticleEvidence();
const replayNodes = bottomTabsRealCaptureFixture();
const winner = replayNodes.find((node) => node.label === 'Article, unselected');
assert.ok(winner);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import assert from 'node:assert/strict';
import type { RawSnapshotNode, SnapshotNode } from '@agent-device/kernel/snapshot';
import { computeTargetEvidence } from '@agent-device/selectors/target-evidence';
import type { TargetAnnotationV1 } from '@agent-device/contracts/replay';

export function toSnapshotNodes(raw: RawSnapshotNode[]): SnapshotNode[] {
Expand Down Expand Up @@ -81,7 +80,8 @@ export function bottomTabsRealCaptureFixture(): SnapshotNode[] {
]);
}

export function recordArticleEvidence(): TargetAnnotationV1 {
export async function recordArticleEvidence(): Promise<TargetAnnotationV1> {
const { computeTargetEvidence } = await import('@agent-device/selectors/target-evidence');
const nodes = bottomTabsRealCaptureFixture();
const winner = nodes.find((node) => node.label === 'Article, unselected');
if (!winner) throw new Error('fixture missing Article tab');
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions scripts/__tests__/committed-source-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ export function renamedSince(repoRoot: string, base: string): ReadonlyMap<string
return renamed;
}

/** A carried entry must have been a source the base closure walker could actually read. */
export function baseProductionPathOf(
repoRoot: string,
entryFile: string,
baseTree: SourceTreeReader,
renamedFrom: ReadonlyMap<string, string>,
): string | null {
const baseFile = renamedFrom.get(entryFile) ?? entryFile;
return WALKED_SOURCE.test(baseFile) &&
isProductionSourceFile(baseFile) &&
baseTree.isFile(path.resolve(repoRoot, baseFile))
? baseFile
: null;
}

/** Every path tracked at `treeish`, repo-root-relative, from ONE `git ls-tree`. */
function listCommittedTree(repoRoot: string, treeish: string): string[] {
const listing = git(repoRoot, ['ls-tree', '-r', '--name-only', '-z', treeish]).toString('utf8');
Expand Down
33 changes: 29 additions & 4 deletions scripts/__tests__/eager-closure-budgets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import os from 'node:os';
import path from 'node:path';
import { eagerClosureGraphOf } from '../../src/__tests__/eager-import-closure.fixtures.ts';
import {
baseProductionPathOf,
createCommittedSourceTree,
headCommit,
mergeBaseWithMain,
Expand Down Expand Up @@ -378,6 +379,8 @@ function mkGitFixtureRepo(prefix: string): string {
fs.writeFileSync(path.join(pkgDir, 'src/facades/top.ts'), 'export const b = 2;\n');
fs.writeFileSync(path.join(pkgDir, 'src/facades/nested/deep.ts'), 'export const c = 3;\n');
fs.writeFileSync(path.join(pkgDir, 'src/facades/skip.test.ts'), 'export const d = 4;\n');
fs.mkdirSync(path.join(repo, 'scripts'));
fs.writeFileSync(path.join(repo, 'scripts/standalone.ts'), 'export const outside = 1;\n');
execFileSync('git', ['init', '-q'], { cwd: repo });
execFileSync('git', ['add', '.'], { cwd: repo });
execFileSync(
Expand Down Expand Up @@ -409,9 +412,32 @@ test('a renamed entry is followed to its path at the base, not treated as first-
execFileSync('git', ['mv', 'packages/demo/src/entry.ts', 'packages/demo/src/moved.ts'], {
cwd: repo,
});
expect(renamedSince(repo, 'HEAD').get('packages/demo/src/moved.ts')).toBe(
const renamed = renamedSince(repo, 'HEAD');
const baseTree = createCommittedSourceTree(repo, 'HEAD');
expect(renamed.get('packages/demo/src/moved.ts')).toBe('packages/demo/src/entry.ts');
expect(baseProductionPathOf(repo, 'packages/demo/src/moved.ts', baseTree, renamed)).toBe(
'packages/demo/src/entry.ts',
);
execFileSync(
'git',
['mv', 'packages/demo/src/facades/skip.test.ts', 'packages/demo/src/fixture.ts'],
{ cwd: repo },
);
const withFixtureRename = renamedSince(repo, 'HEAD');
expect(withFixtureRename.get('packages/demo/src/fixture.ts')).toBe(
'packages/demo/src/facades/skip.test.ts',
);
expect(
baseProductionPathOf(repo, 'packages/demo/src/fixture.ts', baseTree, withFixtureRename),
).toBe(null);
execFileSync('git', ['mv', 'scripts/standalone.ts', 'packages/demo/src/standalone.ts'], {
cwd: repo,
});
const withOutsideRename = renamedSince(repo, 'HEAD');
expect(withOutsideRename.get('packages/demo/src/standalone.ts')).toBe('scripts/standalone.ts');
expect(
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
baseProductionPathOf(repo, 'packages/demo/src/standalone.ts', baseTree, withOutsideRename),
).toBe(null);
});

test('discovery is recursive and reads TRACKED files only', () => {
Expand Down Expand Up @@ -520,10 +546,9 @@ const baseTree = createCommittedSourceTree(repoRoot, mergeBase);
const renamedFrom = renamedSince(repoRoot, mergeBase);
const entries = eagerClosureEntries(repoRoot);

/** The entry's path in the merge-base tree (renames followed), or null when it was not there. */
/** The entry's readable source path in the merge-base tree, if it had one. */
function basePathOf(entryFile: string): string | null {
const file = renamedFrom.get(entryFile) ?? entryFile;
return baseTree.isFile(absolute(file)) ? file : null;
return baseProductionPathOf(repoRoot, entryFile, baseTree, renamedFrom);
}

const baseGraphs = new Map<string, ReadonlyMap<string, string | null>>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
import {
bottomTabsRealCaptureFixture,
recordArticleEvidence,
} from './session-replay-target-classification-fixtures.ts';
} from '@agent-device/replay-port/target-classification-fixtures';
import {
captureSnapshotThroughLegacyDispatchFixture,
legacyDispatchCapture,
Expand Down Expand Up @@ -108,7 +108,7 @@ function assertDivergenceShape(
test('(a) an ANNOTATED press whose dispatch throws a selector-miss yields REPLAY_DIVERGENCE, not COMMAND_FAILED', async () => {
const root = mkdtempForTestSync('agent-device-replay-dispatch-miss-annotated-');
const { sessionStore, sessionName } = setupSession(root);
const evidence = recordArticleEvidence();
const evidence = await recordArticleEvidence();
const filePath = writeReplayFile(root, [
`# agent-device:target-v1 ${JSON.stringify(evidence)}`,
'click id="article"',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import {
bottomTabsRealCaptureFixture,
recordArticleEvidence,
toSnapshotNodes,
} from './session-replay-target-classification-fixtures.ts';
} from '@agent-device/replay-port/target-classification-fixtures';

/** Repair-transaction status, or `undefined` outside a repair publication. */
function sessionRepairStatus(session: SessionState | undefined) {
Expand Down Expand Up @@ -86,7 +86,7 @@ test('a record-and-heal divergence on the LAST step resumes with an empty tail a
const sessionStore = new SessionStore(path.join(root, 'sessions'));
const sessionName = 'default';
sessionStore.set(sessionName, makeIosSession(sessionName, { appBundleId: 'com.example.app' }));
const evidence = recordArticleEvidence();
const evidence = await recordArticleEvidence();
const filePath = writeReplayFile(root, [
'open "Demo" --relaunch',
`# agent-device:target-v1 ${JSON.stringify(evidence)}`,
Expand Down Expand Up @@ -545,7 +545,7 @@ test('an unauthorized --from one past the plan end is rejected on an ARMED sessi
const sessionStore = new SessionStore(path.join(root, 'sessions'));
const sessionName = 'default';
sessionStore.set(sessionName, makeIosSession(sessionName, { appBundleId: 'com.example.app' }));
const evidence = recordArticleEvidence();
const evidence = await recordArticleEvidence();
const filePath = writeReplayFile(root, [
'open "Demo" --relaunch',
`# agent-device:target-v1 ${JSON.stringify(evidence)}`,
Expand Down Expand Up @@ -628,7 +628,7 @@ test('a stale --plan-digest on an empty-tail resume is rejected WITHOUT consumin
const sessionStore = new SessionStore(path.join(root, 'sessions'));
const sessionName = 'default';
sessionStore.set(sessionName, makeIosSession(sessionName, { appBundleId: 'com.example.app' }));
const evidence = recordArticleEvidence();
const evidence = await recordArticleEvidence();
const filePath = writeReplayFile(root, [
'open "Demo" --relaunch',
`# agent-device:target-v1 ${JSON.stringify(evidence)}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
selector,
} from '../../../../commands/interaction/runtime/selector-read-utils.ts';
import { createInteractionDevice } from '../../../../commands/interaction/runtime/__tests__/test-utils/index.ts';
import { classifyReplayTarget } from '../session-replay-target-classification.ts';
import { classifyReplayTarget } from '@agent-device/replay-port/target-classification';
/** The verified-member guard denotation the replay loop mints (identity + structural position). */
function guardFor(node: SnapshotNode, nodes: SnapshotNode[]): ReplayTargetGuardDenotation {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ function describeReplayStepValue(action: SessionAction): string | undefined {
// daemon-side: it re-inspects the already-projected `DaemonResponse` wire
// shape to decide whether the wire-level diagnostics-augmentation step
// applies, which is daemon/wire authority, not target-binding classification
// itself (that already happened, in `session-replay-target-classification.ts`'s
// itself (that already happened, in `@agent-device/replay-port/target-classification`'s
// `classifyReplayTarget`, called from `classifyPreDispatchTarget`).
function isCompleteTargetBindingDivergenceResponse(response: DaemonResponse): boolean {
if (response.ok || response.error.code !== 'REPLAY_DIVERGENCE') return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
} from '@agent-device/replay-port/repair-hint';
import { buildReplayDivergenceFailureResponse } from './session-replay-runtime-failure-response.ts';
import { buildAndPersistReplayDivergenceResume } from './session-replay-resume.ts';
import { classifyReplayTarget } from './session-replay-target-classification.ts';
import { classifyReplayTarget } from '@agent-device/replay-port/target-classification';
import { extractReplayTargetToken, readRefLabel } from '@agent-device/replay-port/target-token';

// ---------------------------------------------------------------------------
Expand Down
Loading