Skip to content
Closed
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
47 changes: 23 additions & 24 deletions src/daemon/__tests__/generic-settle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ async function dispatchGeneric(params: {
};
return await dispatchGenericCommand({
req,
session: params.session,
ref: params.sessionStore.lookup(params.sessionName)!,
sessionName: params.sessionName,
logPath: '',
sessionStore: params.sessionStore,
Expand Down Expand Up @@ -549,32 +549,31 @@ test('scroll without --settle takes no observation captures and issues no refs',
expect(refFrameState(sessionStore.get(sessionName) as SessionState)).toBe('expired');
});

test('a settle observation that cannot build a runtime degrades instead of failing the action', async () => {
test('an ended generic lifetime is refused before dispatch or settle construction', async () => {
const sessionStore = makeSessionStore();
const sessionName = 'generic-settle-evicted';
// The session the router handed us is no longer in the store — evicted
// between dispatch and observation. Building the settle runtime throws
// SESSION_NOT_FOUND, and the observation is best-effort: the scroll already
// happened, so the response keeps its result and simply carries no settle.
const session = makeIosSession(sessionName);
setSessionSnapshot(
session,
buildSnapshotState({ nodes: BEFORE_NODES, backend: 'xctest', producer: 'apple-runner' }, {}),
);
activateCompleteRefFrame(session);
seedSession(sessionName, sessionStore);
const ref = sessionStore.lookup(sessionName)!;
sessionStore.retire(ref);
mockCommandDispatch([AFTER_NODES]);

const response = await dispatchGeneric({
sessionName,
sessionStore,
session,
command: 'scroll',
positionals: ['down'],
flags: { ...SETTLE_FLAGS },
});

const data = expectOkData(response);
expect(data.settle).toBeUndefined();
await expect(
dispatchGenericCommand({
req: {
token: 't',
session: sessionName,
command: 'scroll',
positionals: ['down'],
flags: SETTLE_FLAGS,
},
ref,
sessionName,
sessionStore,
logPath: '',
contextFromFlags,
executePlatformCommand: platformExecution,
}),
).rejects.toMatchObject({ details: { reason: 'session_lifetime_ended' } });
expect(mockDispatch).not.toHaveBeenCalled();
expect(captureObservations).toEqual([]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ test('a healed script survives repair + fresh-session replay: self-contained ope
expect(session.actions.map((a) => a.command)).toEqual(['open']);

// --- Agent presses the blessed @ref (record-and-heal): recorded live. ---
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e7'],
flags: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ test('a record-and-heal divergence on the LAST step resumes with an empty tail a
expect(session.actions.map((a) => a.command)).toEqual(['open']);

// --- Agent performs the corrective press (blessed @ref), recorded live. ---
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e6'],
flags: {},
Expand Down Expand Up @@ -280,7 +280,7 @@ test('a manual divergence (unannotated action-failure) on the LAST step resumes

// --- Agent performs the step's intent as a recorded action (blessed
// @ref), recorded live. ---
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e6'],
flags: {},
Expand Down Expand Up @@ -408,7 +408,7 @@ test('a caution (identity-mismatch) divergence on the LAST step resumes with an
// --- Agent presses the actual (renamed) control via a blessed @ref,
// recorded live — the record-and-heal-shaped repair for path (a) from
// #1262 ("selector binds the wrong node on the right screen"). ---
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e6'],
flags: {},
Expand Down Expand Up @@ -659,7 +659,7 @@ test('a stale --plan-digest on an empty-tail resume is rejected WITHOUT consumin
expect(divergence.resume.from).toBe(3);

const session = sessionStore.get(sessionName)!;
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e6'],
flags: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { storeSessionForTest } from '../../../__tests__/test-utils/store-factory.ts';
import { isSessionRecording } from '../../session-script-publication-capability.ts';
/**
* ADR 0012 decision 6: `replay --save-script` arming (R1), the repair-run
Expand Down Expand Up @@ -134,7 +135,7 @@ test('R1/R2/R6: prefix steps get fresh evidence, corrective + resumed steps land
expect(divergence.resume.from).toBe(3);

// --- Agent performs the corrective action live (recorded). ---
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e9'],
flags: {},
Expand Down Expand Up @@ -381,13 +382,13 @@ test('a --no-record state-fix action never enters session.actions', () => {

// Agent fixes app state with --no-record, then performs the real corrective
// action (recorded).
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['100', '200'],
flags: { noRecord: true },
result: {},
});
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e9'],
flags: {},
Expand Down Expand Up @@ -578,7 +579,7 @@ test('Fix 3: a --from resume that lands on the terminal close skips it too, lett
expect(divergence.resume.from).toBe(2);

const session = sessionStore.get(sessionName)!;
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e9'],
flags: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { storeSessionForTest } from '../../../__tests__/test-utils/store-factory.ts';
/**
* #1271 stage 2 (ADR 0012 amendment): repair-segment default exclusion of
* observation-only commands (`snapshot`/`get`/`is`/a read-only `find`), the
Expand Down Expand Up @@ -193,14 +194,14 @@ test('diagnostic get/is reads mid-repair are excluded from the healed script by
// --- The agent explores mid-repair: a couple of diagnostic reads to locate
// the renamed control. Both are observation-only and the session is
// repair-armed, so per the default exclusion neither is appended. ---
ctx.sessionStore.recordAction(session, {
ctx.sessionStore.recordAction(storeSessionForTest(ctx.sessionStore, session), {
command: 'get',
positionals: ['attrs', '@e5'],
flags: {},
result: { selectorChain: ['id="save-v2"'] },
interactiveObservation: true,
});
ctx.sessionStore.recordAction(session, {
ctx.sessionStore.recordAction(storeSessionForTest(ctx.sessionStore, session), {
command: 'is',
positionals: ['visible', 'id="save-v2"'],
flags: {},
Expand All @@ -212,7 +213,7 @@ test('diagnostic get/is reads mid-repair are excluded from the healed script by

// --- The agent performs the corrective press (blessed @ref), recorded live —
// a mutating action is never observation-only, so it is unaffected. ---
ctx.sessionStore.recordAction(session, {
ctx.sessionStore.recordAction(storeSessionForTest(ctx.sessionStore, session), {
command: 'press',
positionals: ['@e7'],
flags: {},
Expand Down Expand Up @@ -295,7 +296,7 @@ test("a --record'ed diagnostic read lands in the healed script (the diverged-ste
// --- The agent's correction for the diverged step IS itself a read (the
// wave-3 E3 shape): `get attrs` on the renamed control, explicitly forced
// into the heal with `--record` since it would otherwise be excluded. ---
ctx.sessionStore.recordAction(session, {
ctx.sessionStore.recordAction(storeSessionForTest(ctx.sessionStore, session), {
command: 'get',
positionals: ['attrs', '@e7'],
flags: { record: true },
Expand Down Expand Up @@ -374,14 +375,14 @@ test('empty-segment guard: a --from resume refuses with an actionable --record h
// --- The agent ONLY inspects — never performs a corrective action. Both
// reads are excluded from `session.actions`, so nothing was recorded in
// this repair segment. ---
ctx.sessionStore.recordAction(session, {
ctx.sessionStore.recordAction(storeSessionForTest(ctx.sessionStore, session), {
command: 'get',
positionals: ['attrs', '@e5'],
flags: {},
result: { selectorChain: ['id="save-v2"'] },
interactiveObservation: true,
});
ctx.sessionStore.recordAction(session, {
ctx.sessionStore.recordAction(storeSessionForTest(ctx.sessionStore, session), {
command: 'find',
positionals: ['id', 'save-v2', 'exists'],
flags: {},
Expand Down Expand Up @@ -418,7 +419,7 @@ test('non-repair authoring recording is unchanged: a read in a fresh `open --sav
session.scriptPublication = authoringPublication('armed');
expect(session.scriptPublication.kind).not.toBe('repair');

ctx.sessionStore.recordAction(session, {
ctx.sessionStore.recordAction(storeSessionForTest(ctx.sessionStore, session), {
command: 'get',
positionals: ['attrs', '@e5'],
flags: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { storeSessionForTest } from '../../../__tests__/test-utils/store-factory.ts';
/**
* ADR 0012 decision 6 repair-transaction `--force`/`--overwrite` semantics (#1258): a
* `--save-script` target that already exists is refused at arm time, before any step runs, unless
Expand Down Expand Up @@ -244,7 +245,7 @@ test('#1258 preflight honors PERSISTED force: a --from continuation without --fo

// The agent's corrective press (blessed @ref), recorded live.
const session = sessionStore.get(sessionName)!;
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e7'],
flags: {},
Expand Down Expand Up @@ -317,7 +318,7 @@ test('#1258 preflight is per-target: a --from continuation RETARGETING to an exi
expect(sessionTargetPath(session)).toBe(targetA);
expect(sessionTargetForce(session)).toBe(true);
// The agent's corrective press.
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e7'],
flags: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ test('end-to-end repair transaction: cold divergence stays alive, corrective res

// --- Agent performs the corrective press (blessed @ref), recorded live. ---
const session = sessionStore.get(sessionName)!;
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'press',
positionals: ['@e7'],
flags: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { storeSessionForTest } from '../../../__tests__/test-utils/store-factory.ts';
import { isSessionRecording } from '../../session-script-publication-capability.ts';
/**
* Shared fixtures for the ADR 0012 decision 6 repair-loop tests. The mock
Expand Down Expand Up @@ -59,7 +60,7 @@ export function makeRecordingReplayInvoke(config: RecordingReplayInvokeConfig):
}
const session = resolveInvokeSession(config, req);
const evidence = isSessionRecording(session) ? config.evidence?.(req) : undefined;
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
Comment thread
thymikee marked this conversation as resolved.
command: req.command,
positionals: req.positionals ?? [],
flags: req.flags ?? {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ test('an owner-armed session still records its target and publishes its script',

// What `open`/`close --save-script` do once past the seam: arm the session,
// then publish at teardown. Unchanged by the ingress rejection.
sessionStore.recordAction(session, {
sessionStore.recordAction(storeSessionForTest(sessionStore, session), {
command: 'open',
positionals: ['Example'],
flags: { saveScript: target },
Expand Down
39 changes: 24 additions & 15 deletions src/daemon/__tests__/selector-recording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ test('a repair-armed session excludes get/is/find by default but keeps recording
});
store.set('default', session);

recordIfSession(store, 'default', req('get'), {});
recordIfSession(store, 'default', req('is'), {});
recordIfSession(store, 'default', req('find'), {});
recordIfSession(store, 'default', req('wait'), {});
recordIfSession(store, store.lookup('default'), req('get'), {});
recordIfSession(store, store.lookup('default'), req('is'), {});
recordIfSession(store, store.lookup('default'), req('find'), {});
recordIfSession(store, store.lookup('default'), req('wait'), {});

expect(store.get('default')!.actions.map((a) => a.command)).toEqual(['wait']);
});
Expand All @@ -73,9 +73,9 @@ test('a repair-armed session still records get/is/find dispatched as replay plan
});
store.set('default', session);

recordIfSession(store, 'default', planStepReq('get'), {});
recordIfSession(store, 'default', planStepReq('is'), {});
recordIfSession(store, 'default', planStepReq('find'), {});
recordIfSession(store, store.lookup('default'), planStepReq('get'), {});
recordIfSession(store, store.lookup('default'), planStepReq('is'), {});
recordIfSession(store, store.lookup('default'), planStepReq('find'), {});

expect(store.get('default')!.actions.map((a) => a.command)).toEqual(['get', 'is', 'find']);
});
Expand All @@ -92,9 +92,9 @@ test('--record forces get/is/find through even while repair-armed', () => {
});
store.set('default', session);

recordIfSession(store, 'default', req('get', { record: true }), {});
recordIfSession(store, 'default', req('is', { record: true }), {});
recordIfSession(store, 'default', req('find', { record: true }), {});
recordIfSession(store, store.lookup('default'), req('get', { record: true }), {});
recordIfSession(store, store.lookup('default'), req('is', { record: true }), {});
recordIfSession(store, store.lookup('default'), req('find', { record: true }), {});

expect(store.get('default')!.actions.map((a) => a.command)).toEqual(['get', 'is', 'find']);
});
Expand All @@ -105,10 +105,10 @@ test('outside a repair-armed session, get/is/find/wait all record normally', ()
expect(session.scriptPublication).toBeUndefined();
store.set('default', session);

recordIfSession(store, 'default', req('get'), {});
recordIfSession(store, 'default', req('is'), {});
recordIfSession(store, 'default', req('find'), {});
recordIfSession(store, 'default', req('wait'), {});
recordIfSession(store, store.lookup('default'), req('get'), {});
recordIfSession(store, store.lookup('default'), req('is'), {});
recordIfSession(store, store.lookup('default'), req('find'), {});
recordIfSession(store, store.lookup('default'), req('wait'), {});

expect(store.get('default')!.actions.map((a) => a.command)).toEqual([
'get',
Expand All @@ -122,7 +122,7 @@ test('wait absent records positionals without target-v1 annotation', () => {
const store = makeStore();
store.set('default', makeIosSession('default'));

recordIfSession(store, 'default', waitAbsentReq(), { waitedMs: 0 });
recordIfSession(store, store.lookup('default'), waitAbsentReq(), { waitedMs: 0 });

expect(store.get('default')!.actions[0]).toMatchObject({
command: 'wait',
Expand All @@ -131,3 +131,12 @@ test('wait absent records positionals without target-v1 annotation', () => {
});
expect(store.get('default')!.actions[0]?.targetEvidence).toBeUndefined();
});

test('sessionless recording cannot acquire a newly published address', () => {
const store = makeStore();
const admitted = store.lookup('default');
const session = makeIosSession('default');
store.publish('default', session);
recordIfSession(store, admitted, req('wait'), { waitedMs: 100 });
expect(session.actions).toEqual([]);
});
Loading
Loading