Route snapshot capture sources through one platform-neutral router

Aberta
#4,205 2 comentários 0 reações 0 responsáveis Ver no GitHub

Mantenedores costumam responder em até 1 dia

Ninguém assumiu esta issue ainda.

Avaliação

Dificuldade
5/5
Tempo estimado
Mais de uma semana
Facilidade para iniciantes
5/100
Tipo de issue
Refatoração
Clareza
Razoavelmente clara
Status de atividade
Ativa
Stack de tecnologia
android, ios, macos, typescript
Domínio
mobile, tooling

Direção de pesquisa

Start with the iOS route core in packages/platform-apple/src/snapshot-route.ts and its failure policy in snapshot-route-failure-policy.ts, then the constant ports in packages/platform-apple/src/runner-runtime.ts. The ADR decision on Android's system-UI annotation and SurfaceProof must land before any code moves, and the work is blocked by #4182 on unmerged runner layers. Done means one router in packages/capture-kit serves all channels, with no constant-filled ports and the existing route tests passing unchanged.

Escrita pelo modelo de indexação a partir do texto da issue.

Descrição

refactor

Purpose

Snapshot capture source selection must be one product rule for every platform and provider. Today the rule exists only for iOS, in packages/platform-apple/src/snapshot-route.ts, and each channel fills the route's ports with placeholders. Android, macOS, web, WebDriver and Revyl each have one hard-wired source with no shared fallback, refusal or proof vocabulary. Producer facts live in about six owners.

Current state (evidence)

  • The iOS route core: packages/platform-apple/src/snapshot-route.ts (probes, target resolution, acquisition, one fallback, identity stamping, warnings) and snapshot-route-failure-policy.ts (refusal scope).
  • The runner channel (#4182) fills the route's ports with constants: isDiscoveryPending: () => false, targetFreshness: 'cached', systemSurfacePresent: async () => 'unknown' (packages/platform-apple/src/runner-runtime.ts). The route reads them at snapshot-route.ts (discovery wait, cached-target refresh, presence probe).
  • Eligibility is split: isEligible in the route, the observe-only bypass in runner-runtime.ts bindBridgeSnapshot, and the preferredBackend verdict pin in packages/capture-kit/src/snapshot-quality-verdict.ts → stable-capture.ts.
  • Producer facts: packages/contracts/src/ios-snapshot.ts, three producer-keyed tables in packages/capture-kit/src/ios-snapshot-acquisition.ts, packages/kernel/src/snapshot.ts (SnapshotProvenance), packages/command-runtime/.../stable-capture.ts, producer switches in packages/command-runtime/src/commands/capture/runtime/snapshot.ts. Revyl's Android tree is labeled appium-source, the same name WebDriver uses for iOS.
  • Single-source platforms: Android packages/platform-android/src/snapshot-capture.ts, macOS packages/platform-apple/src/os/macos/surface-snapshot.ts, Revyl packages/provider-revyl/src/snapshot.ts, WebDriver packages/provider-webdriver/src/webdriver-ios-snapshot.ts.
  • validateTarget is duplicated in snapshot-source/adapter.ts and runner-snapshot-bridge.ts. Three fallback settlers exist: runFallback, runSurfaceFallback, captureOffRoute.
  • ADR 0004 is about 60 KB. docs/adr/FORMAT.md asks for about 8 KB.

Required behavior

  1. A new ADR, "Snapshot capture source routing", states the rule for all platforms. ADR 0004 keeps only the iOS facts (bridge, runner plan, system-surface hosts) and drops its amendment history.

  2. One platform-neutral router in packages/capture-kit owns: the source plan (plan(input) returns the source and the reason), one fallback per capture, the per-target generation circuit, the refusal table (capture scope vs generation scope), occlusion proof, identity stamping, and warnings. It imports no platform package.

  3. Each platform or provider declares its channels. A channel declares only what it has. A channel never fills a port with a constant.

    type SurfaceProof = (device, signal) =>
      Promise<'clear' | Readonly<{ occluded: string }> | 'unproven'>;
    type SourceChannel<T> = Readonly<{
      producer: SnapshotProducer;
      acquire(req: { target: T; hint; signal }): Promise<SourceOutcome>;
      resolveTarget(device, app, signal): Promise<T>;
      refreshTarget?(device, app, signal): Promise<T>;   // replaces targetFreshness
      awaitDiscovery?(device, signal): Promise<void>;    // replaces isDiscoveryPending
      proof: 'none' | SurfaceProof;                      // replaces the presence/foreground pair
      supports: { customActions: boolean; observeOnly: boolean; pinnedBackend: boolean };
    }>;
    
  4. Owners with more than one channel route through the router: iOS local and iOS provider (Limrun) Simulators (bridge, then apple-runner). Every one-channel owner (Android, macOS, web, WebDriver, Revyl, HarmonyOS, Linux, TV CDP, composing providers, physical iOS, tvOS, visionOS) does not route through the router. It stamps identity through the capture-kit helper and declares its registry row. An owner that gains a second channel moves to the router.

  5. One producer registry in packages/contracts, declared with satisfies Record<SnapshotProducer, ProducerFacts> (channel, truncation, residue, quiet time, ref-namespace owner). The producer union is platform-neutral. Every other producer table derives from the registry.

  6. The refusal fixture contracts/fixtures/ios-snapshot-bridge-refusals.json (#4182) becomes the router's refusal table input for every channel that emits typed refusals.

Completion conditions

  • git grep finds no route port filled by a constant (() => false, async () => 'unknown') and no targetFreshness.
  • A layering rule fails when the router module imports a platform or provider package.
  • A repo guard fails when a producer in the registry has no production emitter.
  • A repo guard fails when an ADR cites a file path or test name that does not exist.
  • Existing route behavior tests pass unchanged against the router, for the guest and runner channels. No step deletes, skips, or loosens a behavior assertion to pass.
  • Each step removes the path it replaces in the same PR. No dual routing, no compatibility shim, and no adapter that wraps the old route.
  • Every router interface member has a real implementation in one of the two iOS Simulator channels. No member exists for a hypothetical owner.
  • Each step leaves the touched code simpler: fewer owners of a rule, fewer settlement paths, or fewer producer tables. Reviewers judge this, not line count. A step that only moves code does not merge.
  • Live check: local iOS Simulator, Limrun iOS, local Android emulator. Each capture names the same producer and fallback warnings as before the change.
  • The new ADR is accepted, and ADR 0004 is under about 15 KB.

Migration plan (moved from the ADR 0038 proposal, #4255)

After acceptance, each small PR must merge alone:

  1. Move the provenance carriers and helpers from packages/kernel/src/snapshot.ts to contracts. Migrate the registry consumers atomically. Keep the node and geometry primitives in kernel. Do not allow a kernel-to-contracts cycle.
  2. Simplify in place first, inside packages/platform-apple: remove the placeholder ports, merge the three fallback settlers into one settlement path, and give eligibility one owner. Keep the route behavior tests unchanged.
  3. Lift the simplified route core into packages/capture-kit as the router. Give a source one way to ask for a fresh target: today refreshTarget? and resolveTarget(..., 'refresh') both exist, and the stale-target fallback still calls the second (from the #4278 review). Decide this together with the local source's cached-target refresh, so that local behavior does not change by accident. Then lift, with its refusal-table input and the layering enforcement. Bind both iOS Simulator channels to it and delete the Apple route core in the same PR. If the lifted router is not simpler than the in-place result, stop and record why in this issue.
  4. Move the one-channel owners to the identity-stamp helper and the registry rows. Rename the Revyl Android producer to revyl-android-tree together with its emitter and consumers.
  5. Make ADR 0004 smaller than about 15 KB. First move its unique contracts to their owners. Keep the iOS rules.

Guard inventory

  • Router layering enforcement, with a failing dependency control.
  • Producer-emitter and refusal guards in test/repo-guards/. They reject a reachable production emitter that has no registry row, an unlisted refusal code, and a scope disagreement.
  • An ADR-citation guard that rejects cited paths and test names that do not exist.
  • Select these guards through affected checks, together with docs-prose, docs-links, and agent-guidance.
  • Live producer and fallback parity on the local iOS Simulator, Limrun iOS, and the local Android emulator.

Design detail (moved out of ADR 0038)

ADR 0038 records only the decisions. The detailed design is in this issue comment. It covers the SourceChannel<T> members, planner inputs, circuit keys, target refresh and discovery, the per-owner source list, registry row facts, quiet times, and the Android systemSurfaceOnly classification. Move each item to its owning type or package doc when the router lands. Do not move them back into the ADR.

Dependencies

Blocked by: #4182 (runner-served bridge and refusal fixture), which is stacked on the unmerged Limrun runner layers (stack/limrun-native-runner).
Open question: whether Android's system-UI annotation fits the SurfaceProof port. Decide this in the ADR before code moves.

Linguagem predominante
TypeScript
Estrelas
5.3k
Forks
377
Merge médio
13h 58min
PRs com merge (30d)
654

Preparar o ambiente

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de callstack/agent-device

Todas as issues de callstack/agent-device

Issues semelhantes

Mais issues de TypeScript

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.