Repository navigation
Conversation
…ce switch (WIP) Adds Workflow.surfaces, Step.surface/opens/surface_origin, ActionKind.SWITCH_SURFACE, the surface contract check, reviewed v2 sealed-digest omissions, and the design doc docs/MULTI_SURFACE.md. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…lt on undeclared ones (WIP) SurfaceRouter, Replayer surface_backends, SWITCH_SURFACE identity check, recorder page/switch capture, compiler surfaces. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
… and contract refusals Also detect popup vs tab from window.opener rather than Playwright's opener(). Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…es for mypy Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…urface bundles (WIP) Each step result records the surface it ran on; the outcome classifier requires that record to match the sealed contract. A qualification case observes every surface it switches to against that surface's boundary (QualificationProject.surface_environments) and moves the input-edge guard with it. Resume and managed dispatch stay refused. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…[:TARGET] and a config surfaces: map (WIP) Kind defaults to the kind the bundle declares; TARGET fills that kind's one target field; a config entry carries any other field. Bindings the bundle cannot use are refused before anything runs; an unbound backend surface is named up front and halts at its switch. A web backend surface opens as a page in the run's own browser. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
Owned browser: the listener script and event binding are bound on the context, new pages are claimed by the shared Recorder when the action that opened them is written, and acting in another page records a surface switch. The listener re-arms per document (Chromium reuses a popup's initial about:blank Window). Attach mode stays one tab. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
Record the chosen design and rejected alternatives for each gap, replace the stale refusal text, and update the limits in MULTI_SURFACE, LIMITS, PRODUCTION, and BROWSER_RECORDING. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
A surface is a name plus an identity check, never a live handle. The router binds a page or backend surface to the one live target that passes the step's vision-first identity check (anchor template or recorded screen text, plus origin/URL/title/opener where exposed); zero or several halt. Checkpoints carry each surface's last-seen observation and the active surface (omitted when unset), so a linear resume re-binds by identity. Leaving a self-closed page needs an explicit switch. Managed dispatch is admitted when every backend surface is bound; program resume stays refused. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…tests (WIP) Resume revalidation binds the checkpoint's active surface to the one live target that shows its retained contract before approval is consumed. A surface switch's armed identity is its surface check, not an entity identity row, so compiled switch steps can be checkpointed. Tests: resume across surfaces behind the Check-the-record approval, an ambiguous re-bind, and page-routing mismatches. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…h back A page-surface backend shows a blank frame once its page closes, so postconditions on a closed popup fail rather than reading another window. Both recorders write the return to the opener as an explicit SWITCH_SURFACE with that page's identity check; nothing returns on its own. The interactive recorder refuses, with a clear reason, a popup whose first action closes it before any frame of it was captured. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…answered by identity; docs Attach mode (record --cdp) follows a page only when a followed page opened it (window.opener, or the browser target's opener id for a noopener tab), installs the session binding on that page alone, and rebinds the exact CSS-viewport geometry at each switch. A tab the person opens by hand, even one that closes before the next poll, is still refused. Step.dialog carries the answer to a JavaScript dialog the step's action raises. The browser backend answers it only when its message matches; a different message is dismissed, never accepted, and the step halts. A backend that cannot see browser dialogs refuses the step before any input. The field is omitted when empty, so existing sealed bundles are unchanged. docs/MULTI_SURFACE.md documents the vision-first design, resume and Cloud, routing, explicit switches back, dialogs, the native focus step, rejected alternatives, and the remaining limits.
Member
Author
|
Superseded: this work landed in the OpenAdapt monorepo as OpenAdaptAI/OpenAdapt#1197. openadapt-flow is now developed in packages/flow there. |
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.
For review only. openadapt-flow is frozen for the monorepo cutover: do not merge, no release. Base is the freeze SHA
f21281fa(origin/main).What this adds
A bundle can now declare more than one surface:
Each step names its surface. Moving between surfaces takes an explicit, typed
SWITCH_SURFACEstep, which has its own identity check. The design, the rejected alternatives, and the file:line audit of origin/main are indocs/MULTI_SURFACE.md.Vision first: a surface is a name plus an identity check (new)
A bundle-level surface exists only at an actuator boundary: a browser driven through its pages, a local desktop, or an RDP/Citrix session. Tabs, popups, native windows and dialogs inside one actuator are screen states, handled by per-step visual identity checks. No surface kind was added.
surface,active_surfaceand per-surfacesurface_observations: kind, origin, perceptual hash, and the step last seen. They hold no handles, URL paths, titles or screen text. Resume re-binds the checkpoint's surface by identity before the approval is consumed. An ambiguous or missing match raisesStateDivergedwith nothing driven. "Check the record" (RECONCILIATION_REQUIRED) still comes first after a save that may have gone through.surfaces:. One refusal is kept because it guards something real: a managed run will not start with an unbound backend surface.--cdp) follows popups and tabs that a followed page opened. It useswindow.opener, or the browser target's opener id for anoopenertab. The binding goes on that page only, with exact per-page viewport geometry and the same origin, mask and guard contract. A tab the person opens by hand is still refused.Step.dialog = {response, message}is set on the step whose action raises the dialog. The browser backend answers it only when the message matches. Any other message is dismissed (never accepted) and the step halts. Screen-driven backends refuse a dialog step, because there a dialog is just pixels.Safety invariants
Replayer.backend. The active surface is routed through it, and backend-bound caches are reset on each switch.Governed, qualified, and durable runs (new)
A multi-surface run can reach
VERIFIEDunder the same rules as a single-surface run. No gate is weakened.workflow.json. That file is sealed in the bundle content digest, which the governed authorization already binds, so there is no second binding that could drift.StepResultrecords thesurfaceit ran on. The outcome classifier returnsCOMPLETED_UNVERIFIEDunless every result matches its step's declared surface. A single-surface report never carries the field.VERIFIEDstill requiresindependent_readback, meaning a non-UI read of the system of record. A persisted re-read or a screen read never reaches it, on any surface.QualificationProject.surface_environments(oneEnvironmentBoundaryper non-primary surface).report.observed_surface_environments. The input guard moves to the active backend.CLI backend binding (new)
replay/runtake a repeatable--surface NAME=KIND[:TARGET]. A deployment config takes asurfaces:map of full backend configs.KINDdefaults from the bundle, andTARGETfills that kind's usual field.Interactive
record --url(new)In a browser Flow launched, popups and tabs are followed as surfaces automatically.
Recorder.Recorder.Compatibility:
SCHEMA_VERSIONstays 2.switch_surfaceaction, so it fails closed.Tests
test_multi_surface_e2e.py: popup + tab, recorded, compiled, and replayed withmodel_calls == 0, plus refusals.test_multi_surface_backends.py: web then MockWaa Windows; wrong, missing, or mismatched backends halt at the switch.test_multi_surface_contract.py: IR contract, plus the remaining resume and managed-dispatch refusals.test_multi_surface_identity.py(new): durable resume across surfaces. It pauses at "Check the record" on a tab, the checkpoint holds the name and observation, the resume needs approval, re-binds by identity, and makes 0 model calls. Also: an ambiguous re-bind halts, a page-routing mismatch halts, and router-level routing works.test_multi_surface_selfclose.py(new): a self-closing popup followed by an explicit switch back, in the scripted and interactive recorders. It replays with 0 model calls, and without the switch back the bundle is invalid.test_multi_surface_attach.py(new): CDP attach with a popup and anoopenertab replays in the same browser with 0 model calls. A hand-opened tab is refused.test_multi_surface_dialog.py(new): aconfirm()answer replays. An identity mismatch is dismissed and halts. A screen-driven backend refuses the step.test_multi_surface_governed.py(new):VERIFIED;test_surface_bindings.py(new): flag and config resolution, plus a CLIreplaythat binds a backend surface (0 model calls) or halts with a hint.test_multi_surface_interactive.py(new): an owned-browser interactive recording through a popup and a tab gives the scripted bundle shape and replays with 0 model calls.test_qualification_gate_campaign.pypasses alone (38/38). In the combined run, its 470 s campaign fixture went over the 600 s pytest timeout (8 errors) under load (scrypt-bound checkpoint reads), so those 8 are counted from that separate run.ruffis clean on the package and the changed tests.mypyis clean on the whole package and on the CI strict safety set.test_qualification_gate_campaign.pyerrors were environmental. A macOSmktemppath goes through the/var→/private/varsymlink, and the durable authority path refuses links. With a resolved path, all 38 pass.Limits
expect_dialog). The interactive recorder does not capture them yet. Qualification does not treat a dialog answer as separate evidence, andprompt()text answers are not supported.surface_environments; the operator writes them.