pi-browser-taskbar-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e3c308da05c9c6e2e26a77a466a549454f8444b128b884596f882e3f69c3c03b
4
+ data.tar.gz: 12a53b0f9467166cc976942baf5ad654378f3f9a26b097f7f8962b7f94fdbb36
5
+ SHA512:
6
+ metadata.gz: f723429595fd26ca8142908255baced015e764f114caa986468d1344332eb5063b9cd1084553bf988a32d0a2f53be7a66a411b7eec412619188c6e4bbe1d617b
7
+ data.tar.gz: bf76fe4cef80f09161b45ef674a77e30c1a87d77bea2218a9c5163866642ade465264bf2324f5a82af3ec9d69ecf0440e4a3eb5851a7372c64f2c7ed2fa2d694
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Rails adapter changelog
2
+
3
+ ## 0.1.0 - Unreleased
4
+
5
+ - Add the development-only isolated engine, native CSRF/access boundary, ERB installer, and package-served Browser Client.
6
+ - Add the external checkout-scoped broker with exclusive election, verified Unix-socket identity, fake-Pi task completion, and atomic admission.
7
+ - Add bounded structural page capture and independent normalized-context validation.
8
+ - Add up to eight ordered advisory focus marks with stable selectors and fair bounded detail.
9
+ - Attach conservative project-relative ERB template hints from Rails' native rendered-template annotations.
10
+ - Add idempotent task cancellation through the external broker with stable HTTP errors and settled terminal state.
11
+ - Add confirmed in-process session reset through the broker with retained-state rejection and process-recovery fallback.
12
+ - Report Pi progress, bounded UTF-8 output, cancelled extension dialogs, and safe recoverable protocol failures.
13
+ - Recover from task and abort deadlines, startup failures, and process exits with bounded process-tree replacement.
14
+ - Preserve one verified checkout-scoped broker across Rails reload, forked/clustered/phased Puma processes, and concurrent servers, with idle-only grace and graceful process-tree cleanup.
15
+ - Reconcile canonical state across tabs with bounded polling recovery and preserve one taskbar host across full and Turbo navigation without stale marks.
16
+ - Enforce startup-fixed server configuration and fail-closed native development activation.
17
+ - Secure exact remote-host access, native CSRF responses, filtered diagnostics, and persistent trusted-network HTTP warnings.
18
+ - Make Rails installation atomically preflighted, checksummed, explicitly selectable, mounted before host fallbacks, idempotently updatable, and all-or-nothing reversible through Rails' native inverse generator.
19
+ - Complete the Shadow DOM Corner composer with stable lifecycle text, native accessible controls, one live region, keyboard marking and focus return, narrow/200% reflow, and reduced-motion behavior.
20
+ - Verify every stable Rails minor from 7.1 through 8.1 at its minimum Ruby, plus newest Rails on newest MRI, in generated artifact-installed applications and supported Puma modes.
21
+ - Run artifact-isolated Chromium, Firefox, and WebKit Turbo/lifecycle/keyboard flows with taskbar-owned automated accessibility evidence.
22
+ - Enforce lockstep versions, deterministic artifact bytes, strict package contents, clean artifact-installed examples, manual-evidence prerequisites, checksums, and source/workflow provenance before release preparation.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dave Lens
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # Pi Browser Taskbar Rails
2
+
3
+ Development-only Rails adapter for conventional Rails 7.1 through 8.1 ERB applications on MRI/Linux.
4
+ The gem includes the Browser Client, so consuming applications need no Node or JavaScript package
5
+ manager for the taskbar. Exact release-blocking Ruby/Rails rows and Puma modes are maintained in the
6
+ repository's compatibility matrix. Shared wire behavior is defined only by the packaged
7
+ [Conformance Contract](contract/docs/index.md).
8
+
9
+ ## Dependency
10
+
11
+ Add the matching product version to the development group:
12
+
13
+ ```ruby
14
+ group :development do
15
+ gem "pi-browser-taskbar-rails", "~> 0.1.0", require: "pi/browser/taskbar/rails"
16
+ end
17
+ ```
18
+
19
+ ## Installer
20
+
21
+ Install into the conventional application layout and default mount:
22
+
23
+ ```sh
24
+ bin/rails generate pi_browser_taskbar:install
25
+ ```
26
+
27
+ For a nonstandard application, select the ERB layout and mount explicitly:
28
+
29
+ ```sh
30
+ bin/rails generate pi_browser_taskbar:install \
31
+ --layout app/views/layouts/internal.html.erb \
32
+ --mount /internal/pi
33
+ ```
34
+
35
+ The generator plans every edit before writing and refuses ambiguous layouts, unsupported templates,
36
+ route conflicts, unclear head markup, and edited generated sections.
37
+
38
+ ## Generated integration
39
+
40
+ The generator owns checksummed sections in a development initializer, `config/routes.rb`, and each
41
+ selected ERB layout. The initializer activates the isolated engine and Rails filename annotations;
42
+ the layout helper emits package-served assets and bounded bootstrap data. Rails processes connect to
43
+ the package broker rather than owning Pi themselves.
44
+
45
+ ## Configuration
46
+
47
+ Use Rails-native syntax in the generated development initializer:
48
+
49
+ ```ruby
50
+ Pi::Browser::Taskbar::Rails.configure do |config|
51
+ config.mount_path = "/dev/pi-browser-taskbar"
52
+ config.enabled = true
53
+ config.allowed_hosts = []
54
+ config.executable = "pi"
55
+ config.project_root = Rails.root.to_s
56
+ config.task_timeout = 1_800
57
+ end
58
+ ```
59
+
60
+ The matching `PI_BROWSER_TASKBAR_*` environment fallbacks and the normative field semantics are in
61
+ [Server-owned configuration and activation](contract/docs/index.md#server-owned-configuration-and-activation).
62
+ Restart Rails after a configuration change.
63
+
64
+ ## Verification
65
+
66
+ Start Rails in development, open a host page, and confirm the lower-left **Page task** launcher is
67
+ present. Run a whole-page task, mark an element for a focused task, stop a running task, start a new
68
+ session, and follow a Turbo navigation. Repository contributors run `bin/verify` at the monorepo root; `examples/rails/` names the same
69
+ scenarios and stable selectors.
70
+
71
+ ## Diagnosis
72
+
73
+ If the launcher is absent, confirm Rails is in development, the generated route and layout markers
74
+ remain intact, and the initializer has not disabled the adapter. If it is unavailable, run `pi
75
+ --mode rpc` from the configured project root and inspect only the adapter's safe diagnostics. For
76
+ host, CSRF, source-hint, busy, and cancellation symptoms, use the packaged [troubleshooting guide](docs/troubleshooting.md).
77
+
78
+ ## Updates
79
+
80
+ Update the gem within the matching product version, then rerun the generator. It reports a current
81
+ installation or updates only recognized checksummed content. Changing an installed mount requires
82
+ uninstalling first.
83
+
84
+ ## Security
85
+
86
+ Rails supplies native session CSRF, normalized host/peer information, filtered request parameters,
87
+ and development activation. The shared threat model and remote-access rules are in the packaged
88
+ [security guide](docs/security.md); normative invariants remain in the
89
+ [Conformance Contract](contract/docs/index.md#remote-development-access-and-diagnostics).
90
+
91
+ ## Uninstall
92
+
93
+ ```sh
94
+ bin/rails destroy pi_browser_taskbar:install
95
+ ```
96
+
97
+ Uninstall preflights all owned sections and removes all recognized content or nothing. It reports the
98
+ development dependency for manual removal and does not delete credentials, sessions, broker runtime
99
+ artifacts, or unrelated host code.
100
+
101
+ ## Changelog and example
102
+
103
+ See the [Rails changelog](CHANGELOG.md) and repository path `examples/rails/` for the executable Rails
104
+ ERB/Turbo example.
105
+
106
+ ## Matching-version contract
107
+
108
+ This adapter version is `0.1.0`. Use the same product version shown by the Phoenix adapter and the
109
+ root `VERSION`; the packaged [Conformance Contract](contract/docs/index.md) is the offline normative
110
+ reference for both adapters.
@@ -0,0 +1,381 @@
1
+ # Conformance Contract v1
2
+
3
+ ## Authority and scope
4
+
5
+ This directory is the normative shared source for cross-adapter wire behavior. Schemas and fixtures
6
+ are test-time authority; they are not a Ruby or Elixir runtime dependency and do not generate
7
+ adapter implementation code.
8
+
9
+ The executable foundation establishes fixture formats and the first task/context shapes. Both
10
+ whole-page adapter tracer bullets execute the same black-box HTTP scenario from built artifacts in
11
+ clean conventional hosts against a deterministic fake Pi peer. Later tracer bullets extend these
12
+ scenarios without moving authority into either adapter.
13
+
14
+ ## Versioning
15
+
16
+ Every contract artifact declares integer `contract_version: 1`. Product package versions are
17
+ independently lockstepped by the root `VERSION` file. A contract version changes only when a client
18
+ must branch on incompatible wire behavior.
19
+
20
+ ## Server-owned configuration and activation
21
+
22
+ Native framework development mode is an immutable activation boundary. Outside Rails development or
23
+ a Phoenix `Mix.env() == :dev` compilation, adapters mount no routes, emit no taskbar assets, and own
24
+ no Pi process even when `enabled` is configured true. In development, `enabled` defaults to true; an
25
+ explicit false compiles or boots routes, assets, and Pi ownership out together. Changes take effect
26
+ only after the framework's required restart or recompile.
27
+
28
+ The complete shared semantic configuration surface is `enabled`, `allowed_hosts`, `executable`,
29
+ `project_root`, and `task_timeout`. Framework-native configuration has precedence over matching
30
+ `PI_BROWSER_TASKBAR_ENABLED`, `PI_BROWSER_TASKBAR_ALLOWED_HOSTS`,
31
+ `PI_BROWSER_TASKBAR_EXECUTABLE`, `PI_BROWSER_TASKBAR_PROJECT_ROOT`, and
32
+ `PI_BROWSER_TASKBAR_TASK_TIMEOUT` environment fallbacks, which have precedence over defaults. The
33
+ defaults are enabled in development, an empty remote-host allowlist, `pi` resolved from `PATH`, the
34
+ canonical host project root, and 1,800 seconds. Timeout values are integer seconds from 60 through
35
+ 86,400. Environment host lists are comma-separated; native host lists are framework lists.
36
+
37
+ When enabled, malformed booleans, host entries, executable values, project roots, and timeouts fail
38
+ startup with the affected setting name. A disabled adapter need not validate its inactive settings.
39
+ Configuration is resolved, canonicalized, and fixed at startup; it is not exposed as a browser API.
40
+ Browser requests cannot select or override the executable, `--mode rpc` arguments, inherited server
41
+ environment, working directory, timeout, protocol bounds, route behavior, or security behavior.
42
+
43
+ Adapters resolve the configured project root to an existing canonical directory and spawn the
44
+ configured executable directly, without a shell, as `executable --mode rpc` in that directory. Pi
45
+ inherits the development server environment unchanged. A missing or non-executable command produces
46
+ only the sanitized unavailable session state; the optional adapter failure does not prevent either
47
+ host application from booting.
48
+
49
+ ## Remote development access and diagnostics
50
+
51
+ Every route and asset request must pass both the framework-normalized request host and client peer
52
+ checks. A loopback peer may use `localhost`, a syntactically valid subdomain of `.localhost`,
53
+ `127.0.0.1`, `::1`, or an explicitly configured exact allowed host. A non-loopback peer is denied
54
+ unless the normalized host exactly matches an entry in a non-empty `allowed_hosts` list. Host
55
+ normalization lowercases DNS names, removes their optional final dot, and canonicalizes IP literal
56
+ spelling before exact comparison.
57
+
58
+ Allowed-host entries are bare exact DNS names or IPv4/IPv6 literals only. Schemes, ports, paths,
59
+ wildcards, suffix patterns, scoped IP literals, empty list entries, and empty comma-separated entries
60
+ are startup errors naming `allowed_hosts`. The empty list is the safe default and enables no remote
61
+ access. There is no CIDR, wildcard, remote-access boolean, or suffix matching.
62
+
63
+ Rails uses only `request.host` and `request.remote_ip`; Phoenix uses only `conn.host` and
64
+ `conn.remote_ip`. Those values already reflect the host application's framework and trusted-proxy
65
+ configuration. Neither adapter reads `Forwarded`, `X-Forwarded-Host`, `X-Forwarded-For`, or similar
66
+ headers itself. Proxy deployments must configure the host framework's trusted proxies rather than
67
+ expecting a second taskbar-specific forwarding model.
68
+
69
+ Mutations remain protected by each framework's native session-bound CSRF check and return the stable
70
+ `invalid_csrf` code with a safe message when rejected. Adapter responses add no permissive CORS
71
+ headers. Browser validation failures return `invalid_task` and a fixed safe message rather than
72
+ copying attacker-controlled fields or local details.
73
+
74
+ A non-empty allowlist causes the server bootstrap to expose only a boolean remote-access warning
75
+ state, never the host list. When that state is active on an HTTP page, the taskbar persistently warns
76
+ that remote HTTP is unencrypted and is suitable only on a trusted network. HTTPS does not show the
77
+ unencrypted-transport warning. This development mode provides neither transport encryption nor host
78
+ user authentication.
79
+
80
+ Adapters do not log browser context, prompts, commands, inherited environment, absolute paths, raw
81
+ Pi/provider errors, stderr, or protocol records. Rails additionally registers host parameter filters
82
+ for `prompt` and `context`; Phoenix's forwarded Plug reads the body without controller parameter
83
+ logging. Browser-visible task/session diagnostics and adapter-generated errors use bounded fixed
84
+ messages and stable codes only.
85
+
86
+ ## Shared HTTP API and stable errors
87
+
88
+ Both adapters expose the same JSON API below their generated, application-owned mount base:
89
+
90
+ - `GET /state` returns the complete current snapshot;
91
+ - `POST /tasks` admits one normalized task or rejects it atomically;
92
+ - `DELETE /tasks/:id` stops the retained task under the cancellation rules below;
93
+ - `POST /session/reset` performs the confirmed session switch described below.
94
+
95
+ Successful reads and mutations return the complete canonical snapshot and `Cache-Control: no-store`.
96
+ Mutation requests are never retried; an ambiguous result is reconciled through `GET /state`. Snapshot
97
+ session states are `starting`, `ready`, `busy`, `resetting`, and `unavailable`; retained task states
98
+ are `running`, `cancelling`, `completed`, `failed`, and `cancelled`. Clients branch only on contract
99
+ version, these enums, and the stable codes below, never presentation messages.
100
+
101
+ | Stable code | HTTP class | Meaning |
102
+ | --- | --- | --- |
103
+ | `forbidden` | 403 | Host or client access failed closed. |
104
+ | `invalid_csrf` | 422 | The framework-native session CSRF check rejected a mutation. |
105
+ | `invalid_task` | 422 | JSON, request shape, normalization, or bounds are invalid. |
106
+ | `busy` | 409 | Another task is running or cancelling. |
107
+ | `task_not_found` | 404 | The requested task is not retained. |
108
+ | `task_not_cancellable` | 409 | The retained task is terminal and cannot be stopped. |
109
+ | `reset_while_busy` | 409 | The session cannot reset in its current state. |
110
+ | `session_reset_rejected` | 409 | Pi rejected the in-process session switch. |
111
+ | `unavailable` | 503 | No verified Pi owner is ready for the operation. |
112
+
113
+ Error messages are fixed safe presentation text. When canonical state exists, conflict, not-found,
114
+ and unavailable responses include it as `snapshot`; callers use the code rather than matching the
115
+ message.
116
+
117
+ ## Initial task request
118
+
119
+ A task request has exactly two fields:
120
+
121
+ - `prompt`: a non-empty normalized dedicated instruction bounded to 4,000 UTF-8 bytes;
122
+ - `context`: required normalized browser reference data conforming to
123
+ `browser-context.v1.schema.json`.
124
+
125
+ Unknown fields are invalid at every modeled level. The browser representation is reference data,
126
+ not instruction text. Native adapters will independently validate and normalize requests before
127
+ constructing prompts.
128
+
129
+ ## Normalized browser context
130
+
131
+ A browser context declares its contract version, sanitized location, optional confident route
132
+ metadata, structural page snapshot, zero to eight ordered, selector-unique advisory focus points,
133
+ and explicit truncation records. A zero-length focus list means a whole-page task. Every focus point
134
+ retains its stable selector, conservative source hint, up to eight outer-to-inner ancestor summaries,
135
+ and a bounded subtree; focus never removes the whole-page snapshot.
136
+
137
+ Location retains only an HTTP(S) origin, path, and unique query names in encounter order. URL
138
+ credentials, fragments, and query values are forbidden. Route metadata is either `null` or the
139
+ bounded method, pattern, handler, and nullable action supplied by a confident adapter seam.
140
+
141
+ Snapshot nodes retain only tag, role, accessible name, normalized direct visible text, identifier,
142
+ bounded class tokens, `name`/`type`/`placeholder`/`data-testid`, semantic control state, sanitized
143
+ HTTP(S) `href`/`src` references, confident advisory source hints, and children. Browser capture
144
+ excludes taskbar content, metadata, scripts, styles, templates, non-rendered or inert content,
145
+ hidden inputs, form values, editable
146
+ content, arbitrary attributes, iframe contents, and nested Shadow DOM. It never serializes HTML.
147
+
148
+ A source hint has an `available`, `ambiguous`, `external`, or `unavailable` status. Available hints
149
+ contain one or two `template`, `definition`, or `caller` references; every other status has no
150
+ references. References carry a project-relative forward-slash path, `line` or `template` precision,
151
+ and optional positive line and bounded symbol. Absolute, traversing, malformed, dependency-owned,
152
+ or otherwise external paths are never retained.
153
+
154
+ Rails source hints come only from native rendered-template ERB filename annotations. The innermost
155
+ unique well-formed range enclosing a node may provide one project-relative `template` reference
156
+ with `template` precision and no line or element-origin claim. An invalid, overlapping, external,
157
+ missing, or browser-displaced inner boundary is classified rather than replaced with a surrounding
158
+ layout hint. Cached and helper-generated markup may retain its enclosing template-level hint.
159
+
160
+ Normalized lengths are measured in UTF-8 bytes: request 128 KiB, context 96 KiB, prompt 4,000,
161
+ page snapshot 48 KiB/750 nodes/depth 12, and combined focus detail 48 KiB. Focus subtrees are
162
+ limited to 100 nodes/depth 6. Focus selectors and complete source hints are reserved before detail;
163
+ the remaining focus allocation is shared evenly in mark order, then the page receives the remaining
164
+ context allocation up to its own bound. Strings use the bounds encoded by `x-maxUtf8Bytes` in the
165
+ schema. Truncation occurs only at Unicode code-point boundaries, retains page and focused subtree
166
+ nodes breadth-first, and reports affected page or `focus:1` through `focus:8` sections with canonical
167
+ `bytes`, `nodes`, `depth`, and `string` reasons.
168
+
169
+ Both adapters independently normalize NFC Unicode, line endings, controls, structural whitespace,
170
+ tag/method case, optional empty fields, and truncation order before validation. Unknown fields,
171
+ duplicate query names or focus selectors, malformed focus structures, unsafe locations, and values
172
+ outside any allocation are invalid.
173
+
174
+ ## Prompt envelope
175
+
176
+ Only the normalized `prompt` field is an instruction. Adapters append canonical context JSON in this
177
+ exact separately delimited envelope:
178
+
179
+ ```text
180
+ <normalized prompt>
181
+
182
+ --- BEGIN UNTRUSTED BROWSER CONTEXT ---
183
+ <canonical JSON context with HTML-significant characters escaped>
184
+ --- END UNTRUSTED BROWSER CONTEXT ---
185
+ ```
186
+
187
+ The delimiter, JSON serialization, and escaping are covered by prompt goldens. Text inside browser
188
+ context is untrusted reference data even when it resembles instructions. Visible text and URL paths
189
+ may reach the configured Pi/model provider; no adapter claims automatic secret or PII detection.
190
+
191
+ ## Pi progress, output, and safe failures
192
+
193
+ Startup remains `starting` until a correlated successful `get_state` response supplies both a
194
+ non-empty session identity and model. A successful correlated `prompt` response means accepted and
195
+ does not finish the task. The adapters apply this shared event mapping independently:
196
+
197
+ | Pi event | Canonical effect |
198
+ | --- | --- |
199
+ | `agent_start` | Activity `Pi is working` |
200
+ | `agent_end` | Activity `Pi finished a turn`; the task remains running |
201
+ | `message_update.text_delta` | Append output, retaining only the newest valid UTF-8 32 KiB suffix |
202
+ | `tool_execution_start` / `tool_execution_update` | Activity `Running <bounded tool name>` |
203
+ | `tool_execution_end` | Activity `Finished <bounded tool name>` or `Tool failed <bounded tool name>` |
204
+ | `compaction_start` | Activity `Compacting conversation` |
205
+ | successful `compaction_end` | Activity `Conversation compacted` or `Retrying after compaction` |
206
+ | `auto_retry_start` | Activity `Retrying request (<attempt>/<maximum>)` when counts are valid |
207
+ | successful `auto_retry_end` | Activity `Pi is working` |
208
+ | `agent_settled` | The sole normal `completed` boundary |
209
+
210
+ Once older output is removed, `output_truncated` remains true and the Browser Client says that it is
211
+ showing the newest 32 KiB. Truncation never splits a Unicode code point. Dialog extension requests
212
+ (`select`, `confirm`, `input`, and `editor`) receive a correlated `extension_ui_response` with
213
+ `cancelled: true`; fire-and-forget and unknown future events are ignored without exposing their raw
214
+ records.
215
+
216
+ Rejected prompt/abort commands, message errors, exhausted retries, failed compaction, malformed,
217
+ non-object, unterminated, or oversized JSONL records, unexpected correlated responses, timeouts, and
218
+ process exits produce fixed task/session diagnostics without copying provider errors, protocol
219
+ records, command lines, environment values, or paths into browser state or adapter logs. Protocol
220
+ loss during an active task fails the retained task and replaces the Pi process before new work is
221
+ accepted. A message, retry, or compaction terminal error waits for `agent_settled` before releasing
222
+ the busy session. Unknown future event types remain forward-compatible and do not change state.
223
+
224
+ The progress and failure RPC transcripts, native runtime tests, packaged clean-host flows, and root
225
+ semantic comparison exercise the same mapping in Rails and Phoenix while normalizing only opaque
226
+ identities and timestamps.
227
+
228
+ ## Task cancellation
229
+
230
+ `DELETE /tasks/:id` sends one correlated Pi `abort` command for a retained `running` task and returns
231
+ the complete canonical `cancelling` snapshot with HTTP 202. The session remains `busy`, the task has
232
+ no `finished_at`, and completion waits for Pi's `agent_settled` event. At that boundary the task
233
+ becomes `cancelled`, receives `finished_at` and `Task stopped` activity, and the session returns to
234
+ `ready`.
235
+
236
+ Cancellation is idempotent: repeating the request while `cancelling` returns the same 202 lifecycle
237
+ without sending another abort, and repeating it after `cancelled` returns the retained snapshot with
238
+ HTTP 200. A different or forgotten ID returns `404 task_not_found`; a retained `completed` or
239
+ `failed` task returns `409 task_not_cancellable`. Error responses include the current snapshot and
240
+ all cancellation mutations remain protected by each framework's native CSRF and access checks.
241
+ Stopping is not transactional and cannot roll back file changes Pi already made.
242
+
243
+ ## Timeout and process recovery
244
+
245
+ A configured task timeout fails the active task with retained bounded output and safe diagnostics,
246
+ then replaces Pi before accepting more work. Cancellation has its own bounded deadline: if Pi does
247
+ not reach `agent_settled`, the task becomes `cancelled` with safe diagnostics and the adapter replaces
248
+ the process rather than claiming the old conversation survived.
249
+
250
+ Replacement sends TERM to the owned Pi process group, waits a bounded interval, escalates to KILL,
251
+ and reaps the child before starting a replacement. The public session identity and model are cleared
252
+ while replacement is `starting`; a successful startup exposes a new opaque identity. Unexpected exit
253
+ while busy preserves terminal task evidence, while idle exit retains no invented task. Startup and
254
+ replacement attempts are bounded, ending in `unavailable` when Pi is missing, non-executable, or
255
+ repeatedly fails to establish a valid startup state. These failures do not terminate the host
256
+ application.
257
+
258
+ Equivalent Rails and Phoenix fake-Pi runtime scenarios cover task timeout, missed abort settlement,
259
+ startup failure, active and idle crashes, successful replacement, exhausted replacement, and owned
260
+ child cleanup.
261
+
262
+ ## Rails broker topology
263
+
264
+ Rails serving processes never own Pi. One gem-packaged external broker owns Pi and canonical task and
265
+ session state for the canonical checkout path and OS user. A user-private runtime directory, exclusive
266
+ OS lock, Unix socket, atomic endpoint metadata, protocol version, canonical identity, and fresh
267
+ instance token make concurrent single-process, threaded, preloaded, clustered, phased, and separate
268
+ server invocations converge on the same verified broker. An incompatible or unverifiable live broker
269
+ is not terminated, and failure to establish a verified connection is unavailable rather than a
270
+ process-local fallback.
271
+
272
+ Each Rails process lazily retains one PID-aware client outside application reload paths. Reloads keep
273
+ the connection; a fork discards only the child's inherited socket and mutex state before lazy
274
+ reconnection. The broker remains alive while a client is connected or work is active. Its five-minute
275
+ grace begins only after both conditions become false, so disconnected work settles before the timer
276
+ starts. Graceful broker shutdown closes and reaps the Pi process group with bounded TERM-to-KILL
277
+ cleanup; a broker or Pi replacement truthfully starts a new conversation.
278
+
279
+ ## Browser reconciliation and host navigation
280
+
281
+ Each mounted Browser Client reads the complete canonical snapshot independently. It polls every 500
282
+ milliseconds while the session is `starting`, `resetting`, or `busy`, or the task is `running` or
283
+ `cancelling`, and every 30 seconds while stable. Failed reads preserve the last rendered snapshot
284
+ and retry with exponential delays bounded between one and 30 seconds. One isolated network failure
285
+ stays quiet; repeated network failures or other read errors show a retry indication. Returning
286
+ browser visibility triggers an immediate read.
287
+
288
+ A task, cancellation, or reset mutation is sent exactly once. Any HTTP failure or ambiguous network
289
+ result is reconciled with `GET /state`; the client never retries the mutation. This makes admission,
290
+ progress, output, cancellation, reset, terminal feedback, and busy controls converge across tabs
291
+ without browser-to-browser coordination.
292
+
293
+ The client appends one Shadow DOM host as a direct body child, marks it permanent for partial
294
+ navigation, and reuses an existing host if packaged scripts execute again. Before a body replacement
295
+ it moves that host into the incoming body; navigation completion refreshes canonical state. Full
296
+ controller/document navigation mounts one new host and reads state. Live page patches leave the host
297
+ outside their owned roots. Removed or selector-displaced marked elements are discarded with their
298
+ outlines; source hints are captured again from the current DOM at submission rather than retained
299
+ across patches. Draft text remains browser-local while a surviving partial-navigation host is reused.
300
+
301
+ Current-browser acceptance exercises two tabs, ambiguous submission, shared busy,
302
+ progress, cancellation, and output, plus idle/active partial navigation, live patching, and full
303
+ navigation remounts.
304
+
305
+ ## Corner composer and taskbar accessibility
306
+
307
+ The Browser Client owns one lower-left launcher and compact composer entirely inside its Shadow DOM.
308
+ It uses only native controls, inline taskbar markup, system fonts, and taskbar styles. The open order is
309
+ Pi identity/model, task focus and optional removable marks, task instruction, lifecycle feedback, then
310
+ footer status and the task action. Zero marks says **Whole page**; one through eight marks remain
311
+ advisory focus points with whole-page surroundings.
312
+
313
+ The stable visible states are **Connecting**, **Ready**, **Working**, **Finished**, **Stopped**, and
314
+ **Unavailable**. They are exposed as text and programmatic state, not color alone. One atomic polite
315
+ live region announces changed activity and terminal results; visible errors use that same announcer so
316
+ status, activity, and error elements do not produce duplicate live regions. Bounded output remains
317
+ keyboard-scrollable and labelled, and a stopped task retains the warning that existing file changes
318
+ were not rolled back.
319
+
320
+ Opening moves focus to the labelled instruction field; collapse or `Escape` returns focus to the
321
+ launcher. **Mark element** exposes pressed state and visible pointer/focus guidance: a keyboard user
322
+ may focus a host-page element and press Enter or Space, while `Escape` cancels and returns focus to
323
+ **Mark element**. Removing or clearing marks returns focus to a surviving remove control or **Mark
324
+ element**. Native buttons cover submission and stopping. Active work disables editing, marking, and
325
+ clearing while leaving **Stop task** available.
326
+
327
+ The composer fits the available narrow viewport, caps its block size so content remains scrollable at
328
+ 200% zoom, and removes animation and transition effects under `prefers-reduced-motion: reduce`.
329
+ Automated acceptance extracts each Browser Client from the built gem or Hex archive and runs both
330
+ equivalent example surfaces in current Playwright Chromium, Firefox, and WebKit. It covers every
331
+ material lifecycle state, whole-page and focused tasks, mark removal/clear, progress/output, stop,
332
+ unavailable/network recovery, cross-tab reconciliation, Turbo navigation,
333
+ LiveView navigation/patching, taskbar-owned axe results, names/states/live regions, keyboard focus,
334
+ Shadow DOM isolation, desktop/narrow reflow, 200% CSS-zoom reflow emulation, and reduced motion. Artifact hashes, exact engine
335
+ versions, scope, and scenarios form deterministic build evidence.
336
+
337
+ The WCAG 2.2 AA target and automated checks apply only to the Shadow-DOM taskbar interface; they
338
+ neither test nor claim accessibility for the host application.
339
+
340
+ ## Session reset
341
+
342
+ `POST /session/reset` is accepted only while the session is `ready`; a running, cancelling, or
343
+ already-resetting session returns `409 reset_while_busy` with the unchanged snapshot. An accepted
344
+ request enters `resetting`, sends Pi's supported correlated `new_session` command, then sends
345
+ `get_state` and returns HTTP 202 only after the replacement is confirmed `ready`. The public session
346
+ identity changes and retained task feedback is cleared.
347
+
348
+ Pi reports an extension veto as a successful `new_session` response with `data.cancelled: true`.
349
+ Adapters return `409 session_reset_rejected` and preserve the old session identity and retained task
350
+ snapshot exactly. A failed RPC command, invalid confirmation, protocol failure, or process exit during
351
+ the switch invokes process replacement as recovery; an ordinary accepted or rejected switch does not
352
+ replace the healthy process.
353
+
354
+ ## Fixture manifest
355
+
356
+ `fixtures/manifest.json` is the only fixture registry. Each entry identifies a schema, a repository-
357
+ relative JSON file, and whether validation must succeed. An entry expecting rejection must include
358
+ an error fragment and is considered passing only when the validator rejects it for that reason.
359
+ This prevents an invalid fixture from becoming inert sample data.
360
+
361
+ Negative fixtures prove unknown/malformed fields, duplicate query names, URL credentials, path
362
+ query/fragment leakage, UTF-8 byte bounds, aggregate node bounds, and the two-source-reference limit
363
+ are rejected. Shared source fixtures exercise Phoenix template and definition/caller precision plus
364
+ all non-available classifications. Rich whole-page and prompt fixtures exercise every semantic node
365
+ section and the trusted-instruction/untrusted-context boundary.
366
+
367
+ ## Other executable formats
368
+
369
+ HTTP scenarios, prompt goldens, and Pi RPC transcript formats are versioned alongside browser
370
+ context. An HTTP scenario may name a contract task fixture as its request body. The deterministic
371
+ fake RPC peer replays transcript `receive`/`send` steps. Shared cancellation scenarios cover accepted
372
+ and repeated aborts, wrong and completed task IDs, and the settled terminal snapshot. Shared reset
373
+ scenarios and transcripts cover accepted, busy, and extension-rejected switches plus mandatory state
374
+ confirmation. Both packages execute these flows; root conformance rejects semantic drift except
375
+ opaque IDs and timestamps.
376
+ Only `prompt` is instructional; context is canonical JSON inside an explicitly untrusted delimiter,
377
+ with HTML-significant characters escaped. Visible text and URL paths may reach the developer's
378
+ configured Pi/model provider, so the taskbar is unsuitable for sensitive datasets.
379
+
380
+ See the [traceability index](../traceability.md) for normative parent sections and their eventual
381
+ acceptance seams.