Skip to content

Commit 450c605

Browse files
committed
docs: close active-session publication boundaries
1 parent 78c108c commit 450c605

2 files changed

Lines changed: 73 additions & 37 deletions

File tree

CONTEXT.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
- Session: daemon-owned state for a selected target and opened app or surface.
3131
- Script recording: opt-in session mode armed before actions so a persisted `.ad` can carry portable
3232
action inputs and recording-time target identity evidence. It is distinct from screen/video recording.
33-
- Open-to-destination script: self-contained `.ad` script that starts with `open`, checks its destination
34-
with a destination guard, ends before `close`, and leaves the app session active. Avoid: replay
35-
(artifact noun), fragment (reserved for lifecycle-free composition), partial script.
36-
- Destination guard: target-bearing `wait` near the end of an open-to-destination script that confirms a
37-
landmark selector on the ready destination screen before replay hands the live session to its caller.
33+
- Open-to-destination script: self-contained `.ad` script with exactly one initial `open`, a destination
34+
guard after its last app-state mutation, no `close`, and an app session left active for subsequent work.
35+
Avoid: replay (artifact noun), fragment (reserved for lifecycle-free composition), partial script.
36+
- Destination guard: portable selector-targeted `wait` near the end of an open-to-destination script that
37+
confirms a landmark on the ready destination screen before replay hands the live session to its caller.
3838
- Recording backend: daemon-internal module interface selected per recording target that owns platform recording validation, output path policy, start/stop execution, and record-only cleanup below the daemon recording lifecycle.
3939
- Device lease: logical remote ownership of one selected device for a
4040
tenant/run/client and lease provider, separate from platform helper process

docs/adr/0016-active-session-script-publication.md

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,30 @@ or the existing generated default.
4747

4848
### Recording lifecycle
4949

50-
Ordinary script recording gains a two-state publication lifecycle:
51-
52-
- **ARMED**: established by `open --save-script[=<path>]` before target-bearing actions run. The session
53-
records portable action inputs and fresh target identity evidence.
50+
An ordinary recording eligible for active-session publication has three states:
51+
52+
- **ARMED**: established only when a new session's initial successful
53+
`open --save-script[=<path>]` is recorded as action zero. The session records portable action inputs
54+
and fresh target identity evidence.
55+
- **ABORTED**: reached when another plain `open` succeeds while ARMED. The app operation may continue,
56+
but the recording is no longer a single-open bootstrap and close-time publication is disarmed. The
57+
successful `open` response warns that a fresh session is required to author another script.
5458
- **PUBLISHED**: reached only after `session save-script` atomically commits the complete history from
55-
the recorded `open` through the current action. The session remains active at the destination, but
56-
close-time script publication is disarmed.
57-
58-
A publication failure leaves the recording ARMED, including its path and same-target `--force`
59-
authorization, so the caller can correct the target or permissions and retry. PUBLISHED is terminal for
60-
that recording: later actions are ordinary session work and a later `close` performs teardown only. V1
61-
does not re-arm or publish multiple open-to-destination artifacts from one session.
59+
the sole recorded `open` through the current action. The session remains active at the destination,
60+
but close-time script publication is disarmed.
61+
62+
A filesystem or target-collision failure leaves the recording ARMED, including its path and same-target
63+
`--force` authorization, so the caller can correct the target or permissions and retry. ABORTED and
64+
PUBLISHED are terminal until the session is destroyed. `session save-script` in either state fails
65+
loudly and never writes.
66+
67+
Every existing arming entry point respects terminality. `open --save-script` on any existing session —
68+
unarmed, ARMED, ABORTED, or PUBLISHED — is rejected before app dispatch; a plain later `open` is allowed,
69+
causing ARMED to become ABORTED while leaving ABORTED/PUBLISHED unchanged.
70+
`close --save-script[=<path>]` in ABORTED or PUBLISHED is rejected before platform close or filesystem
71+
work, so the caller can retry with plain `close`. Plain `close` tears down ABORTED/PUBLISHED without
72+
writing; closing an unpublished ARMED recording retains the existing close-time publication behavior. A
73+
fresh session is the only re-arming boundary.
6274

6375
This lifecycle is distinct from ADR 0012's repair transaction. `session save-script` rejects a session
6476
with `saveScriptBoundary` set and directs the caller to finish or abort the repair through its existing
@@ -68,19 +80,29 @@ commits a healed slice, writes `# agent-device:heal-complete`, or changes repair
6880
### Destination readiness and replay handoff
6981

7082
The destination is an authored postcondition, not the last navigation action. Before publication, the
71-
recorded suffix after the last mutating action must contain a target-bearing `wait` for a landmark that
72-
identifies the ready destination screen. A duration wait or `wait stable` alone does not qualify, though
73-
`wait stable` may follow the landmark wait. `session save-script` refuses publication without this
74-
**destination guard** and tells the author to record one. V1 does not infer a screen identity from a
75-
snapshot or synthesize an implicit guard.
76-
77-
The initial guard is selector-level. `wait` does not yet carry ADR 0012 `target-v1` evidence through
78-
recording and replay verification, so the guard proves that an element matching its selector exists, not
79-
that it is the same landmark element observed while authoring. Authors must choose a selective
80-
destination-specific landmark; a reshuffled screen containing the same weak label elsewhere can
81-
false-pass. [#1349](https://github.com/callstack/agent-device/issues/1349) extends identity annotations
82-
and replay-time refusal to target-bearing waits, at which point the same guard gains recorded-landmark
83-
identity verification without changing the ADR 0016 artifact shape.
83+
recorded suffix after the last mutating action must contain a `wait` whose target is a portable selector
84+
or selector chain identifying a landmark on the ready destination screen. A duration wait, `wait stable`,
85+
or `wait @ref` does not qualify, though `wait stable` may follow the landmark wait. The publisher validates
86+
the serialized guard before filesystem work and never relies on repair-only bare-ref rejection.
87+
`session save-script` refuses publication without this **destination guard** and tells the author to
88+
record one. V1 does not infer a screen identity from a snapshot or synthesize an implicit guard.
89+
90+
The initial guard is selector-level. `wait` does not yet carry recorded-landmark identity through its
91+
polling resolution, so the guard proves that an element matching its selector exists, not that it is the
92+
same landmark element observed while authoring. Authors must choose a selective destination-specific
93+
landmark; a reshuffled screen containing the same weak label elsewhere can false-pass.
94+
[#1349](https://github.com/callstack/agent-device/issues/1349) owns the identity design for waits and
95+
remaining read-only steps. It must preserve polling: ADR 0012's current pre-action `target-v1`
96+
verification cannot be attached to a wait unchanged because a not-yet-present landmark is the expected
97+
starting condition. Any identity check for a wait happens after its selector resolves, before the wait
98+
reports success, or uses a distinct guard-specific mechanism.
99+
100+
The phrase "last mutating action" is derived from a request-sensitive recording-effect trait on the
101+
central `CommandDescriptor`, required for every command that records session actions and guarded by a
102+
completeness test. It is not a publisher-local command-name set. The trait distinguishes app-state
103+
mutation from observation for subcommands such as read-only versus mutating `find`, `keyboard`, and
104+
`alert` actions; the existing conservative `refFrameEffect: may-invalidate` classification is not precise
105+
enough for this boundary.
84106

85107
On consumption, a script without `close` preserves the existing replay behavior: the named session stays
86108
active and the successful `ReplayCommandResult` returns its `session` id. The caller binds subsequent
@@ -103,8 +125,10 @@ credentials that are safe to persist. CLI help must state this warning next to t
103125

104126
The published `.ad`:
105127

106-
- contains one recorded `open` and every recordable action through the publication request;
107-
- contains a destination guard after its last mutating action;
128+
- contains exactly one recorded `open` as its first action and every recordable action through the
129+
publication request;
130+
- contains a portable selector/selector-chain destination guard after its last descriptor-classified
131+
mutating action;
108132
- does not append or serialize `session save-script` or `close`;
109133
- uses the ordinary session context header, selector-chain optimization, and canonical `target-v1`
110134
annotations captured while ARMED;
@@ -114,9 +138,10 @@ The published `.ad`:
114138
`--force` authorizes atomic replacement.
115139

116140
The success response identifies the final path and session and reports the number of serialized actions.
117-
The command must fail before writing when there is no active session, recording was not armed, no
118-
recorded `open` or destination guard exists, or a repair transaction owns the session. Every failure
119-
explains the recovery action; none degrades to `{ written: false }` success.
141+
The command must fail before writing when there is no active session, recording was not armed, the
142+
recording is ABORTED or PUBLISHED, the history does not contain exactly one initial `open`, no portable
143+
destination guard exists, or a repair transaction owns the session. Every failure explains the recovery
144+
action; none degrades to `{ written: false }` success.
120145

121146
### Surface and naming
122147

@@ -138,6 +163,8 @@ executing that script, not the artifact being saved.
138163
the destination is known only when the caller publishes.
139164
- Normal unarmed interactions keep their current fast paths and retention behavior.
140165
- A successful active-session publication cannot collide with a later close-time auto-save.
166+
- A second successful open abandons the in-flight artifact instead of silently publishing a multi-open
167+
bootstrap; authoring resumes only in a fresh session.
141168
- Intermediate lifecycle-free fragments, entry guards, include semantics, composed digests, and shared
142169
fragment pinning remain entirely under #1336.
143170
- Secret-bearing authoring remains unsafe until #1348; the initial workflow is limited to journeys that
@@ -167,7 +194,10 @@ executing that script, not the artifact being saved.
167194
- An unarmed session refuses publication before filesystem work and names `open --save-script` as the
168195
recovery.
169196
- An armed session without a destination guard refuses publication before filesystem work and names a
170-
target-bearing `wait` as the recovery.
197+
selector-targeted `wait` as the recovery; `wait @ref`, duration waits, and `wait stable` are covered
198+
refusal cases.
199+
- A second successful plain `open` transitions ARMED to ABORTED and disables all publication, while
200+
`open --save-script` on an existing session is rejected before app dispatch.
171201
- An armed session publishes `open` plus target-annotated actions without `close`, returns the final path,
172202
remains active, and can continue accepting commands.
173203
- The artifact replays from a cold start, completes its destination guard, returns the live session id,
@@ -176,8 +206,14 @@ executing that script, not the artifact being saved.
176206
unresolved `@ref` reaches disk; the destination guard remains selector-level until #1349 lands.
177207
- Existing-target refusal preserves the original bytes; `--force` replaces atomically; a failed publish
178208
remains retryable.
179-
- Closing after successful publication performs no second write, while closing an unpublished ARMED
180-
ordinary recording preserves current close-time publication behavior.
209+
- After PUBLISHED, later ordinary actions remain usable, repeated `session save-script` fails, plain
210+
`open --relaunch` cannot re-arm, and `open --save-script` is rejected before app dispatch.
211+
- In ABORTED/PUBLISHED, `close --save-script[=<other>]` is rejected before platform close and plain
212+
`close` tears down without writing; closing an unpublished ARMED recording preserves current
213+
close-time publication behavior.
214+
- Descriptor completeness tests classify every recordable request's mutation effect, including
215+
request-sensitive read-only/mutating subcommands, and destination-guard ordering consumes only that
216+
trait.
181217
- Repair-armed sessions refuse this action without changing repair state.
182218
- CLI help warns that literal `fill` inputs are persisted and tells authors not to record secret-bearing
183219
journeys until #1348's parameterized-input mechanism is available.

0 commit comments

Comments
 (0)