Skip to content

chore(gates): enforce session identity through copies and destructuring - #3172

Closed
thymikee wants to merge 1 commit into
chore/session-followup-review-basefrom
chore/session-write-scanner-review
Closed

thymikee wants to merge 1 commit into
chore/session-followup-review-basefrom
chore/session-write-scanner-review

Conversation

@thymikee

@thymikee thymikee commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Summary

Finish the session-write scanner review for #3155 and #3116. Optional chains, record clones/rest copies and destructured refs keep their ownership identity. Nested writes through a tracked SessionRef.session reach R7; unrelated request session names stay outside that recognition.

Patch-return collection skips nested functions. Fixtures must parse as valid modules, preventing parser recovery from masking invalid examples. Two files changed; the scanner remains a scoped AST gate.

Validation

Reconciled head: 14c30ab142d4e977ab6467f3fc84a070f59e0294. The review base pins #3170 at 111f50f783b703adc6731537f9b3581bae4b040e; #3170 remains a prerequisite.

  • Exact-head pnpm check:affected --base 111f50f783b703adc6731537f9b3581bae4b040e --run passes all runnable checks.
  • git range-diff confirms the reviewed patch is unchanged; both files are byte-identical to the prior head.
  • Retained regression proof: 30 scanner controls pass; nine production violations are rejected by R7, with original source restored.
  • Prior CI passed Typecheck & Package, Coverage and Integration Tests at 2ce928f5e6. Checks on the reconciled head are pending. No device run is required for this tooling-only diff.

@thymikee thymikee changed the title chore/session write scanner review chore(gates): enforce session identity through copies and destructuring Oct 3, 2026

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

14 issues found across 380 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/daemon/interaction/internal/interaction-touch-fill.ts">

<violation number="1" location="src/daemon/interaction/internal/interaction-touch-fill.ts:213">
P1: A fill that completes after its admitted session is retired now fails response construction with `session_lifetime_ended`. Resolve the lifetime optionally and, like the targeted-touch response builder, omit the retired result's reference frame and settle observation rather than requiring the old lifetime.

(Based on your team's feedback about skipping settle refs after retirement.)</violation>
</file>

<file name="src/__tests__/test-utils/store-factory.ts">

<violation number="1" location="src/__tests__/test-utils/store-factory.ts:22">
P3: `ref.session !== session` compares object identity, but `SessionStore.update(ref, patch)` replaces the stored record with a new object (`next = { ...current, ...changes }; entry.current = next`). Re-calling this helper with the originally published session object after an update on the same address therefore throws "A different test session occupies this address" for the same logical session. Pass the current record (e.g. the ref's `session`) or document that callers must, since the identity check cannot tell a replaced record from a foreign one.</violation>
</file>

<file name="packages/capture-kit/src/durable-capture/session-binding.fixtures.ts">

<violation number="1" location="packages/capture-kit/src/durable-capture/session-binding.fixtures.ts:22">
P3: `lookup` throws "Test session retired" whenever the address has no entry, including the first-time case where `set` was never called for it (all callers `set` before binding). A never-registered session isn't retired, so the message misleads when a test binds to a typo'd or unseeded address. Use a distinct message for the unregistered case, e.g. "Test session not found".</violation>
</file>

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

<violation number="1" location="src/daemon/__tests__/replay-suite/session-test-suite.test.ts:500">
P2: Same mismatch as the macOS hunk: when `sessionStore.get(req.session)` returns an existing record (the `?? makeAndroidSession(...)` fallback is there precisely because that branch is reachable), `sessionStore.publish(req.session, session)` throws `session_address_occupied` instead of the previous no-op/replace `set`. `get` already returns the live record whose field mutation is a durable write, so publishing the same address back is unnecessary and now fatal. Only publish when the session was newly created (`get` returned undefined).</violation>
</file>

<file name="src/daemon/screen-recording-session-binding.ts">

<violation number="1" location="src/daemon/screen-recording-session-binding.ts:15">
P2: This recreates the delegated binding for every `read`, discarding its last observed resource. If the published recording is replaced and the session then retires, `read()` returns the original handle instead of the latest owned handle; retain one delegated binding for the wrapper lifetime.

(Based on your team's feedback about retaining session capture resources after retirement.)</violation>

<violation number="2" location="src/daemon/screen-recording-session-binding.ts:19">
P2: This allows a binding whose published lifetime has ended to create a second session lifetime under the same address. Check the existing `published` ref’s lifetime before the publish path so delayed adoption cannot resurrect the draft.</violation>
</file>

<file name="src/daemon/__tests__/replay-divergence/session-replay-divergence-observation.test.ts">

<violation number="1" location="src/daemon/__tests__/replay-divergence/session-replay-divergence-observation.test.ts:130">
P3: `store.get('default')` is always undefined in this test: only `'cwd:worktree:default'` (and ref.address) is ever published, so this assertion never fails and verifies nothing about lifetime binding. Remove it; the meaningful assertions are `store.get(ref.address)` plus the state/snapshot checks.</violation>
</file>

<file name="packages/capture-kit/src/durable-capture/adoption.ts">

<violation number="1" location="packages/capture-kit/src/durable-capture/adoption.ts:64">
P2: The post-cleanup `canPersist()` gate can strand the manifest written by this attempt when the bound lifetime retires during async disposal. Always run the fence-guarded `confirmFailedAdoptionTransition`; its fence check prevents overwriting a successor's record while allowing this record to become `completed`.</violation>
</file>

<file name="src/daemon/__tests__/app-log-session-resource.test.ts">

<violation number="1" location="src/daemon/__tests__/app-log-session-resource.test.ts:382">
P3: The "successor manifest" this test writes is the failed adoption's own `runtime.result.envelope` (same sessionId, fence, and lifecycle as the 'active' record adoption already wrote to `resourcePath`), so the final `{lifecycle: 'open'}` assertion passes even if a regression rewrote the record back to the adoption's envelope instead of preserving legitimate successor evidence. Write a distinguishable envelope — e.g., the successor fence used in the 'late adoption disposes its pending handle' test — so the preservation claim is actually verified.</violation>
</file>

<file name="packages/capture-kit/src/durable-capture/adoption.test.ts">

<violation number="1" location="packages/capture-kit/src/durable-capture/adoption.test.ts:91">
P3: This retired-lifetime failure path reports `reportUndurableCleanup` with `{ confirmed: true }` (canPersist() is false after the lifetime retires, so no tombstone is persisted and the cleanup is confirmed), but the test never asserts the report. The two sibling tests in this file both assert their `reportUndurableCleanup` outcomes, while this test leaves that distinct reporting branch uncovered.</violation>
</file>

<file name="packages/platform-android/src/recording/failed-finish.test.ts">

<violation number="1" location="packages/platform-android/src/recording/failed-finish.test.ts:145">
P3: `sessionStore.get` and `sessionStore.set` are now dead code: the shared coordinator mutates `session` only through `binding.adopt`/`binding.clear`, and `get`/`set` are never invoked. Keep only `resolveSessionDir` (or inline it) so the test reflects the new binding contract.</violation>
</file>

<file name="src/daemon/selector-runtime-backend.ts">

<violation number="1" location="src/daemon/selector-runtime-backend.ts:90">
P2: `ref` refreshes captures to the current same-lifetime session, but native text reads still use the admission-time `session` captured by `createSelectorBackend`. Resolve `params.ref` immediately before each native read and build its execution context from that current session, otherwise a rebuild during capture can use stale app, surface, or trace metadata.</violation>
</file>

<file name="packages/capture-kit/src/capture-admission/__tests__/durable-capture-resource.fixtures.ts">

<violation number="1" location="packages/capture-kit/src/capture-admission/__tests__/durable-capture-resource.fixtures.ts:67">
P3: `context.session` is now a stale snapshot: `binding.adopt`/`binding.clear` replace the store entry with a new object via `slot.replace`, so the returned `session` (the original `{}` object seeded before the binding was created) never reflects an adopted or cleared resource. Test authors reading `context.session.appLog` get `undefined` even after a successful start. Drop `session` from the returned context, or derive it from the binding instead of seeding it separately.</violation>
</file>

<file name="packages/host-kit/src/internal/process-lock.ts">

<violation number="1" location="packages/host-kit/src/internal/process-lock.ts:166">
P2: When `releaseReclaimMutex` throws on the path where the acquisition already succeeded, the raw fs error propagates while the lock directory, owner record, and the mutation guard file all stay behind, permanently wedging the path. Unlike the acquisition-failure branch, this branch never rolls back (`fs.rmdirSync(lockDirPath)` only runs in the `acquireUnderMutationGuard` catch) and never removes the guard file. Because `holdReclaimMutex` is checked before the lock is ever inspected, every later `tryAcquireProcessLock` returns `busy` at the guard check and can never reach the reclaim that would clear the left-over lock. Unlink failure is rare (external deletion, `EPERM`/`EACCES`), but when it happens the only recovery is manual removal of both paths; the caller additionally receives a bare `ENOENT`/`EPERM` error instead of the typed AppError the rest of this module reports.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Re-trigger cubic

result: FillCommandResult;
text: string;
flags: CommandFlags | undefined;
staleRefsWarning: string | undefined;
}): InteractionResponsePayloads {
const { session, result } = params;
const { result, ref, sessionStore } = params;
const session = sessionStore.requireCurrent(ref);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: A fill that completes after its admitted session is retired now fails response construction with session_lifetime_ended. Resolve the lifetime optionally and, like the targeted-touch response builder, omit the retired result's reference frame and settle observation rather than requiring the old lifetime.

(Based on your team's feedback about skipping settle refs after retirement.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/daemon/interaction/internal/interaction-touch-fill.ts, line 213:

<comment>A fill that completes after its admitted session is retired now fails response construction with `session_lifetime_ended`. Resolve the lifetime optionally and, like the targeted-touch response builder, omit the retired result's reference frame and settle observation rather than requiring the old lifetime.

(Based on your team's feedback about skipping settle refs after retirement.) </comment>

<file context>
@@ -198,13 +202,15 @@ async function prepareFillRefTarget(
 }): InteractionResponsePayloads {
-  const { session, result } = params;
+  const { result, ref, sessionStore } = params;
+  const session = sessionStore.requireCurrent(ref);
   const maestroFallback = maestroFallbackDisclosure(
     params.flags?.maestro?.allowNonHittableCoordinateFallback === true,
</file context>

Comment thread src/daemon-client/daemon-client-lifecycle.ts
Comment thread scripts/layering/session-state.ts Outdated
Comment thread src/session-repair-tombstone.ts
platform: 'android',
});
sessionStore.set(req.session, session);
sessionStore.publish(req.session, session);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Same mismatch as the macOS hunk: when sessionStore.get(req.session) returns an existing record (the ?? makeAndroidSession(...) fallback is there precisely because that branch is reachable), sessionStore.publish(req.session, session) throws session_address_occupied instead of the previous no-op/replace set. get already returns the live record whose field mutation is a durable write, so publishing the same address back is unnecessary and now fatal. Only publish when the session was newly created (get returned undefined).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/daemon/__tests__/replay-suite/session-test-suite.test.ts, line 500:

<comment>Same mismatch as the macOS hunk: when `sessionStore.get(req.session)` returns an existing record (the `?? makeAndroidSession(...)` fallback is there precisely because that branch is reachable), `sessionStore.publish(req.session, session)` throws `session_address_occupied` instead of the previous no-op/replace `set`. `get` already returns the live record whose field mutation is a durable write, so publishing the same address back is unnecessary and now fatal. Only publish when the session was newly created (`get` returned undefined).</comment>

<file context>
@@ -497,7 +497,7 @@ test('test aggregates snapshot diagnostics from replay session samples', async (
         platform: 'android',
       });
-      sessionStore.set(req.session, session);
+      sessionStore.publish(req.session, session);
       return { ok: true, data: { replayed: 1, healed: 0 } };
     },
</file context>

).rejects.toMatchObject({ code: 'COMMAND_FAILED' });
expect(start.forceCleanup).toHaveBeenCalledOnce();
expect(context.sessionStore.get(context.sessionName)).toBe(successor);
expect(testCaptureStore.read(context.resourcePath).status).toBe('missing');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This retired-lifetime failure path reports reportUndurableCleanup with { confirmed: true } (canPersist() is false after the lifetime retires, so no tombstone is persisted and the cleanup is confirmed), but the test never asserts the report. The two sibling tests in this file both assert their reportUndurableCleanup outcomes, while this test leaves that distinct reporting branch uncovered.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/capture-kit/src/durable-capture/adoption.test.ts, line 91:

<comment>This retired-lifetime failure path reports `reportUndurableCleanup` with `{ confirmed: true }` (canPersist() is false after the lifetime retires, so no tombstone is persisted and the cleanup is confirmed), but the test never asserts the report. The two sibling tests in this file both assert their `reportUndurableCleanup` outcomes, while this test leaves that distinct reporting branch uncovered.</comment>

<file context>
@@ -68,3 +68,25 @@ test('a failed terminal transition preserves the primary error and reports it un
+  ).rejects.toMatchObject({ code: 'COMMAND_FAILED' });
+  expect(start.forceCleanup).toHaveBeenCalledOnce();
+  expect(context.sessionStore.get(context.sessionName)).toBe(successor);
+  expect(testCaptureStore.read(context.resourcePath).status).toBe('missing');
+});
</file context>
Suggested change
expect(testCaptureStore.read(context.resourcePath).status).toBe('missing');
expect(testCaptureStore.read(context.resourcePath).status).toBe('missing');
expect(context.reportUndurableCleanup).toHaveBeenCalledWith(context.device, { confirmed: true });

Comment on lines +145 to +149
get: () => session,
set: (_name: string, next: AndroidRecordingSession) => {
session = next;
},
resolveSessionDir: (name) => path.join(sessionsDir, name),
resolveSessionDir: (name: string) => path.join(sessionsDir, name),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: sessionStore.get and sessionStore.set are now dead code: the shared coordinator mutates session only through binding.adopt/binding.clear, and get/set are never invoked. Keep only resolveSessionDir (or inline it) so the test reflects the new binding contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/platform-android/src/recording/failed-finish.test.ts, line 145:

<comment>`sessionStore.get` and `sessionStore.set` are now dead code: the shared coordinator mutates `session` only through `binding.adopt`/`binding.clear`, and `get`/`set` are never invoked. Keep only `resolveSessionDir` (or inline it) so the test reflects the new binding contract.</comment>

<file context>
@@ -128,42 +127,50 @@ async function adoptAndroidRecording(params: {
-  const sessionStore: DurableCaptureSessionStore<AndroidRecordingSession> = {
-    set: (_name, next) => {
+  const sessionStore = {
+    get: () => session,
+    set: (_name: string, next: AndroidRecordingSession) => {
       session = next;
</file context>
Suggested change
get: () => session,
set: (_name: string, next: AndroidRecordingSession) => {
session = next;
},
resolveSessionDir: (name) => path.join(sessionsDir, name),
resolveSessionDir: (name: string) => path.join(sessionsDir, name),
resolveSessionDir: (name: string) => path.join(sessionsDir, name),

Comment thread src/__tests__/daemon-exit-wait.test.ts
const session: TestCaptureSession = {};
sessionStore.set(sessionName, session);
return {
binding: makeCaptureSessionBinding(sessionStore, sessionName, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: context.session is now a stale snapshot: binding.adopt/binding.clear replace the store entry with a new object via slot.replace, so the returned session (the original {} object seeded before the binding was created) never reflects an adopted or cleared resource. Test authors reading context.session.appLog get undefined even after a successful start. Drop session from the returned context, or derive it from the binding instead of seeding it separately.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/capture-kit/src/capture-admission/__tests__/durable-capture-resource.fixtures.ts, line 67:

<comment>`context.session` is now a stale snapshot: `binding.adopt`/`binding.clear` replace the store entry with a new object via `slot.replace`, so the returned `session` (the original `{}` object seeded before the binding was created) never reflects an adopted or cleared resource. Test authors reading `context.session.appLog` get `undefined` even after a successful start. Drop `session` from the returned context, or derive it from the binding instead of seeding it separately.</comment>

<file context>
@@ -72,6 +64,10 @@ export function makeDurableCaptureContext(
   const session: TestCaptureSession = {};
   sessionStore.set(sessionName, session);
   return {
+    binding: makeCaptureSessionBinding(sessionStore, sessionName, {
+      read: (session) => session.appLog,
+      replace: (session, appLog) => ({ ...session, appLog, appLogFailure: undefined }),
</file context>

Comment thread src/daemon-client/daemon-client-lifecycle.ts
@thymikee
thymikee force-pushed the chore/session-write-scanner-review branch from 971332d to 26db357 Compare October 3, 2026 15:07
@thymikee
thymikee force-pushed the fix/session-shutdown-review branch from d3d0c57 to 7e46a5a Compare October 3, 2026 15:16
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.96 MB 4.96 MB 0 B
Package (unpacked) 4.96 MB 4.96 MB 0 B
Package (download) 1.49 MB 1.49 MB -12 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.6 ms 26.0 ms -0.6 ms
CLI --help 82.3 ms 80.9 ms -1.4 ms

@thymikee
thymikee changed the base branch from fix/session-shutdown-review to main October 3, 2026 16:07
@thymikee
thymikee added this pull request to stack #3175 October 3, 2026 16:07
@thymikee
thymikee removed this pull request from stack #3175 October 3, 2026 16:15
@thymikee
thymikee changed the base branch from main to fix/session-shutdown-review October 3, 2026 16:15
@thymikee
thymikee force-pushed the chore/session-write-scanner-review branch from 26db357 to bc132d7 Compare October 3, 2026 16:19
@thymikee
thymikee force-pushed the fix/session-shutdown-review branch 2 times, most recently from e4ea909 to 408192c Compare October 3, 2026 16:56
@thymikee
thymikee force-pushed the chore/session-write-scanner-review branch from bc132d7 to 2ce928f Compare October 3, 2026 17:22
@thymikee

thymikee commented Oct 3, 2026 •

Copy link
Copy Markdown
Member Author

Rechecked the original 26-thread review against the runtime tree inherited from #3170 at 408192c52f and the updated scanner. #3172 contains two scanner files.

The scanner fixes are at 2ce928f5e6: tracked ref.session.field writes reach R7, and fixtures fail on parser errors. Thirty focused tests pass; a nested foreign lease write planted in the production app-log owner produces the expected R7 violation. The existing copy, optional-chain and destructuring controls remain.

Four threads are resolved with evidence: log truncation preserves its inode; the legacy guard control never waits behind an external guard; release already waits for the guard; and a real exclusive write against the hardened lock directory returns handled EEXIST on this Mac.

The remaining owning-layer reviews stay open:

Finding Owner Required follow-up
Missing process birth times compare equal during reuse #3133 Require a proved identity match, with the nearest missing-proof negative control
Startup cleanup omits the private-directory capability #3131 Let the retirement helper read the capability from its settings for every caller
Owned launch retirement requires birth-time proof before joining #3127 Separate monitored-child completion from the proof needed to signal a process
Failed adoption skips its fenced durable transition after retirement #3136 Check resource authority separately from current session-slot authority
Guard unlink failure after acquisition #3122 Review the retained-guard contract, typed failure and recovery control; do not remove uncertain guards unconditionally
Fill response requires a retired session after native completion #3151 Audit the completed-result boundary alongside the targeted-touch path and prove its reachable cancellation schedule

Test-control and documentation findings are tracked too: distinguish successor envelopes, assert cleanup reports, remove stale fixture projections/no-op methods, make the zombie control match its title, and strengthen the remaining fixture controls. The guard-held release assertion and ADR release wording are now corrected in the later parent 94807d36594b816167b40b2827bdcd6301636d57: the test asserts the claim and guard still exist while release waits, and the ADR includes unreadable owner records and removal failures as unverified outcomes. Binding-retention and second-adoption claims still need a reachable production path; the record-only wrapper is currently local to one start operation.

The earlier “fake process group” diagnosis was too strong. In #3131’s exact-head Integration Tests job, screenshot cleanup signals group -4399 outside the hermetic guard’s live-child set. The fixture launches real Node subprocesses. The screenshot path aborts its concurrent rotation probe in finally; the relevant exit/abort schedule still needs diagnosis. Keep the signaling guard intact.

These follow-ups remain tracked in #3116. Review findings are assessed at their owning boundary, including regression controls, before resolution.

@thymikee
thymikee force-pushed the fix/session-shutdown-review branch from 408192c to 94807d3 Compare October 3, 2026 17:49
@thymikee
thymikee changed the base branch from fix/session-shutdown-review to chore/session-followup-review-base October 3, 2026 18:13
@thymikee

thymikee commented Oct 3, 2026

Copy link
Copy Markdown
Member Author

At 2ce928f the R7 gate still misses foreign session writes in most files that hold a SessionRef, so this needs one more change before merge. hasNamedType in session-state.ts only matches a bare SessionRef annotation, and sessionRefPropertyPaths only reads inline type literals. Three common shapes never enter sessionRefBindings: ref: SessionRef | undefined (record-runtime.ts, session-app-deployment.ts, session-runtime-command.ts, snapshot-alert.ts, snapshot-settings.ts), sessionRef: SessionRef | undefined (generic-settle.ts, interaction-session.ts), and ref: SessionRef inside a named params type that is destructured or accessed (interaction-runtime.ts, interaction-touch-fill.ts). In those files if (ref) ref.session.lease = x; or params.ref.session.appLogFailure = e; makes isSessionRecord false, so R7 reports nothing. That is the gap #3155 asked to close, and the new tests only use bare : SessionRef or inline literals, so they cannot show it.

The rule to enforce is this: every <expr>.session.<declared SessionState field> write and every ...<expr>.session copy is a session write, whatever the declared type of <expr>. The simplest way is to keep the old memberName(node) === 'session' test in the member branch of isSessionRecord, together with the declared-field filter that isSessionBinding already pairs it with. If you keep typed tracking instead, hasNamedType must accept unions that contain SessionRef, and property lookup must cover named interfaces. Either way, please add ref: SessionRef | undefined and named-params controls that fail on the planted write.

Could the SessionRef type tracking (isSessionRefValue, sessionRefPropertyPaths, the objectDestructurings loop) go entirely, keeping the name rule plus declared-field filter and everything else that closes real holes (ChainExpression unwrapping, MemberExpression write targets, Object.assign/structuredClone, object-rest, collectPatchReturns, the parse-error assertion)? That would be roughly 100 lines smaller and would also cover the union-typed and named-interface refs. The only reason I can see for narrowing is a non-record session property, and I found no production instance of one.

Not blocking: the same ~120-line tracking layer exists only to avoid a false match when session is a string, which cannot be spread into a record or field-written anyway, so you can take or leave dropping it.

The open cubic-dev-ai threads do not apply to this 2-file diff, so please resolve them. They point at files outside this change (inherited from the #3170 base or not in this diff): interaction-touch-fill.ts:213, daemon-client-lifecycle.ts:232, session-repair-tombstone.ts:74, session-test-suite.test.ts:500, screen-recording-session-binding.ts:15, screen-recording-session-binding.ts:19, capture-kit adoption.ts:66, daemon-registration-owner.ts:318, selector-runtime-backend.ts:90, host-kit process-lock.ts:189, installation.md:123, store-factory.ts:22, session-binding.fixtures.ts:22, session-replay-divergence-observation.test.ts:130, app-log-session-resource.test.ts:382, adoption.test.ts:91, failed-finish.test.ts:149, daemon-exit-wait.test.ts:223, durable-capture-resource.fixtures.ts:67, daemon-client-lifecycle.ts:429.

I read the code but did not run the scanner or its tests, so the gap above comes from reading hasNamedType and sessionRefPropertyPaths at head. I also did not confirm that the new tests fail on the base beyond reading its code. Smoke Tests is still running. This diff touches only the layering scanner and its test, which the device smoke route never runs, so a failure there would likely be unrelated. There are no conflicts. Before merge, R7 must flag every <expr>.session.<declared field> write and ...<expr>.session copy, including SessionRef | undefined and named-interface refs, with controls for those shapes.

@thymikee
thymikee force-pushed the chore/session-write-scanner-review branch from 2ce928f to 14c30ab Compare October 3, 2026 19:39
@thymikee
thymikee force-pushed the chore/session-followup-review-base branch from 408192c to 111f50f Compare October 3, 2026 19:39
@thymikee

thymikee commented Oct 3, 2026

Copy link
Copy Markdown
Member Author

Consolidated into #3155 as part of reducing #3116 to seven PRs. The composition preserves the complete pre-consolidation source tree, including tests and later review corrections. This PR is superseded; its review discussion and native evidence remain available. Outstanding findings transfer to the owning keeper in the implementation record.

@thymikee thymikee closed this Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-03 21:16 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant