You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* refactor(daemon): remove the superseded coarse snapshotRefsStale marker (ADR 0014 step 8)
The coarse `snapshotRefsStale` client-stale marker is fully superseded by the
ref-frame model and is removed:
- `setSessionSnapshot` and `buildNextSnapshotSession` no longer set/clear it —
replacing the latest observation is a read that never touches the frame.
- Read-only ref staleness now derives from frame state: a plain ref warns once
the frame has EXPIRED (a device side effect changed the screen), and a
read-only capture no longer marks refs stale because it does not expire the
frame. Pinned-ref warnings keep comparing against the frozen frame epoch.
- Deletes `markSessionSnapshotRefsIssued` (its only job was clearing the marker)
and the `session.snapshotRefsStale` field.
Migrates every test off the marker to the frame model (frame-expiry drives the
read warning; complete/partial activation drives admission), and updates the
ADR status + module docs to record step 8 as landed. Ships as follow-up to the
merged #1257 since that PR closed before this step.
Full unit-core + provider-integration green; tsc/lint/fallow/production-exports clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmnraQhvxzEFEUoUiqRpJa
* fix(daemon): resolve @ref reads from the frame tree; scope find's internal warning
Address three review blockers on the coarse-marker removal (ADR 0014 step 8):
1. @ref reads now bind against the authorized frame tree
(`refFrameSnapshot ?? snapshot`) in `requireSnapshotSession`, so an
internal read-only capture that replaced the observation cannot let a
plain `@eN` resolve a different element by positional coincidence.
Missing frame evidence fails instead of falling through to a newer
observation.
2. A mutating find's internal leaf dispatch (`internal.findResolvedTarget`)
no longer attaches a stale-ref warning in either the press or fill path —
the caller never consumed a `@ref`, so the public find response must not
claim it did.
3. `resolveRefStalenessWarning` checks frame expiry FIRST, matching the
admission order: an expired frame is stale for any ref, even a pin that
matches the epoch (a matching pin proves identity within the retained
frame, not that the UI is current).
Regressions: divergent observation-vs-frame trees resolve from the frame
tree or fail when evidence is missing; a locator-based mutating find from an
expired frame carries no stale-ref warning; the reordered resolver unit test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmnraQhvxzEFEUoUiqRpJa
* fix: correct stale-ref warning comments and ADR-0014 present-tense marker refs
The get/wait dispatch comments in selector-runtime.ts still described the
superseded coarse snapshotRefsStale marker ("warn when that tree was
replaced since the client last received refs") even though staleness is
now derived from ref-frame expiry (ADR 0014 migration step 8). Reworded
both to describe the frame-derived mechanism actually implemented by
resolveRefStalenessWarning.
session-snapshot.ts's early-return comment in markSessionPartialRefsIssued
referenced "the coarse marker" as something still left untouched, but that
field no longer exists — reworded to name the ref frame fields it actually
preserves.
ADR-0014's "Ref frames are separate from operational observations" section
still described snapshotRefsStale as part of "the existing... implementation"
in present tense, contradicting the Decision section's own note (line 39)
that migration step 8 already removed it. Reworded to keep the historical
mention while stating the removal.
* fix: frame-lifetime wording for the stale-ref warning and read comments
Address the follow-up review blocker plus the co-located terminology cleanup
(ADR 0014 step 8):
- STALE_SNAPSHOT_REFS_WARNING no longer claims "the session snapshot changed";
it now describes frame lifetime in terms valid for both read warnings and
mutation rejection — the UI may have changed since the refs were issued, so
take a new snapshot before relying on or interacting with them. The warning
fires on frame expiry, including device side effects where no stored snapshot
changed.
- selector-runtime.ts: the get/wait @ref comments now say the read binds to the
retained ref-frame evidence and its staleness is frame-derived, not a property
of the stored snapshot or the live polling capture.
- settle.ts: an unsettled stored capture replaces the observation without
touching the ref frame; read staleness is driven by side-effect-seam expiry,
not by storing a fresh observation.
- interaction-settle.test.ts: renamed the settle test off the removed
stale-marker language to "activates a partial ref frame" (what it asserts).
Comments/test-name/warning-text only — no runtime behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmnraQhvxzEFEUoUiqRpJa
* fix(daemon): name the ref-frame epoch in the pinned-stale-ref warning
The pinned-ref warning is compared against refFrameEpoch(session) — the frozen
frame epoch — not the latest observation generation, and after a read-only
capture those two diverge. The message still said "the session tree is now sN",
which is ambiguous once the observation counter has advanced past the frame
epoch. Name the ref-frame epoch instead:
Ref @E12 was minted from snapshot s3 but the session's ref frame is now s15 —
re-run snapshot -i.
Renames the builder param to `currentFrameEpoch` and corrects its doc comment to
say the pin is compared against the frame epoch, not the stored tree generation.
Regression: `resolveRefStalenessWarning` names the frozen frame epoch, not the
bumped observation generation — a read-only `setSessionSnapshot` advances the
observation counter (15 -> 16) while the frame epoch stays frozen at 15; a pin
at s15 is clean and a pin at s12 names s15, never s16.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BmnraQhvxzEFEUoUiqRpJa
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/adr/0014-session-ref-frame-lifetime.md
+19-18Lines changed: 19 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,8 +36,9 @@ remain compatible with selector-based replay, and add no automatic capture or pe
36
36
## Decision
37
37
38
38
The terms introduced below are now the implemented model; the ref-frame vocabulary is promoted into
39
-
`CONTEXT.md`. The coarse `snapshotRefsStale` marker still backs read-only staleness warnings and is
40
-
removed once per-platform enforcement is confirmed by live evidence (migration step 8).
39
+
`CONTEXT.md`. The superseded coarse `snapshotRefsStale` marker has been removed (migration step 8):
40
+
read-only ref staleness is now derived from frame state — a plain ref warns once the frame has expired,
41
+
and a read-only capture no longer marks refs stale because it does not expire the frame.
41
42
42
43
### Ref frames are separate from operational observations
43
44
@@ -62,9 +63,9 @@ the emitted subset, including ancestors, siblings, overlays, and the viewport. T
62
63
the evidence tree, bounds partial authority. The frame and latest observation share immutable capture
63
64
data when they originate from the same capture; neither transition deep-copies the tree.
64
65
65
-
The existing `snapshotGeneration`/`snapshotRefsStale` implementation evolves behind one ref-frame
66
-
module. The public name `refsGeneration` and the `@e12~s42` grammar remain unchanged for wire
67
-
compatibility.
66
+
The existing `snapshotGeneration` implementation evolves behind one ref-frame module; the
67
+
`snapshotRefsStale` marker it originally paired with has since been removed (migration step 8). The
68
+
public name `refsGeneration` and the `@e12~s42` grammar remain unchanged for wire compatibility.
68
69
69
70
### Frame transitions
70
71
@@ -221,12 +222,12 @@ resolver rather than pretending all subcommands behave alike.
221
222
The registry is the exhaustive source of truth. The following table is non-exhaustive guidance for
222
223
classifying representative actions; it must not become a second prose registry:
223
224
224
-
| Effect | Commands/actions |
225
-
| --- | --- |
226
-
|`may-invalidate`| press, click, fill, longpress, type, focus, scroll, swipe, gesture, back, home, `tv-remote`, orientation, open/relaunch, trigger/push delivery, settings changes, install/reinstall, React Native overlay dismissal, and lifecycle operations that can replace the visible surface |
227
-
| Conditional resolver | keyboard status preserves while dismiss/return/input invalidate; alert get/wait preserve while accept/dismiss invalidate; find reads preserve while click/fill/focus/type delegate to their leaf mutation |
228
-
|`delegated`| batch, replay, and test/suite orchestrators; each nested leaf owns its transition |
229
-
|`preserve`| snapshots and other observation, assertion, screenshot, recording, trace, logs, events, network inspection, performance, inventory, capability, lease, and transport-management operations unless a selected subaction directly manipulates the visible surface |
|`may-invalidate`| press, click, fill, longpress, type, focus, scroll, swipe, gesture, back, home, `tv-remote`, orientation, open/relaunch, trigger/push delivery, settings changes, install/reinstall, React Native overlay dismissal, and lifecycle operations that can replace the visible surface |
228
+
| Conditional resolver | keyboard status preserves while dismiss/return/input invalidate; alert get/wait preserve while accept/dismiss invalidate; find reads preserve while click/fill/focus/type delegate to their leaf mutation |
229
+
|`delegated`| batch, replay, and test/suite orchestrators; each nested leaf owns its transition|
230
+
|`preserve`| snapshots and other observation, assertion, screenshot, recording, trace, logs, events, network inspection, performance, inventory, capability, lease, and transport-management operations unless a selected subaction directly manipulates the visible surface|
230
231
231
232
Clipboard reads and writes preserve the frame because pasteboard state alone does not change element
232
233
identity. A later paste/type action is independently invalidating.
@@ -437,11 +438,12 @@ Each step lands green and independently useful:
437
438
evidence; promote the implemented vocabulary into `CONTEXT.md`; then remove the superseded coarse
438
439
stale marker.
439
440
440
-
Implementation status: steps 1–7 have landed — the ref-frame module and observation split (1), the
441
-
complete daemon classification and gate (2), the pre-side-effect seam at every leaf (3), correct
442
-
complete/partial publication with bounded scope, MCP pin retention, and pinned partial CLI text (4),
443
-
Android freshness decoupled from positional ref authorization (5), the cross-platform contract and
444
-
provider evidence (6), and fail-closed admission enforcement across platforms with typed reasons (7).
441
+
Implementation status: all migration steps have landed — the ref-frame module and observation split
442
+
(1), the complete daemon classification and gate (2), the pre-side-effect seam at every leaf (3),
443
+
correct complete/partial publication with bounded scope, MCP pin retention, and pinned partial CLI text
444
+
(4), Android freshness decoupled from positional ref authorization (5), the cross-platform contract and
445
+
provider evidence (6), fail-closed admission enforcement across platforms with typed reasons (7), and
446
+
the docs/vocabulary promotion plus removal of the superseded coarse `snapshotRefsStale` marker (8).
445
447
Fresh live evidence has exercised nearly every production seam — Apple runtime-ref, direct/native
0 commit comments