Extract snapshot policy from daemon to host-side facet (#1983) - #2014
Open
thymikee wants to merge 2 commits into
Open
Extract snapshot policy from daemon to host-side facet (#1983)#2014thymikee wants to merge 2 commits into
thymikee wants to merge 2 commits into
Conversation
#2005 established the presentation ownership boundary and moved the iOS presentation policies out of `src/daemon/`. It left the three remaining Wave 4 policies behind their existing daemon adapters. This closes that gap, so `src/snapshot/` owns host-side snapshot policy generally rather than presentation alone. Freshness recovery: the window vocabulary, the Android staleness classification and its thresholds, and the retry loop move to `src/snapshot/snapshot-freshness/`. The loop is parameterized by a classifier and a retry schedule, so how long a backend may lag behind a real transition is a policy input rather than a constant the loop owns. `src/daemon/session-snapshot-freshness.ts` keeps only what needs a session — reading and retiring the window on store-owned `SessionState`, and choosing the comparison baseline from snapshot lineage — and remains the declared R7 owner of `androidSnapshotFreshness`. The two call sites #1739 named as the Wave 5 blockers, `selector-capture-runtime.ts` and `deferred-interaction-outcome.ts`, now reach freshness through the seam. Timeout evidence: whether a failure is the accessibility-timeout shape becomes a policy in `src/snapshot/snapshot-timeout-policy.ts`. The published `details.androidSnapshotTimeoutScreenshot` payload becomes vocabulary in `@agent-device/contracts/snapshot-timeout-evidence`, built through constructors so an assembly site cannot publish a fifth, undeclared arm. It gets its own subpath rather than riding the shared capture facade, which keeps it out of the CLI cold-start closure. Typed details, diagnostics and screenshot evidence are unchanged. Screenshot-overlay policy: which Android nodes earn an overlay ref, and what rectangle an overlay covers, move to `src/snapshot/screenshot-overlay/`. The daemon keeps approved artifact and ref assembly only — ranking, projection to screenshot pixels, drawing and PNG IO. The boundary test generalizes from the presentation subtree to the whole facet: nothing under `src/snapshot/` may import `src/daemon/`. It gains a positive control, because a filter that stopped matching would look identical to a boundary being obeyed. The residual call sites #1983 also named are audited and deliberately left in place. `direct-ios-selector.ts` carries no presentation policy; its two pure exports are selector derivation and ADR 0011 delegation-on-error, whose owner would be the selector pipeline governed by R19, not this facet. ADR 0004 records the finding so it does not have to be re-derived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLYhmt5ZNHQATG8T8ZFo7R
Size Report
npm unpacked components
Startup median (7 runs, lower is better):
Top changed chunks:
Top changed packed files
|
Three findings from an adversarial pass over bc95d7f, all in the new seams. `SnapshotFreshnessRetrySchedule.deadlineMs` was an absolute epoch instant named almost identically to the duration constant `ANDROID_FRESHNESS_RETRY_DEADLINE_MS` that feeds it. A backend binding the loop with the duration instead of `markedAt + duration` type-checked, drove `remainingMs` hugely negative, and silently ran zero retries with no annotation. Renamed to `retryUntilMs` — the pre-refactor local's name — and the doc now says which one it is. The recovery loop also gains direct tests it never had: the trustworthy, recovered and still-suspicious paths, plus an already-expired deadline that pins the budget to the action rather than to whenever the first capture returned, which is the shape the mis-binding would have taken. Two stale doc references from earlier drafts of the same commit: the timeout assembly claimed its evidence shape lives in `@agent-device/contracts/capture`, which is where it deliberately does NOT live — following that comment would re-home the type into the shared facade and reintroduce the cold-start closure cost the dedicated subpath exists to avoid. And the freshness window doc cited `SnapshotFreshnessPolicy`, a type removed before commit for being unused; the real seam is the loop's `classify` callback. No production behavior change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GLYhmt5ZNHQATG8T8ZFo7R
Member
Author
|
Changes requested at
Coordinator local evidence at the exact head:
The basic host assembly works on both local lanes, but it does not clear the typed-policy findings above. Do not mark ready on this head. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refactors snapshot-related policies out of the daemon layer into a host-side
src/snapshot/facet, establishing a clean separation of concerns where policy logic is testable independently of daemon assembly. This completes Wave 4 debt tracked by #1983.Key Changes
Snapshot freshness policy: Moved Android staleness classification, retry logic, and thresholds from
src/daemon/android-snapshot-freshness.tstosrc/snapshot/snapshot-freshness/. The recovery loop is now parameterized by a classifier and retry schedule, making it reusable across backends.Snapshot timeout policy: Extracted timeout error recognition from daemon-specific code into
src/snapshot/snapshot-timeout-policy.ts, which identifies accessibility-timeout failures through both helper error types and daemon hint text.Screenshot overlay policy: Moved Android-specific overlay source classification and rect balancing from
src/daemon/screenshot-overlay-android.tstosrc/snapshot/screenshot-overlay/android.ts.Snapshot timeout evidence contract: Created
@agent-device/contracts/snapshot-timeout-evidence.tswith a discriminated union type and builder functions to ensure type-safe evidence construction across the daemon/host boundary.Session binding layer: Created
src/daemon/session-snapshot-freshness.tsto handle daemon-specific concerns: reading/retiring the freshness window fromSessionState, choosing comparison baselines from session snapshot lineage, and running the recovery loop with Android policy bound to it.Test reorganization: Moved snapshot policy tests to
src/snapshot/__tests__/where they can run without daemon infrastructure, while keeping session-specific tests insrc/daemon/__tests__/.Implementation Details
The freshness window type is now platform-agnostic (
SnapshotFreshnessWindow), allowing other backends with async hierarchy dumps to reuse the recovery loop with their own thresholds.Android overlay policy is now testable independently via
isAndroidUnlabeledClickableSource()andresolveAndroidOverlaySourceRect()functions.Layering boundary tests updated to enforce that
src/snapshot/cannot import fromsrc/daemon/, ensuring policies remain testable without session state.Constants like
ANDROID_FRESHNESS_WINDOW_MSare now exported from the policy modules rather than hidden in daemon code.https://claude.ai/code/session_01GLYhmt5ZNHQATG8T8ZFo7R