@timurproko/a1 0.2.7-dev.792 → 0.2.7-dev.806

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.
Files changed (110) hide show
  1. package/dist/app/session-shell/clipboard-diagnostics.js +1 -1
  2. package/dist/app/session-shell/response-copy-helper.js +21 -6
  3. package/dist/app/session-shell/response-copy-protocol.d.ts +1 -1
  4. package/dist/app/session-shell/response-copy-transport.d.ts +3 -6
  5. package/dist/app/session-shell/response-copy-transport.js +60 -17
  6. package/dist/app/session-shell/session-shell-root.d.ts +2 -0
  7. package/dist/app/session-shell/session-shell-root.js +6 -1
  8. package/dist/app/session-shell/session-shell.d.ts +1 -0
  9. package/dist/app/session-shell/session-shell.js +22 -19
  10. package/dist/cli/tabs.js +1 -1
  11. package/dist/composition/owned-ui.d.ts +1 -0
  12. package/dist/composition/owned-ui.js +16 -3
  13. package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
  14. package/dist/features/resident-tab/shortcut-declarations.js +1 -0
  15. package/dist/foundation/launch-guardian/exit-watcher.d.ts +14 -0
  16. package/dist/foundation/launch-guardian/exit-watcher.js +94 -0
  17. package/dist/foundation/launch-guardian/main.d.ts +2 -1
  18. package/dist/foundation/launch-guardian/main.js +58 -6
  19. package/dist/foundation/process-containment/{native-guardian-containment.d.ts → addon-containment.d.ts} +7 -5
  20. package/dist/foundation/process-containment/addon-containment.js +139 -0
  21. package/dist/foundation/process-containment/addon-process-inspector.d.ts +11 -0
  22. package/dist/foundation/process-containment/addon-process-inspector.js +36 -0
  23. package/dist/foundation/process-containment/addon.d.ts +42 -0
  24. package/dist/foundation/process-containment/addon.js +57 -0
  25. package/dist/foundation/process-containment/artifact.d.ts +1 -1
  26. package/dist/foundation/process-containment/artifact.js +6 -5
  27. package/dist/foundation/process-containment/contracts.d.ts +2 -0
  28. package/dist/foundation/process-containment/index.d.ts +4 -4
  29. package/dist/foundation/process-containment/index.js +3 -3
  30. package/dist/foundation/process-containment/native-process-inspector.d.ts +2 -1
  31. package/dist/foundation/process-containment/native-process-inspector.js +10 -10
  32. package/dist/foundation/release/bootstrap.js +53 -16
  33. package/dist/foundation/release/index.d.ts +1 -1
  34. package/dist/foundation/release/index.js +1 -1
  35. package/dist/foundation/release/release-store.d.ts +12 -0
  36. package/dist/foundation/release/release-store.js +124 -2
  37. package/dist/foundation/resident-tabs/bounded-host-command.d.ts +3 -1
  38. package/dist/foundation/resident-tabs/bounded-host-command.js +6 -2
  39. package/dist/foundation/resident-tabs/bridge-client.d.ts +2 -0
  40. package/dist/foundation/resident-tabs/bridge-client.js +13 -0
  41. package/dist/foundation/resident-tabs/doctor.d.ts +0 -1
  42. package/dist/foundation/resident-tabs/doctor.js +4 -4
  43. package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +7 -2
  44. package/dist/foundation/resident-tabs/ensure-terminal-host.js +15 -11
  45. package/dist/foundation/resident-tabs/host-entry.d.ts +1 -0
  46. package/dist/foundation/resident-tabs/host-entry.js +17 -0
  47. package/dist/foundation/resident-tabs/index.d.ts +2 -2
  48. package/dist/foundation/resident-tabs/index.js +1 -1
  49. package/dist/foundation/resident-tabs/maintenance.d.ts +5 -4
  50. package/dist/foundation/resident-tabs/maintenance.js +32 -17
  51. package/dist/foundation/resident-tabs/release-holds.d.ts +3 -2
  52. package/dist/foundation/resident-tabs/release-holds.js +16 -7
  53. package/dist/foundation/resident-tabs/settings.d.ts +0 -1
  54. package/dist/foundation/resident-tabs/settings.js +1 -7
  55. package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +11 -15
  56. package/dist/foundation/resident-tabs/terminal-host-artifact.js +57 -32
  57. package/dist/foundation/session-writer-lock/index.d.ts +1 -3
  58. package/dist/foundation/session-writer-lock/index.js +8 -6
  59. package/dist/integrations/pi/components/shell-editor-autocomplete.js +2 -1
  60. package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -0
  61. package/dist/native/darwin-arm64/manifest.json +8 -6
  62. package/dist/native/darwin-arm64/process-guardian.node +0 -0
  63. package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
  64. package/dist/native/darwin-arm64/terminal-host.manifest.json +14 -13
  65. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  66. package/dist/native/darwin-x64/manifest.json +9 -7
  67. package/dist/native/darwin-x64/process-guardian.node +0 -0
  68. package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
  69. package/dist/native/darwin-x64/terminal-host.manifest.json +15 -14
  70. package/dist/native/darwin-x64/terminal-host.node +0 -0
  71. package/dist/native/linux-x64/manifest.json +8 -6
  72. package/dist/native/linux-x64/process-guardian.node +0 -0
  73. package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
  74. package/dist/native/linux-x64/terminal-host.manifest.json +14 -13
  75. package/dist/native/linux-x64/{terminal-host → terminal-host.node} +0 -0
  76. package/dist/native/win32-x64/manifest.json +8 -6
  77. package/dist/native/win32-x64/process-guardian.node +0 -0
  78. package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +1 -1
  79. package/dist/native/win32-x64/terminal-host.manifest.json +14 -13
  80. package/dist/native/win32-x64/{terminal-host.exe → terminal-host.node} +0 -0
  81. package/dist/product-identity.d.ts +1 -1
  82. package/dist/product-identity.js +1 -1
  83. package/dist/product-identity.json +3 -2
  84. package/dist/runtime-payload-inventory.json +1 -0
  85. package/dist/ui/settings/declarations.d.ts +0 -12
  86. package/dist/ui/settings/declarations.js +0 -10
  87. package/docs/architecture/boundaries.md +10 -9
  88. package/docs/architecture/process-guardian-provenance.md +17 -11
  89. package/docs/architecture/resident-tabs.md +36 -34
  90. package/docs/architecture/terminal-host-provenance.md +5 -3
  91. package/docs/architecture/toolchain.md +8 -6
  92. package/docs/ci-release-runbook.md +1 -1
  93. package/docs/openspec-archive-automation.md +23 -9
  94. package/docs/validation.md +6 -2
  95. package/package.json +5 -3
  96. package/dist/foundation/process-containment/darwin-process-inspector.d.ts +0 -5
  97. package/dist/foundation/process-containment/darwin-process-inspector.js +0 -7
  98. package/dist/foundation/process-containment/native-guardian-containment.js +0 -152
  99. package/dist/foundation/process-containment/windows-process-inspector.d.ts +0 -19
  100. package/dist/foundation/process-containment/windows-process-inspector.js +0 -56
  101. package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
  102. package/dist/native/darwin-arm64/process-guardian +0 -0
  103. package/dist/native/darwin-arm64/terminal-host +0 -0
  104. package/dist/native/darwin-x64/a1_session_lock.node +0 -0
  105. package/dist/native/darwin-x64/process-guardian +0 -0
  106. package/dist/native/darwin-x64/terminal-host +0 -0
  107. package/dist/native/linux-x64/a1_session_lock.node +0 -0
  108. package/dist/native/linux-x64/process-guardian +0 -0
  109. package/dist/native/win32-x64/a1_session_lock.node +0 -0
  110. package/dist/native/win32-x64/process-guardian.exe +0 -0
@@ -4,7 +4,7 @@ Status: implemented. Milestones 1–6 deliver resident tabs as the bare-`a1` rou
4
4
 
5
5
  ## Context
6
6
 
7
- Today bare `a1` runs as a chain: `bin/cli.js`, then a detached per-release **supervisor**, then the foreground **launch guardian**, then the native **process-guardian**, then `bin/ui.js`. That last process hosts one in-process Pi `AgentSessionRuntime` behind the owned UI. The process-guardian puts the UI in kill-on-close containment, so closing the terminal kills the agent. `launch-instance-lifecycle` requires that behavior and reserves "a separately specified explicit resident capability" for anything that must survive. This design defines that capability.
7
+ Today bare `a1` runs as a chain: `bin/cli.js`, then a detached per-release **supervisor**, then the foreground Node **launch guardian**, then its in-process native **process-guardian addon**, then `bin/ui.js`. That last Node process hosts one in-process Pi `AgentSessionRuntime` behind the owned UI. The addon puts the UI in kill-on-close containment, so closing the terminal kills the agent; no process-guardian executable sits in the chain. `launch-instance-lifecycle` requires that behavior and reserves "a separately specified explicit resident capability" for anything that must survive. This design defines that capability.
8
8
 
9
9
  The user set three product constraints:
10
10
 
@@ -14,12 +14,12 @@ The user set three product constraints:
14
14
 
15
15
  A structured, message-based agent protocol cannot meet (2), because an extension's custom UI component is code running inside the agent process. It also forces a second, unrelated system for (3). Every tab is therefore a **terminal session**: a pseudoterminal running the complete A1 owned UI in "tab mode". Native extension components retain the declared text-terminal behavior; inline image protocols are explicitly outside the first version and use the existing text fallback.
16
16
 
17
- The first version is deliberately narrower than the eventual product. It targets exactly Windows x64, Linux x64, and macOS arm64 (the targets A1 currently validates for the native process-guardian), with resident tabs on bare `a1` and argument-free `a1 solo` as the explicit direct-mode opt-out. It is delivered as six sequenced milestone changes. Restoring tabs after reboot or logout, another architecture or platform, automatic resident-cohort upgrades, arbitrary CLI tabs, and split layouts each require a later change and their own evidence.
17
+ The first version is deliberately narrower than the eventual product. It initially targeted Windows x64, Linux x64, and macOS arm64, with macOS x64 added by the later recorded decision; those are the targets A1 packages for the native addons. Resident tabs are the bare-`a1` route and argument-free `a1 solo` is the explicit direct-mode opt-out. Restoring tabs after reboot or logout, another architecture or platform, automatic resident-cohort upgrades, arbitrary CLI tabs, and split layouts each require a later change and their own evidence.
18
18
 
19
19
  Existing assets:
20
20
 
21
21
  - **`native/terminal-host`** is a working in-terminal host. It uses `libghostty-vt` pinned at `c5a21edf`, `portable-pty` 0.9.0 (ConPTY on Windows, Unix PTYs elsewhere), crossterm 0.29, and an A1-owned damage-aware frame composer. Its retained model and native encoders came from the fixed 2×2 proof; milestone 2 removed that interactive proof path and moved the model into the resident holder/attach roles. Three-platform certification remains separate from implementation.
22
- - **The supervisor, process-guardian, and launch guardian** provide the patterns for detached spawn, verified process identity (`--inspect-pid`), an endpoint probe with join-the-winner, and atomic metadata. The process-guardian already has Windows, macOS, and Linux implementations.
22
+ - **The supervisor, process-guardian addon, and launch guardian** provide the patterns for detached spawn, addon-backed process-start inspection, an endpoint probe with join-the-winner, and atomic metadata. The process-guardian addon has Windows, macOS, and Linux implementations.
23
23
  - **The 2×2 spike evidence** in [`evidence/terminal-host-spike/`](evidence/terminal-host-spike/) is historical. Its verdicts stay pending and are not evidence for this design.
24
24
 
25
25
  References:
@@ -53,8 +53,8 @@ The read-only review used A1 `develop` at `b42b3c9e`, PR #586 at `8fdb7361`, `E:
53
53
  ### Milestone 2 pinned launch contract (2026-10-11)
54
54
 
55
55
  - Bare `a1` from a terminal always requests the resident tab route; with piped or redirected standard input or output it runs the direct UI without probing the host, because the attach client needs a terminal to size and own. Tab enablement is not persisted or configurable; host discovery or verification failure uses the bounded direct single-agent fallback.
56
- - Tab settings `tabsMax` and `tabsAutoName` live in the flat `a1` object of the global agent `settings.json`; the former versioned profile document remains a read-only compatibility fallback while that object is absent.
57
- - The host artifact resolves to `dist/native/<platform>-<arch>/a1-terminal-host[.exe]`, with hermetic `A1_TERMINAL_HOST_PATH`; discovery may use `A1_TABS_ENDPOINT`.
56
+ - The `tabsAutoName` setting lives in the flat `a1` object of the global agent `settings.json`. A stored legacy `tabsMax` value is preserved as unknown compatibility data but never limits creation or appears in Settings.
57
+ - The host artifact resolves to `dist/native/<platform>-<arch>/terminal-host.node`, with hermetic `A1_TERMINAL_HOST_PATH` only when `A1_DEV_INSTANCE_ID` is present; the fixed role entry is `dist/foundation/resident-tabs/host-entry.js`, and discovery may use `A1_TABS_ENDPOINT`.
58
58
  - Tab children consume and erase `A1_TAB_ID`, `A1_TAB_TOKEN`, and `A1_TAB_ENDPOINT` before loading A1, Pi, extensions, or tools.
59
59
  - Client-scoped bridge detach binds `clientId` and `controllerRevision` captured when the command is admitted. The additive generation-1 `attachment` and `bridge.request` fixtures freeze that shape.
60
60
  - Launch fallback reasons are bounded to unsupported platform, no interactive terminal, missing artifact, failed probe, incompatible generation, unreadable registry, blocked recovery, unverified launch, and startup timeout. Fallback preserves resident records and presents exactly one direct-session notice.
@@ -66,10 +66,10 @@ The read-only review used A1 `develop` at `b42b3c9e`, PR #586 at `8fdb7361`, `E:
66
66
 
67
67
  - The native `core/server.rs` and `core/holder.rs` state machines are deterministic and I/O-free; the production holder uses the holder core for bounded input admission and ordered writer effects, and the production server uses the server core for heartbeat supervision. Blocking identity/termination work runs through the bounded worker shell with deadlines and stale-incarnation filtering.
68
68
  - Every attach, server, and holder role has an independent 15-second watchdog. Watchdog records are bounded, timestamped, owner-only metadata under the profile runtime directory; fault-only role freezes are refused by non-test release builds.
69
- - Holders read PTY output continuously, use one bounded input authority, reserve complete pastes before delivery, and emit a typed uncertain-delivery result after an admitted write failure. Server-to-client control lanes are frame/byte bounded; render lanes retain one replaceable full current surface. Holders stream surfaces only while at least one client views their tab.
69
+ - Holders read PTY output continuously, use one bounded input authority, reserve complete pastes before delivery, and emit a typed uncertain-delivery result after an admitted write failure. Each holder independently refreshes the server's latest complete retained surface even while hidden. Server-to-client control lanes are frame/byte bounded, render lanes retain one replaceable full current surface, and only the viewed tab is delivered to each attach client.
70
70
  - Server replacement is serialized by `server-start.lock` in the profile runtime directory. Recovery verifies the recorded native owner before termination, waits boundedly for the registry-writer lease, increments the durable epoch, derives holder credentials, and re-admits only exact holder/child identities and incarnations. A durable three-start/sixty-second budget stops automatic replacement; explicit `--restart` clears it.
71
71
  - Registry records now retain holder and child pid/start identities and holder incarnation. Replacement treats unverifiable records as `holder-lost`; it never starts a duplicate writer from uncertain state.
72
- - `tabsMaxConcurrentStarts` is an owned version-15 setting, default 2 and bounded 1–8. The server also enforces the existing ten-tab hard default where no earlier `tabsMax` setting exists, spaces starts by at least 250 ms, and requests graceful tab shutdown before verified forced termination.
72
+ - Concurrent starts use an internal default of 2 and hard cap of 8. The server spaces starts by at least 250 ms and requests graceful tab shutdown before verified forced termination; open-tab count has no user-visible quota.
73
73
  - All A1-owned Pi runtimes use one profile-neutral session-writer guard regardless of launch mode. A new target is reserved by canonical location, created as an empty owner-only placeholder, bound to stable file identity without a release gap, and only then exposed to Pi for session content. Runtime replacement acquires and binds the target before publishing it or releasing the prior writer; conflict paths preserve the source and may explicitly fork.
74
74
  - The generation-1 holder transport continues to use the authenticated owner endpoint and replaceable full surfaces. Dedicated holder endpoints and patch deltas remain later optimizations and are not milestone-3 claims.
75
75
 
@@ -130,13 +130,13 @@ Alternatives considered:
130
130
  - it duplicates a terminal core that already exists natively;
131
131
  - v2 recorded stalls from exactly this design.
132
132
 
133
- ### 2. One native binary, three roles, plus the A1 child
133
+ ### 2. One native addon, three Node-hosted roles, plus the A1 child
134
134
 
135
135
  ```text
136
136
  terminal (detached, per OS user × profile)
137
137
  ┌──────────────────────────┐ ┌─────────────────────────────────────┐
138
- │ a1 → guardian → │ │ a1-terminal-host server │
139
- │ a1-terminal-host attach │◀───▶│ registry (atomic JSON, fsync) │
138
+ │ a1 → guardian → │ │ node host-entry server │
139
+ │ node host-entry attach │◀───▶│ registry (atomic JSON, fsync) │
140
140
  │ tab strip + active tab │ pipe│ topology, client fan-out, restore │
141
141
  │ outer terminal I/O │ └───┬──────────────┬──────────────┬───┘
142
142
  └──────────────────────────┘ │pipe │pipe │pipe
@@ -150,6 +150,8 @@ Alternatives considered:
150
150
  └── tab bridge ──▶ server (status, session, name, detach)
151
151
  ```
152
152
 
153
+ Every attach, server, and holder role is `process.execPath` running the release's fixed `host-entry.js`; that entry loads the verified `terminal-host.node` addon and blocks in its native role dispatcher. Node carries no terminal bytes—the role remains native for its complete data path.
154
+
153
155
  - **attach** is the foreground `a1` surface. It runs inside the bare-A1 launch instance and dies with the terminal, which is harmless.
154
156
  - It owns raw mode, the alternate screen, outer terminal mode negotiation, the tab strip, composition of the active tab's retained surface, input arbitration, and shortcut interception.
155
157
  - **server** is resident. It owns:
@@ -162,7 +164,7 @@ Alternatives considered:
162
164
  - exactly one PTY (ConPTY on Windows) and its child process tree;
163
165
  - one libghostty-vt retained model with bounded scrollback;
164
166
  - input encoding and the per-tab damage state.
165
- Because ConPTY belongs to its creating process, each tab has its own creator. A server crash therefore does not destroy a tab's ConPTY; holder failure affects only that tab. Actual child/tree exit and writer-lease release still require verification, not inference from the pseudoconsole closing.
167
+ Because ConPTY belongs to its creating process, each tab has its own creator. A server crash therefore does not destroy a tab's ConPTY; holder failure affects only that tab. Actual child/tree exit and writer-lease release still require verification, not inference from the pseudoconsole closing. The 2026-10-17 Windows x64 migration measurement put an idle Node-hosted holder at approximately 30–40 MiB of resident memory, versus a few MiB for the former native-executable holder; allocator and shared-page accounting make this a range rather than a per-tab budget. The separate holder process is retained for certified crash isolation, and latency budgets were not raised to absorb Node startup.
166
168
  - **Why the server relays surfaces instead of clients connecting to holders directly.** It gives one authenticated endpoint per client, one ordering domain for focus and topology, and simpler multi-client arbitration. Measure the extra local pipe hop in the native hot-path evidence rather than assuming its latency. The server forwards encoded holder surfaces and does not re-render them.
167
169
 
168
170
  ### 3. Host scope, endpoints, writer authority, and credentials
@@ -182,9 +184,9 @@ Alternatives considered:
182
184
 
183
185
  ### 4. Detached start
184
186
 
185
- - **Server start.** The server is started from the Node pre-guardian bootstrap (`ensureTerminalHost()` beside `ensureSupervisor()`), through `a1-terminal-host server --detach`.
187
+ - **Server start.** The server is started from the Node pre-guardian bootstrap (`ensureTerminalHost()` beside `ensureSupervisor()`), through `process.execPath host-entry.js server --detach` with the verified addon path supplied to the entry.
186
188
  - **No general escape from containment.** On Windows, ordinary launch-instance jobs and holder-owned child jobs retain kill-on-close containment without `JOB_OBJECT_LIMIT_BREAKAWAY_OK` or silent breakaway; a job flag cannot allowlist an executable. On every platform, resident creation is a distinct authenticated fixed-role operation, not an arbitrary executable/argv spawn service.
187
- - **Authorized launch and recovery.** Initial creation runs before the launch guardian. A contained attach client or surviving holder invokes the native verified `server --detach` recovery path inside the terminal-host executable; there is no arbitrary-spawn IPC method. The routine accepts only a typed `server` or `holder` role and canonical profile, resolves and opens its own terminal-host artifact, constructs a fixed argument grammar, uses an allowlisted environment/cwd with handle inheritance disabled, and verifies the resulting pid/start identity and containment before success. On Windows, a contained caller uses the fixed WMI escape, while the holder's fixed recovery invocation launches directly from its already-detached resident boundary; recursively invoking WMI from a detached service can fail in non-interactive sessions. Native tests may compile one feature-gated fixture role, but production accepts no executable or free-form argv. Owner-only credentials prevent cross-user access; this is not a sandbox against arbitrary malicious code already running as the same OS user.
189
+ - **Authorized launch and recovery.** Initial creation runs before the launch guardian. A contained attach client or surviving holder invokes the addon's verified `server --detach` recovery path; there is no arbitrary-spawn IPC method. When `runRole` first enters, it records one immutable host command: the current Node executable and the release's fixed role entry. The native routine accepts only a typed `server` or `holder` role and canonical profile, opens its own addon artifact, constructs the fixed role grammar, uses an allowlisted environment/cwd with handle inheritance disabled, and starts that recorded Node-and-entry pair. It verifies the resulting pid/start identity and containment before success. On Windows, a contained caller uses the fixed WMI escape, while the holder's fixed recovery invocation launches directly from its already-detached resident boundary; recursively invoking WMI from a detached service can fail in non-interactive sessions. Native tests may compile one feature-gated fixture role, but production accepts no caller-supplied executable or free-form argv. Owner-only credentials prevent cross-user access; this is not a sandbox against arbitrary malicious code already running as the same OS user.
188
190
  - **Windows:**
189
191
  - Select valid platform creation flags for detached, console-free startup; do not assume combining `DETACHED_PROCESS` and `CREATE_NO_WINDOW` adds a guarantee. Verify resulting process identity and job containment before reporting resident readiness.
190
192
  - For a caller in a foreign kill-on-close job that denies breakaway (Windows OpenSSH, some IDE terminals), use WMI `Win32_Process.Create` with an explicit environment block and cwd, bounded startup, and post-launch verification (herdr reference).
@@ -192,7 +194,7 @@ Alternatives considered:
192
194
  - **Every platform** records the observed detachment mode and any failed verification reason.
193
195
  - **macOS and Linux:**
194
196
  - Start resident roles in a new session (`setsid`) with no controlling terminal, standard streams on `/dev/null`, and an explicit environment and cwd, so terminal close and SSH hang-up (`SIGHUP`) do not reach them. Verify the resulting session, process group, and identity before reporting readiness.
195
- - The process-guardian's kill-on-close tree tracking must not adopt resident roles, and ordinary descendants must not gain a way to leave their tree.
197
+ - The process-guardian addon's kill-on-close tree tracking must not adopt resident roles, and ordinary descendants must not gain a way to leave their tree.
196
198
  - macOS: keep the resident roles in the user's per-user bootstrap namespace, so clipboard, keychain, and other per-user services keep working after the launching terminal closes (herdr #4100 is the reference). Linux: a session manager that kills user processes at logout (systemd `KillUserProcesses`) ends tabs at logout; that is the documented logout behavior, not a failure.
197
199
  - Milestone 1 proves these with fixtures on each platform before any milestone relies on survival.
198
200
  - **Holders** are spawned by the server through the same platform detach routine.
@@ -289,7 +291,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
289
291
  - restarts: `restarts`, `restartWindowStart`, `lastExit`
290
292
  - `registryRevision`, `epoch`, and `bootId`
291
293
  - **No per-tab credential is persisted in the registry.** The owner-only profile secret is stored separately and derives per-tab/per-incarnation credentials from recorded non-secret identities. The environment sent with `tab.create` is kept in holder memory only; a tab restarted after a crash reuses its holder's environment.
292
- - **Session-writer lease.** All A1-owned session writers (resident, solo/fallback, and Pi-comparison) on the supported platforms use one profile-neutral native lock primitive before opening a session for writing, independent of launch mode. It does not start a host, holder, or bridge for direct modes. The primitive is an OS lock held by the writer process itself (`LockFileEx` on Windows, an open-file-description `fcntl` lock on Linux, `flock` on macOS), so the kernel releases it only when the writer is gone. Node writers take it in-process through a Node-API 9 artifact produced by the terminal-host Cargo package from the same lock implementation; a helper process cannot hold a lock on their behalf. Existing files are opened, identified by stable volume/device plus file identity, locked through that identity, and revalidated so case, junction/symlink, and hard-link aliases converge. A new file first holds a canonical-parent/name reservation, creates/opens without writing session content, acquires and revalidates file-identity authority while the reservation remains held, and only then admits writing; losing that race writes nothing. The writer routes include startup, `--session`, in-runtime switches, fork, import, new session, and the picker rename. The registry and holder coordinate admission, but a holder-only lock is insufficient: native lease custody must remain valid for the actual writer's lifetime, including any interval after holder death. Pin and prove the writer-bound lock/handle design on each platform in milestone 1; do not infer child death from pseudoterminal loss. Replacement requires lease acquisition and verified prior-writer/tree exit; uncertainty blocks it. Session switching acquires the target before relinquishing the old writer lease, and failed switches preserve the original session. Resident selection focuses an existing tab; direct-mode conflicts fail safely or offer an explicit fork, never silently attach or overwrite. Unmodified external Pi, older nonparticipating builds, and arbitrary file writers are outside this cooperative guarantee; document that limitation rather than claiming an OS sandbox.
294
+ - **Session-writer lease.** All A1-owned session writers (resident, solo/fallback, and Pi-comparison) on the supported platforms use one profile-neutral native lock primitive before opening a session for writing, independent of launch mode. It does not start a host, holder, or bridge for direct modes. The primitive is an OS lock held by the writer process itself (`LockFileEx` on Windows, an open-file-description `fcntl` lock on Linux, `flock` on macOS), so the kernel releases it only when the writer is gone. Node writers take it in-process through the same Node-API 9 `terminal-host.node` addon that exports the resident roles; there is no separate lock artifact, and a helper process cannot hold a lock on their behalf. Existing files are opened, identified by stable volume/device plus file identity, locked through that identity, and revalidated so case, junction/symlink, and hard-link aliases converge. A new file first holds a canonical-parent/name reservation, creates/opens without writing session content, acquires and revalidates file-identity authority while the reservation remains held, and only then admits writing; losing that race writes nothing. The writer routes include startup, `--session`, in-runtime switches, fork, import, new session, and the picker rename. The registry and holder coordinate admission, but a holder-only lock is insufficient: native lease custody must remain valid for the actual writer's lifetime, including any interval after holder death. Pin and prove the writer-bound lock/handle design on each platform in milestone 1; do not infer child death from pseudoterminal loss. Replacement requires lease acquisition and verified prior-writer/tree exit; uncertainty blocks it. Session switching acquires the target before relinquishing the old writer lease, and failed switches preserve the original session. Resident selection focuses an existing tab; direct-mode conflicts fail safely or offer an explicit fork, never silently attach or overwrite. Unmodified external Pi, older nonparticipating builds, and arbitrary file writers are outside this cooperative guarantee; document that limitation rather than claiming an OS sandbox.
293
295
  - **Prompt journal.** `<dataDir>/tabs/<profile-token>/<tabId>/journal.jsonl` is an owner-only child-owned recovery journal, separate from asynchronous prompt history. Each submission has a stable ID and session/incarnation identity. Admission durably records bounded identity and recoverable editor text, never duplicate attachment payload bytes, before dispatch; timeout/failure leaves the prompt in the editor with a visible failure and dispatches nothing. The actual engine command retains every validated attachment. Correlate the record with the committed session entry, and retire it only after the corresponding session data is synchronized, including when command completion observes that the lifecycle already returned to ready; a settled turn's durable completion also records its submission ID so recovery never offers a completed prompt as unfinished. Recovery merges journal/session evidence by identity and never automatically resends, including when a crash occurs between dispatch, session append, sync, and journal retirement. It rehydrates image chips only from validated profile sidecars and removes unavailable tokens rather than presenting inert attachment labels. Preserve the first-turn journal and reserved session identity if Pi has not created its file yet; distinguish that from an unexpectedly missing existing transcript.
294
296
  - **Draft checkpoint.** Use a periodic dirty checkpoint with a maximum one-second dirty interval while storage is healthy, including uninterrupted typing, rather than inactivity-only debounce. Expose failed/late checkpoints as degraded recovery; never continue claiming the one-second bound during a blocked disk or event-loop stall. Drafts and submitted prompts have different guarantees.
295
297
 
@@ -312,7 +314,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
312
314
 
313
315
  ### 10. Updates and version skew
314
316
 
315
- - **Binaries.** The server and holders run from immutable release directories, never from the mutable npm prefix, so no running binary is ever overwritten.
317
+ - **Artifacts and process images.** The server and holders are Node runtimes that load the addon and fixed entry from immutable release directories, never from the mutable npm prefix. Activation therefore never overwrites a loaded addon or entry, and release retention keeps every release used by a verified resident process.
316
318
  - **Same generation, newer client.** The client attaches normally, and features the server lacks are disabled per method.
317
319
  - **No automatic handoff in the first version.** A compatible resident cohort remains on its immutable release until all of its tabs stop. A newer same-generation client may attach with unsupported optional operations disabled.
318
320
  - **Retention.** Release collection keeps the server's, every holder's, and every tab child's release while native identities verify. Package activation does not terminate or overwrite them.
@@ -322,8 +324,8 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
322
324
 
323
325
  - **Strip.** Row 0, drawn by the attach client, reproduces the approved v2 default appearance:
324
326
  - normal chips use RGB background `38,38,38` with foreground `170,170,170`, hover uses `48,48,48`, and the viewed chip uses `55,55,55` with white text, plus v2's palette fallback;
325
- - the empty state uses the v2 wallpaper treatment and the right edge shows its 12-hour clock;
326
- - chips are at most 20 columns, clipped with `…` by grapheme width, with no separators and an always-visible `×` close control;
327
+ - the empty state contains only the hoverable `+` control, with no clock, wallpaper, hint, or child cursor;
328
+ - chips are at most 20 columns, clipped with `…` by grapheme width, with no separators or close glyph;
327
329
  - a `…` overflow menu holds tabs that do not fit, and `+` is always reserved;
328
330
  - these presentation choices are fixed, not settings, while each working spinner uses that tab's reported A1 accent.
329
331
  - **Keys.** These are intercepted by the attach client before input encoding. They are configurable through a keybindings file and conflict-checked against A1's registry at launch.
@@ -333,6 +335,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
333
335
  | `Alt+A` | new tab |
334
336
  | `Alt+W` | close |
335
337
  | `F2` | rename |
338
+ | `Alt+.` | create or activate the global Settings tab |
336
339
  | `Alt+1`…`Alt+9`, `Alt+0` | jump to a tab |
337
340
  | unbound (`tabs.next` / `tabs.previous`) | next / previous, wrapping |
338
341
  | unbound (`tabs.moveRight` / `tabs.moveLeft`) | move the tab right / left |
@@ -343,43 +346,43 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
343
346
  - **`Ctrl+C` twice leaves, in every tab (user decision).** The client forwards the first `Ctrl+C` to the tab unchanged, so a single press still clears, copies, or interrupts. It consumes the second press within the existing clear/exit interval as the detach request and does not forward it. The tab therefore never receives a second `Ctrl+C` from the chord, which removes v2's bug where `Ctrl+C+C` killed the agent. The trade-off: a future CLI tab cannot receive two quick `Ctrl+C`s. Pressing them more slowly than the interval still reaches the program.
344
347
  - **Mouse on row 0:**
345
348
  - click activates a tab;
346
- - the always-visible `×` closes its tab;
347
- - right-click opens `Rename`/`Close`;
349
+ - right-click opens `Rename`/`Close` (`Close` only for Settings);
348
350
  - dragging reorders, with a `│` drop marker, after 250 ms or once the pointer moves;
349
- - `+` creates a tab.
351
+ - `+` opens a persistent dark v2 menu with `+ Agent`, a separator, and `Settings`; hover and click remain active through the opening release.
352
+ - **Settings.** The strip owns one profile-global `Settings` tab. The grouped launcher and `Alt+.` create or activate it; closing the screen or tab removes it. Resident Agent autocomplete omits `/settings`, and manually entering it does not open a per-Agent screen. `a1 solo` and `a1 pi` retain their existing Settings routes.
350
353
  - **Commands inside A1 tabs:** existing `/name <name>` also renames the tab. Milestone 5 adds no tab-management slash commands: `/new-tab`, `/close`, `/tabs`, `/quit-all`, and `/restart-tab` are not exposed.
351
354
  - **Naming:**
352
355
  - Default names are `agent` or `agent N`.
353
356
  - When `tabsAutoName` is on (the default, by user decision), the tab's model proposes a hyphenated lowercase name of at most 16 characters after the first settled turn. It uses a small budget and one retry, with a fallback derived from the first prompt, and it runs inside the child.
354
357
  - A user rename sets `nameSource=user`, and auto-naming never overrides it.
355
358
  - An automatic rename uses v2's title transition: 600 ms moving rainbow, typing at 45 ms per character, then a 300 ms settle. Manual renames do not animate.
356
- - Rename is inline in the chip: `Enter` commits, and `Esc` or a click elsewhere cancels. The name is trimmed, must not be empty, is at most 64 characters, and has control characters stripped.
357
- - **Close.** `Alt+W`, the always-visible `×`, and the right-click menu close a tab. When it is `working` or `needs-input`, or its bridge reports queued input, the strip asks `Stop "<name>"? Enter stop · Esc cancel`. Closing sends the child a graceful shutdown, waits a bounded time, terminates the tree, and removes the tab. Its session stays resumable. Closing the last tab leaves the strip with `+` and a hint.
359
+ - Rename is inline in the chip: `Enter` commits, `Esc` or a click elsewhere cancels, and `Ctrl+Left` / `Ctrl+Right` move by word. The name is trimmed, must not be empty, is at most 64 characters, and has control characters stripped.
360
+ - **Close.** `Alt+W` and the right-click menu close a tab directly, including while it is working. Closing removes the chip without a confirmation, stopping/read-only frame, or close glyph, then gracefully stops the child under the existing bounded termination policy. Its session stays resumable. Closing the last tab leaves only the hoverable `+` control.
358
361
  - **Detach.**
359
362
  - `Ctrl+C` twice detaches from any tab and is handled by the attach client, so it works even when the bridge is down. Inside an A1 tab, `/quit` and empty-editor `Ctrl+D` send a detach request carrying the immutable command-origin controller identity and revision; stale, ambiguous or unavailable attribution keeps the tab running and directs the user to `Ctrl+C` twice.
360
363
  - The client restores the terminal. When agents are still running, it prints `1 agent still running run a1 to return.` or `N agents still running run a1 to return.`; otherwise it prints the resume hint.
361
364
  - **Launch and reattach:**
362
365
  - Bare `a1` ensures the server and attaches.
363
366
  - With zero tabs, it creates one A1 tab in the launch cwd.
364
- - Otherwise it views the last active tab from its retained surface without byte-history replay. Measure retained first paint and first input separately from bootstrap; status for the remaining tabs arrives without subscribing to their surfaces.
367
+ - Otherwise it views the last active tab from its retained surface without byte-history replay. Every holder keeps draining its PTY and updating its retained terminal independently while hidden; selecting a working tab requests its newest complete partial-response surface immediately and then continues live delivery. Measure retained first paint and first input separately from bootstrap; status for the remaining tabs arrives without subscribing to their surfaces.
365
368
  - `a1 --session X` opens X in a new tab, or focuses the tab holding it.
366
369
  - A new tab's cwd is the client's launch cwd.
367
- - One internal prewarmed standby keeps the startup graph loaded in a hidden A1 process after a trusted tab has reported its session. Before promotion it owns no session or writer lease, has no registry/tab-strip entry, and does not count toward `tabsMax`. Creation promotes it only for an exact cwd, release, Node executable, and environment match; the server durably commits the new tab before exposing it to input. A mismatch discards it and normal creation retargets the next standby from the newly trusted tab. Standby launch or loss never changes visible tab state. It is not a setting. Measure A1 warm promotion directly; v2's tens-of-milliseconds result is reference evidence only.
370
+ - One internal prewarmed standby keeps the startup graph loaded in a hidden A1 process after a trusted tab has reported its session. Before promotion it owns no session or writer lease and has no registry/tab-strip entry. Creation promotes it only for an exact cwd, release, Node executable, and environment match; the server durably commits the new tab before exposing it to input. A mismatch discards it and normal creation retargets the next standby from the newly trusted tab. Standby launch or loss never changes visible tab state. It is not a setting. Measure A1 warm promotion directly; v2's tens-of-milliseconds result is reference evidence only.
368
371
  - **Several terminals.** Each client has its own active tab, but each tab has one explicit input controller. Focus gain automatically claims the viewed tab through Decision 5's causal input barrier; if focus reporting is unavailable, first input starts the claim and is held until acknowledgement. There is no `Enter` takeover or persistent view-only banner. Only the current controller may send input or set PTY size. Delayed `/quit` and empty-editor `Ctrl+D` preserve their submission origin; stale or unprovable attribution affects neither client and falls back to attach-local double-`Ctrl+C`.
369
372
  - **Background attention.** Only the chip icon changes. There is no bell, sound, or notification, and no setting for one.
370
373
 
371
374
  ### 12. Limits and resources
372
375
 
373
- The settings live in the owned settings screen, each with a hard cap.
376
+ Internal resources remain bounded without imposing a user-visible tab-count quota.
374
377
 
375
- - User-facing Tabs settings during resident launches are only `tabsMax` (default 10, range 1–50) and `tabsAutoName` (default on). Tab enablement is not a setting. Direct `a1 solo` and `a1 pi` launches omit the Tabs section entirely.
378
+ - The only user-facing Tabs setting during resident launches is `tabsAutoName` (default on). Legacy `tabsMax` data is preserved but ignored. Tab enablement is not a setting. Direct `a1 solo` and `a1 pi` launches omit the Tabs section entirely.
376
379
  - Unresponsive restart and stall-notice timing are internal fixed behavior at 120 and 300 seconds; concurrent starts use internal default 2/hard cap 8 and prewarm uses internal count/hard cap 1.
377
380
  - Every open tab keeps its holder and A1 process until explicit close. There is no idle-suspension setting, timer, lifecycle, or glyph.
378
381
  - Scrollback is capped at 10 MiB per holder, and the replay after a server restart is the retained model itself.
379
382
  - The server exits after 10 minutes with no attached client and no live user tab; the prewarmed standby does not keep it alive and is stopped first.
380
383
  - Structured diagnostic logs are `terminal-host-server.log` and `terminal-host-holder-<tab>.log`, rotated at 5 MiB with three kept. Use allowlisted fields/reason codes, not arbitrary exception strings, prompt text, terminal content, or credentials.
381
384
  - Raw child stderr cannot be declared sanitized: when available separately from ConPTY's combined terminal stream, store it only under the owner-only per-tab recovery area, at 5 MiB with three generations and a seven-day maximum age, alongside bounded last-screen files. Do not scrape stderr back out of terminal cells. These private artifacts may contain conversation or sensitive content and are excluded from `a1 tabs doctor`; neither their contents nor arbitrary extension errors enter structured crash records.
382
- - `a1 tabs host status` prints the server pid, build, release, generation, epoch, uptime, detachment mode, tab count by status, client count, configured and hard limits, volatile health counters since server start, and log paths.
385
+ - `a1 tabs host status` prints the server pid, build, release, generation, epoch, uptime, detachment mode, tab count by status, client count, internal resource limits, volatile health counters since server start, and log paths.
383
386
 
384
387
  #### Hard bounds (milestone 6 audit)
385
388
 
@@ -393,7 +396,6 @@ Every queue, buffer, file, and collection the resident roles keep has a declared
393
396
  | Per-client render slot | one pending full surface; a newer one replaces it | `roles/server.rs` `Peer::offer_surface` | `stalled_client_gets_one_full_current_surface` |
394
397
  | Concurrent endpoint connections | 128 | `roles/server.rs` `admit_connection` | `connections_beyond_the_cap_are_refused_and_released_slots_are_reused` |
395
398
  | Attached viewing clients | 16 | `roles/server.rs` `client_slot_available` | `attached_clients_are_capped` |
396
- | Open tabs | `tabsMax` (default 10, hard 50), including session-selection starts | `roles/server.rs` `tab.create`, `attach.start` | `ten_concurrent_tab_starts_respect_the_server_limit` |
397
399
  | Concurrent holder starts | default 2, hard 8 | `core/server.rs` | `limits_starts_and_drops_stale_completion` |
398
400
  | Stopped tab records | 16, oldest dropped first | `roles/server.rs` `prune_stopped_records` | `stopped_records_are_bounded_oldest_first_and_live_tabs_are_kept` |
399
401
  | Prior tab-set archives | bounded history | `roles/registry.rs` | `prior_tab_sets_are_bounded` |
@@ -410,7 +412,7 @@ Every queue, buffer, file, and collection the resident roles keep has a declared
410
412
 
411
413
  ### 13. Packaging and certification
412
414
 
413
- - `a1-terminal-host` is built and packaged for Windows x64, Linux x64, macOS arm64, and macOS x64 (see the 2026-10-14 decision), extending the impact-selected Windows CI owner introduced by #588 to that exact target matrix, with Zig 0.15.2 for pinned libghostty-vt. It ships in the immutable release with artifact manifest, hash verification, provenance, licenses, and notices.
415
+ - `terminal-host.node` and the fixed `dist/foundation/resident-tabs/host-entry.js` are built and packaged for Windows x64, Linux x64, macOS arm64, and macOS x64 (see the 2026-10-14 decision), extending the impact-selected Windows CI owner introduced by #588 to that exact target matrix, with Zig 0.15.2 for pinned libghostty-vt. One addon carries both every role and the session-writer lock; there is no terminal-host executable or separate lock addon. It ships in the immutable release with the v2 addon manifest, hash verification, provenance, licenses, and notices.
414
416
  - Acceptance requires exact-package hermetic suites on each platform plus manual or isolated-worker evidence per platform for terminal close, SSH/session loss, reattach, input fidelity, extension text UI, failure recovery, rollback, and render smoothness. Evidence is never inferred from one platform to another.
415
417
  - An authorized isolated-worker 24-hour soak remains required certification evidence on each platform; a miss blocks certification without silently changing the approved default or weakening fallback.
416
418
 
@@ -498,9 +500,9 @@ The v2 daemon was unreliable for structural reasons, and a forensic pass over it
498
500
  2. With `TERMINAL_HOST_LOCAL_BUILD=1` and `TERMINAL_HOST_PREBUILT_VT` naming that directory, `build.rs` verifies `terminal-vt.manifest.json` against the vendored `VERSION`, the Ghostty commit, the Cargo and Zig targets, and each file's SHA-256, then links it and skips Zig. A mismatch fails the build in one line and never falls back. Builds with `CI` set ignore the variable.
499
501
  3. Run one test: `cargo test --manifest-path native/terminal-host/Cargo.toml --features resident-fixtures --test role_integration <name>`.
500
502
 
501
- Every chaos run prints its seed and records its executed action schedule in its report; `--seed <n>` fixes the seed, and `--replay <report.json>` re-executes a recorded schedule and reports actions it could not apply as divergence. Locally, `--build` is unavailable because it runs the release packaging build, which records the Zig version; build the two binaries through the prebuilt library instead with `cargo build --release --locked --manifest-path native/terminal-host/Cargo.toml --features resident-fixtures --bin terminal-host --bin fake-tab`, then pass `--host` and `--fake-tab` from `native/terminal-host/target/release`.
503
+ Every chaos run prints its seed and records its executed action schedule in its report; `--seed <n>` fixes the seed, and `--replay <report.json>` re-executes a recorded schedule and reports actions it could not apply as divergence. With the verified prebuilt terminal library selected, `--build` builds the release addon and the feature-gated `fake-tab` fixture; otherwise pass `--host <terminal-host.node>` and `--fake-tab <fixture>` explicitly. Product builds never emit a role executable; the `role-host` and `fake-tab` binaries are test fixtures only.
502
504
 
503
- Known limit (2026-10-15): on a Windows desktop whose shells already run inside a job, a server started by a role test cannot leave that job, `verify_detached` rejects it, and tests that expect `started` fail locally while passing in CI; the release retention integration test, which runs whenever debug host binaries exist, fails the same way, and local chaos reports servers that holders did not replace. A separate change addresses this.
505
+ Known limit (2026-10-15): on a Windows desktop whose shells already run inside a job, a server started by a role test cannot leave that job, `verify_detached` rejects it, and tests that expect `started` fail locally while passing in CI; the release retention integration test, which runs whenever a debug host addon exists, fails the same way, and local chaos reports servers that holders did not replace. A separate change addresses this.
504
506
 
505
507
  **Milestone 5 preview performance budgets (p95 on each exact platform build):** cold launch to first input is the direct startup budget plus 800 ms; matching-standby creation to first input is 250 ms; reattach first paint is 300 ms and first accepted input is 400 ms; input-to-process is 16 ms; output-to-present is 33 ms. Each result or miss is recorded independently for Windows x64, macOS arm64, and Linux x64. The platform-native validation lanes run `measure-resident-tabs.mjs` against their built host: its instrumented child reports process-admission timestamps through a real resident holder, while the outer test PTY records presentation, standby promotion, and retained-surface reattach timestamps.
506
508
 
@@ -508,8 +510,8 @@ Known limit (2026-10-15): on a Windows desktop whose shells already run inside a
508
510
 
509
511
  - **[A server crash interrupts the display for everyone]** → Holders keep processes and screens alive. The server holds no VT, Pi, or extension code. It is auto-respawned by any client or holder, and reattach restores exact surfaces.
510
512
  - **[Terminal fidelity in a composed surface]** (keyboard protocols, mouse, paste, IME, Unicode width, hyperlinks) → Reuse libghostty-vt encoding and the proof's input work, and add fidelity suites on each platform. Claims cover only the certified text-terminal contract; image protocols remain an explicit gap.
511
- - **[Per-tab memory multiplies with holder and A1 processes; historical estimates are not measured A1 bounds]** → `tabsMax`, one internal prewarmed standby, and throttled hidden rendering. Every open tab keeps its process until explicit close; there is no idle suspension.
512
- - **[Detachment is platform-specific and fragile]** (Windows jobs and SSH, macOS bootstrap namespaces, Linux logout policies) → Detection and escape live in the native binary per platform. The mode is reported, never assumed, and unverifiable survival falls back instead of claiming persistence.
513
+ - **[Per-tab memory multiplies with holder and A1 processes; measured working sets are observations, not A1 bounds]** → The 2026-10-17 Windows x64 measurement shows Node hosting raising an idle holder from a few MiB to approximately 30–40 MiB resident. One internal prewarmed standby, bounded concurrent starts, and throttled hidden delivery limit avoidable multiplication. Every open tab keeps its process until explicit close; there is no idle suspension or count quota. Merging holders into the server would discard the certified per-tab crash boundary and requires a separate change.
514
+ - **[Detachment is platform-specific and fragile]** (Windows jobs and SSH, macOS bootstrap namespaces, Linux logout policies) → Detection and escape live in the native addon per platform, while the process image is Node running the fixed role entry. The mode is reported, never assumed, and unverifiable survival falls back instead of claiming persistence.
513
515
  - **[Three platforms multiply native and certification work]** → Platform code is confined to the thin I/O shell around one shared sans-IO core, and milestone 1 proves every platform primitive before later milestones build on it.
514
516
  - **[The Zig and libghostty toolchain in CI and release]** → It is pinned already. Extend the #588 Windows owner and provenance gate to a macOS and Linux build matrix.
515
517
  - **[No image protocol in tabs at first]** → Pi's text fallback applies and the gap is documented. Forwarding is a follow-up.
@@ -1,6 +1,6 @@
1
1
  # Terminal-host provenance
2
2
 
3
- The composed-terminal proof uses a small console-hosted stack. It runs inside the user's existing terminal and does not create a separate desktop window.
3
+ The resident terminal host uses a small console-hosted stack. Its native implementation ships as the Node-API 9 addon `terminal-host.node`; the release's fixed `dist/foundation/resident-tabs/host-entry.js` loads that addon and dispatches the server, holder, attach, probe, maintenance, and internal roles inside the hosting Node runtime. It runs inside the user's existing terminal and does not create a separate desktop window. No terminal-host executable is built or shipped, and the same addon exports the in-process session-writer lock.
4
4
 
5
5
  ## Selected source components
6
6
 
@@ -14,7 +14,7 @@ The composed-terminal proof uses a small console-hosted stack. It runs inside th
14
14
 
15
15
  ## Release notices
16
16
 
17
- `scripts/release/generate-terminal-host-notices.mjs` writes `terminal-host-THIRD-PARTY-NOTICES.txt` beside each release artifact. It walks the locked Cargo graph for the build target (normal dependencies, including the `node-addon` session lock; proc-macro crates and their subtrees never reach the binary), adds the Zig-linked components, and fails on a license expression outside MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Unicode-3.0, or Zlib (the `LLVM-exception` addendum is accepted), on a crate without license text, or on a pinned text whose digest changed.
17
+ `scripts/release/generate-terminal-host-notices.mjs` writes `terminal-host-THIRD-PARTY-NOTICES.txt` beside each release addon. It walks the locked Cargo graph for the build target (normal dependencies, including the Node-API glue; proc-macro crates and their subtrees never reach the addon), adds the Zig-linked components, and fails on a license expression outside MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, Unicode-3.0, or Zlib (the `LLVM-exception` addendum is accepted), on a crate without license text, or on a pinned text whose digest changed.
18
18
 
19
19
  A buffered A1-owned frame composer is the initial presentation layer. Ratatui is an optional evaluation candidate only if the narrow composer increases rendering risk; it is not selected by default.
20
20
 
@@ -76,4 +76,6 @@ Unverified, mismatched, or unsigned/unattested artifacts are not eligible for co
76
76
 
77
77
  ## Release artifact
78
78
 
79
- `npm run build:terminal-host` (`scripts/development/build-terminal-host.mjs`) runs only on the release runners. It builds the `terminal-host` executable and the `node-addon` session-writer lock (`a1_session_lock.node`) with `cargo build --release --locked`, checks the executable's `--version` report (protocol, generation, crate version, pinned `libghostty-vt` commit, and that neither `fault-injection` nor `resident-fixtures` is compiled in), writes the notices, and records `terminal-host.manifest.json` (schema `a1-terminal-host-artifact-v1`) with the SHA-256 and size of all three files plus the target triple, Rust and Zig versions, and `Cargo.lock` digest. The publication `package` job refuses to pack unless every platform's guardian and terminal host are present and match their manifests, and A1 verifies the manifest before every execution.
79
+ `npm run build:terminal-host` (`scripts/development/build-terminal-host.mjs`) builds the library once with `cargo build --release --locked --lib --features node-addon`, copies the cdylib as `terminal-host.node`, loads the addon in Node, and checks its `version()` report: protocol, generation, crate version, pinned `libghostty-vt` commit, and the absence of `fault-injection` and `resident-fixtures`. Release and native validation runners invoke it with their pinned Zig toolchain. The normal developer `npm run build` invokes the same release-profile builder for the current platform; on Windows it links a manifest-verified terminal library exported by trusted CI and records the repository-pinned Zig producer version without invoking Zig on the workstation. The macOS x64 cross build may use the recorded Mach-O architecture check when the arm64 runner cannot load the addon.
80
+
81
+ The generated `terminal-host.manifest.json` uses schema `a1-terminal-host-artifact-v2` and records `kind: "node-addon"`, Node-API 9, the addon's SHA-256 and size, the release-relative role-entry digest, the notices digest, target triple, Rust and Zig versions, and `Cargo.lock` digest. The role entry passes only the fixed role grammar and records `{ executable: process.execPath, entry, artifact: addonPath }` once in the addon; the explicit loaded artifact identity makes release-hold reporting independent of development environment propagation. Detached and WMI resident starts use the recorded Node-and-entry command and never resolve an A1-built executable. The publication `package` job refuses to pack unless every platform's process-guardian and terminal-host addons match their manifests, and A1 verifies the addon, entry, and notices before loading the host.
@@ -26,19 +26,21 @@ Physical desktop automation is absent from the active repository baseline. Any f
26
26
  The published package contains:
27
27
 
28
28
  - the sole public `a1` command;
29
- - the internal supervisor entry;
30
- - immutable release/bootstrap, lifecycle, protocol, storage, update, owned-UI, and Pi-adapter modules;
29
+ - the internal supervisor, launch-guardian, exit-watcher, and fixed terminal-host role entries;
30
+ - immutable release/bootstrap, lifecycle, protocol, storage, update, owned-UI, and Pi-adapter modules, plus `process-guardian.node` and `terminal-host.node` for every supported target with manifests and terminal-host notices;
31
31
  - current user and architecture documentation.
32
32
 
33
- It contains no PTY, terminal emulator, browser/desktop GUI, custom renderer, input translator, physical automation driver, or generated runtime data.
33
+ It contains no executable built from A1 sources, separate session-lock addon, browser/desktop GUI, physical automation driver, or generated runtime data. PTY ownership, terminal emulation, composition, and input translation exist only inside the terminal-host addon; the JavaScript product path does not relay terminal bytes.
34
34
 
35
- ## Native launch containment
35
+ ## Native addon layer
36
36
 
37
37
  `npm run doctor` reports this contract against the current machine—Node range, npm major, git, the GitHub CLI, Rust/Cargo, and whether `node_modules` matches the lockfile—and names the install command for anything missing. `npm run build` runs the same check as its first step, so an unmet prerequisite fails by name rather than inside a module resolver or Cargo. Unmet tool prerequisites block; a Node or npm version outside the declared contract is reported as an advisory without failing the build.
38
38
 
39
- Source builds require Rust/Cargo 1.85 or newer; `rust-toolchain.toml` pins the exact version CI and publication install through `scripts/development/prepare-ci-rust.sh`, and rustup selects that version for Cargo commands inside the repository, installing it on first use. The Zig cache (`mlugg/setup-zig`, one entry per workflow and platform per run, bounded to 512 MiB) stays enabled: measured on 2026-10-16, a cold Zig build of `libghostty-vt` added about 40 to 60 seconds to a terminal-host test build (Linux 2m21s cold against 1m20s warm, Windows 2m05s against 1m26s), which the cache saves on every warm run. `npm run build` compiles the standalone `native/process-guardian` crate for the host platform, places it under `dist/native/<platform>-<architecture>/`, and writes an integrity manifest. Preview and stable candidate workflows build platform artifacts on isolated Windows, Linux, and macOS arm64 runners, cross-compile the macOS x64 artifacts on the arm64 runner with `--target x86_64-apple-darwin`, and assemble them before packing. The same runners build the release terminal host and its session-writer lock with `npm run build:terminal-host`; the local `npm run build` does not, because the Zig fetch for `libghostty-vt` is quarantined by workstation antivirus on Windows (see [`terminal-host-provenance.md`](terminal-host-provenance.md)). macOS remains explicitly unsupported until its exact containment adapter is certified; the package must fail before runtime startup rather than use PID-only cleanup.
39
+ Source builds require Rust/Cargo 1.85 or newer; `rust-toolchain.toml` pins the exact version CI and publication install through `scripts/development/prepare-ci-rust.sh`, and rustup selects that version for Cargo commands inside the repository, installing it on first use. The Zig cache (`mlugg/setup-zig`, one entry per workflow and platform per run, bounded to 512 MiB) stays enabled: measured on 2026-10-16, a cold Zig build of `libghostty-vt` added about 40 to 60 seconds to a terminal-host test build (Linux 2m21s cold against 1m20s warm, Windows 2m05s against 1m26s), which the cache saves on every warm run.
40
40
 
41
- The process guardian inherits terminal handles but transports no terminal bytes. See [`process-guardian-provenance.md`](process-guardian-provenance.md).
41
+ `npm run build` compiles `native/process-guardian` as the host platform's Node-API 9 addon, places `process-guardian.node` under `dist/native/<platform>-<architecture>/`, loads it to observe its version identity, and writes the v2 integrity manifest. It also builds the current platform's release-profile `terminal-host.node`, whose v2 manifest binds the addon, `dist/foundation/resident-tabs/host-entry.js`, and notices, so the ordinary `npm run build && ./scripts/dev` handoff exercises resident tabs without deployment. On Windows the normal build reuses a compatible ignored terminal-library cache or downloads the newest compatible library from the current branch or a successful CI run belonging to a pull request already merged into `develop`, then links it under the crate's manifest verification; it never silently invokes the quarantined Zig package fetch. An unavailable or mismatched prerequisite fails the build instead of leaving a nominally successful single-agent-only tree. The npm `prepare` lifecycle and validation preflight use the explicit core build because selected native lanes assemble and certify terminal hosts separately.
42
+
43
+ Preview and stable candidate workflows build the addons on isolated Windows, Linux, and macOS arm64 runners, cross-compile the macOS x64 addons on the arm64 runner with `--target x86_64-apple-darwin`, and assemble all targets before packing. Those release runners retain their explicit `npm run build:terminal-host` step and pinned Zig input rather than consuming a workstation cache. Cross-compiled artifacts record a Mach-O architecture check when they cannot be loaded on the producing runner. Supported native code is always loaded by Node; an absent, incompatible, or unsupported addon fails before runtime startup rather than falling back to PID-only cleanup. The process-guardian addon inherits terminal handles but transports no terminal bytes. Its separate Node-hosted exit watcher restores terminal state after guardian failure, subject to the documented node-wide-kill gap. See [`process-guardian-provenance.md`](process-guardian-provenance.md), [`terminal-host-provenance.md`](terminal-host-provenance.md), and [`resident-tabs.md`](resident-tabs.md).
42
44
 
43
45
  ## Gates
44
46
 
@@ -193,7 +193,7 @@ validation; malformed, mixed, renamed, stale, or unavailable inputs fail closed.
193
193
  A legitimate generated baseline update remains outside the allowlist and follows the
194
194
  manually accepted mixed/code path.
195
195
 
196
- A new implementation-bound specification starts as OpenSpec-only artifacts in one normally named draft PR. Explicit approval to implement continues in that same worktree, branch, history, and PR; the plan does not merge first. Approved refinements reconcile planning before code. After implementation, add one to ten plain implementation-specific bullets under `## Acceptance` and mark the PR ready; the trusted `OpenSpec finalization` workflow conservatively synchronizes deltas, stages the dated archive plus conditional acceptance manifest, and commits them to the same branch, and exact-head CI validates that head. Auto-merge remains disabled: an authorized maintainer's manual merge accepts the listed scenarios and atomically integrates implementation, specs, and archive. No acceptance or archive follow-up PR is created. Closing an unmerged draft integrates nothing; cleanup still needs separate approval. See [delivery and archive handoff](openspec-archive-automation.md) for commands and legacy compatibility.
196
+ A new implementation-bound specification starts as OpenSpec-only artifacts in one normally named draft PR. Explicit approval to implement continues in that same worktree, branch, history, and PR; the plan does not merge first. Approved refinements reconcile planning before code. After implementation, add one to ten plain implementation-specific bullets under `## Acceptance` and mark the PR ready; the trusted `OpenSpec finalization` workflow conservatively synchronizes deltas, stages the dated archive plus conditional acceptance manifest, and commits them to the same branch, and exact-head CI validates that head. Automation-owned auto-merge remains disabled: an authorized maintainer's direct merge, personal exact-head auto-merge arm, or explicit request for the active agent to expected-head squash-merge the currently eligible candidate accepts the listed scenarios and atomically integrates implementation, specs, and archive. No acceptance or archive follow-up PR is created. Closing an unmerged draft integrates nothing; cleanup still needs separate approval. See [delivery and archive handoff](openspec-archive-automation.md) for commands and legacy compatibility.
197
197
 
198
198
  ## Numbered development previews
199
199
 
@@ -1,6 +1,6 @@
1
1
  # Atomic OpenSpec delivery and legacy archival
2
2
 
3
- New OpenSpec deliveries use one ordinary development pull request from draft planning through implementation, validation, acceptance, specification synchronization, and archival. An authorized maintainer chooses integration by manually merging the exact validated head or personally enabling GitHub native auto-merge for that unchanged head. Agents, Apps, bots, merge queue, and repository automation never arm or invoke implementation integration and never create a version-3 acceptance or archive follow-up PR.
3
+ New OpenSpec deliveries use one ordinary development pull request from draft planning through implementation, validation, acceptance, specification synchronization, and archival. An authorized maintainer chooses integration by manually merging the exact validated head, personally enabling GitHub native auto-merge for that unchanged head, or explicitly asking the active agent to merge the identified currently eligible head. Agents never infer or independently choose integration; Apps, bots, merge queue, workflows, and repository automation receive no implementation authority, and no version-3 acceptance or archive follow-up PR is created.
4
4
 
5
5
  Unrelated standalone documentation remains separate: a non-draft unassociated PR changing only `openspec/**`, `docs/**`, and/or root `README.md` retains the existing CI-gated automatic route. A new active change, implementation association, malformed lifecycle data, or mixed path remains outside documentation-owned integration. Before finalization or without valid human provenance, any arm is disabled; a finalized version-3 human arm remains a separate maintainer-owned route.
6
6
 
@@ -8,15 +8,29 @@ Version-1 and version-2 deliveries and their existing comments, acceptance PRs,
8
8
 
9
9
  ## Version-3 lifecycle
10
10
 
11
- 1. **Draft plan:** create one normally named branch/PR such as `feature/...`, `fix/...`, or `chore/...`. Start the body with `## Proposal`, use it for one or two sentences of intent, and follow it with two to ten unchecked, outcome-sized task items under `## Implementation`. Each item names a delivered code outcome. Omit generic process tasks; run, verify, validate, evidence, or manual-test items; quoted phase lines; and routine validation command lists, and keep machine linkage under final collapsed `Automation`. Keep it draft and include only planning artifacts until the maintainer approves the plan and explicitly requests implementation.
11
+ 1. **Draft plan:** create one normally named branch/PR such as `feature/...`, `fix/...`, or `chore/...`. Start the body with `## Proposal`, use it for one or two sentences of intent, and follow it with one or more unchecked, outcome-sized task items under `## Implementation`, with no fixed maximum. Each item names a delivered code outcome. Omit generic process tasks; run, verify, validate, evidence, or manual-test items; quoted phase lines; and routine validation command lists, and keep machine linkage under final collapsed `Automation`. Keep it draft and include only planning artifacts until the maintainer approves the plan and explicitly requests implementation.
12
12
  2. **Same-PR implementation:** continue after explicit approval in the same worktree, branch, history, draft PR, and phase-free body. Check an Implementation item as soon as its associated code and tests are written and its detailed task bookkeeping is complete; an ordinary progress edit changes only `[ ]` to `[x]` and is not deferred until handoff. Adding, removing, reordering, or rewriting an item is a scope refinement requiring explicit approval and coherent planning updates, with affected scope left unchecked. Reconcile approved refinements before corresponding code edits.
13
13
  3. **Complete implementation:** finish the code and its tests, substantive tasks, and explicit known-gap disposition. CI runs those tests after readiness; its success is objective evidence, not acceptance, and GitHub check counts are not Implementation task progress. Manual validation is the maintainer's optional choice before integrating and never blocks a checkbox, finalization, or merge.
14
14
  4. **Plain acceptance list:** before checking the final item, keep the body phase-free and add final `## Acceptance` with one to ten concise implementation-specific behavior-and-result bullets. Do not use checkboxes, generic review/CI/approval/archive statements, angle-bracket placeholders or HTML, URLs, mentions, or automated-test inventory.
15
15
  5. **Automatic readiness and finalization:** reconcile and push current `origin/develop`, run the [delivery body check](#delivery-body-check) on the intended final body, then check the final item. That checkbox update completes the agent-owned implementation job; agents never mark implementation PRs ready themselves. Trusted automation revalidates the exact fully complete version-3 task-list snapshot and all pre-finalization inputs, marks the PR ready, and then conservatively synchronizes all deltas, moves the active change into its dated archive, stages the conditional acceptance manifest, commits that to the same branch with the archive App identity, and writes the emitted paths into the body's implementation fence. It never checks tasks or infers completion. Running the [finalization command](#finalization-command) locally first is optional and yields the same bytes.
16
16
  6. **Validate:** one normal exact-head workflow validates the finalized head: implementation, synchronized specs, archive, manifest, tasks/evidence, exact PR-body list, and every selected product/governance scope before emitting the stable protected aggregate. A new commit or acceptance-list change re-finalizes automatically when needed and requires full renewed validation; no lifecycle body edit or second workflow run is required.
17
- 7. **Maintainer integration accepts:** after reviewing the finalized candidate, an authorized human either manually merges the exact validated head after the stable protected aggregate succeeds or personally enables GitHub native auto-merge for that unchanged head so protected integration waits for the aggregate. That human action means the listed scenarios are accepted. Agents, Apps, bots, merge queue, automation-owned implementation arms, and documentation integration remain forbidden. A changed head, acceptance list, or finalization invalidates the earlier arm and requires the maintainer to choose again.
17
+ 7. **Maintainer integration accepts:** after reviewing the finalized candidate, an authorized human manually merges the exact validated head, personally enables GitHub native auto-merge for that unchanged head, or explicitly asks the active agent to merge that identified head while it is currently eligible. The agent route re-reads authoritative state, requires the exact finalized head and acceptance list, current `develop`, successful `Development validation required`, positive mergeability, and the maintainer's authenticated human `User`, then issues one protected squash merge with expected-head enforcement. It never waits, arms auto-merge, retries later, uses automation credentials or merge queue, or bypasses protection. Each route means the listed scenarios are accepted. A candidate change invalidates the decision and requires the maintainer to choose again.
18
18
  8. **Verify and clean:** trusted post-merge policy derives `Archived` and reports `accepted-and-archived` from committed bytes and immutable GitHub provenance without editing the accepted PR body. It publishes no lifecycle branch or PR. Shared exact-head remote cleanup may delete the unchanged topic ref; local cleanup remains separately ownership-controlled: the agent parks the worktree with `handoff` at step 7, and the next session's `sweep` removes it once the merge, archive, and remote-ref evidence verify (see [local cleanup](local-worktree-cleanup.md)).
19
19
 
20
+ ### Maintainer-requested agent merge
21
+
22
+ The permission is one-shot and candidate-bound. A same-session instruction such as “merge it” is sufficient only when the conversation identifies one handed-off PR and head unambiguously; implementation approval, green CI, an earlier request, or a standing preference is not merge authority. If the PR is not currently eligible, report the blocker and stop. Do not poll, watch, arm native auto-merge, or retain the instruction for later.
23
+
24
+ Immediately before mutation, refresh the PR and authenticated principal. Require an open non-draft same-repository PR into `develop`, the requested exact finalized head and acceptance list, `MERGEABLE`/`CLEAN` state, successful `Development validation required` for that head, and a human `User` with write, maintain, or admin repository permission. Then use the ordinary protected route with no `--admin` or `--auto`:
25
+
26
+ ```bash
27
+ gh pr merge <pr> --repo <owner>/<repository> --squash --match-head-commit <exact-head-sha>
28
+ ```
29
+
30
+ GitHub branch protection remains authoritative. A refusal, changed head, advanced target, stale finalization, failed or pending check, ambiguous identity, or unavailable read ends the attempt and needs a fresh maintainer request. After success, use the ordinary read-only accepted/archive verification and cleanup path.
31
+
32
+ The policy PR introducing this route must integrate through the previously deployed maintainer-owned route. Only a later candidate may use a requested agent merge after this policy is present on `develop`.
33
+
20
34
  ### Complete-regression evidence ordering
21
35
 
22
36
  PR-attached Full regression is selected only for a repair that the trusted triage created from a failed Full regression: base policy verifies the immutable `openspec-ci` App author, generated candidate identity, and `regression-provenance.json`. Human-authored lookalikes, publishing or validation-authority changes, labels, Publish-only repairs, and successful-run persistent-overrun candidates retain bounded ordinary validation. Generated repair drafts run no Development test suites, including after implementation begins. Finish substantive implementation tasks, focused local evidence, pre-finalization observations, and known-gap disposition before checking the final Implementation item. The ready active head defers testing while trusted finalization publishes the candidate; the finalized head then receives all four native lanes as new full evidence, required by `Development validation required` alongside the existing gates.
@@ -27,7 +41,7 @@ The implementation, synchronized canonical specs, conditional acceptance record,
27
41
 
28
42
  ## Draft PR body
29
43
 
30
- The first screen should separate purpose from delivery detail, not foreground CI mechanics. Distill `Proposal` into one or two sentences answering why the PR exists, then put two to ten unchecked, implementation-specific outcomes under `Implementation`. They are a coarse progress summary, not a copy of the detailed OpenSpec task ledger:
44
+ The first screen should separate purpose from delivery detail, not foreground CI mechanics. Distill `Proposal` into one or two sentences answering why the PR exists, then put one or more unchecked, implementation-specific outcomes under `Implementation`, with no fixed maximum. They are a coarse progress summary, not a copy of the detailed OpenSpec task ledger:
31
45
 
32
46
  ```markdown
33
47
  ## Proposal
@@ -153,13 +167,13 @@ node scripts/governance/check-delivery-body.mjs --body-file .artifacts/agent/pr-
153
167
 
154
168
  `--final` validates the body as if every Implementation item were checked, so it can run before the last marker changes. `--pr <n>` reads the current pull request body instead. A defect exits 1 and prints the same `draft-<code>` reason the finalization run would report. The check covers association, Implementation, layout, and Acceptance only; detailed tasks, evidence, and target state remain covered by the [finalization command](#finalization-command) inspection mode.
155
169
 
156
- The committed conditional manifest contains the same ordered text. Trusted policy validates membership but never checks or edits it and never claims that the scenarios passed. The authorized maintainer's manual merge or personal native auto-merge arm for the unchanged exact head is the acceptance decision; no acceptance comment, checkbox edit, review-approval requirement, JSON edit, acceptance PR, archive PR, or later command is needed.
170
+ The committed conditional manifest contains the same ordered text. Trusted policy validates membership but never checks or edits it and never claims that the scenarios passed. The authorized maintainer's direct merge, personal native auto-merge arm, or explicit request for the active agent to merge the unchanged currently eligible exact head is the acceptance decision; no acceptance comment, checkbox edit, review-approval requirement, JSON edit, acceptance PR, archive PR, or later command is needed.
157
171
 
158
172
  If the body list changes, candidate validation reruns and compares it with the committed manifest. If the head changes, all prior exact-head CI is stale. If only the body changes to disagree with the manifest, integration remains blocked until the list and committed candidate agree again. No lifecycle body edit is needed after green CI.
159
173
 
160
174
  ## Automated finalization
161
175
 
162
- The `OpenSpec finalization` workflow (`.github/workflows/openspec-finalization.yml`) runs on `pull_request_target` for `opened`, `synchronize`, `ready_for_review`, `reopened`, and `edited` events targeting `develop`, one event at a time per PR. It checks out default-branch policy, installs the pinned tooling without hooks, and runs `scripts/governance/publish-openspec-finalization.mjs --pr <n>`; the PR head enters that process only as the `openspec/` tree the pinned OpenSpec engine reads. An all-checked version-3 task-list draft with valid Acceptance and complete detailed pre-finalization inputs is re-read as one exact snapshot and marked ready; no content is changed, and incomplete, malformed, legacy, unassociated, forked, or raced drafts remain draft with a stable reason. A draft with any unchecked item is skipped quietly. A draft whose every task-list item is checked but whose promotion is refused for a body, task, evidence, or other content defect fails the run with an `OpenSpec finalization` error annotation naming the `draft-<code>` reason, so the stall shows as a red check instead of a green skip. Closed PRs are skipped. For a ready version-3 candidate the workflow reconciles the head to its finalized form:
176
+ The `OpenSpec finalization` workflow (`.github/workflows/openspec-finalization.yml`) runs on `pull_request_target` for `opened`, `synchronize`, `ready_for_review`, `converted_to_draft`, `reopened`, and `edited` events targeting `develop`, one event at a time per PR. It checks out default-branch policy, installs the pinned tooling without hooks, and runs `scripts/governance/publish-openspec-finalization.mjs --pr <n>`; the PR head enters that process only as the `openspec/` tree the pinned OpenSpec engine reads. An all-checked version-3 task-list draft with valid Acceptance and complete detailed pre-finalization inputs is re-read as one exact snapshot and marked ready; no content is changed, and incomplete, malformed, legacy, unassociated, forked, or raced drafts remain draft with a stable reason. A draft with any unchecked item is skipped quietly. A finalized PR returned to draft is promoted again when its body still has every item checked, its fence names the exact head as finalized, and the tree holds exactly that archive with no active change; otherwise it stays draft with `draft-finalization-redraft-form`. A draft whose every task-list item is checked but whose promotion is refused for a body, task, evidence, or other content defect fails the run with an `OpenSpec finalization` error annotation naming the `draft-<code>` reason, so the stall shows as a red check instead of a green skip. Closed PRs are skipped. For a ready version-3 candidate the workflow reconciles the head to its finalized form:
163
177
 
164
178
  - **Active and current:** ordinary finalization with today's UTC date; one `docs(openspec): finalize <change>` commit.
165
179
  - **Finalized, valid, and current:** nothing is pushed; the run reports `already-finalized`.
@@ -214,7 +228,7 @@ Before a first finalization, canonical specs must still equal the selected targe
214
228
 
215
229
  ## Conditional acceptance and derived receipt
216
230
 
217
- `acceptance.md` truthfully says its verdict activates only when an authorized human manually merges the containing exact head after required current-head checks or personally arms native auto-merge for that unchanged head and GitHub integrates it after those checks. Its `openspec-delivery` block records stable pre-merge facts:
231
+ `acceptance.md` truthfully says its verdict activates only when an authorized human directly merges the containing exact head after required current-head checks, explicitly asks the active agent to execute that protected exact-head merge while it is eligible, or personally arms native auto-merge for the unchanged head and GitHub integrates it after those checks. Its `openspec-delivery` block records stable pre-merge facts:
218
232
 
219
233
  - schema version, repository, change, and source PR;
220
234
  - archive and manifest paths and finalization date;
@@ -223,7 +237,7 @@ Before a first finalization, canonical specs must still equal the selected targe
223
237
  - archive, canonical-spec, task, and evidence digests; and
224
238
  - explicit known-gap dispositions.
225
239
 
226
- It does not predict source head, merge commit, merger, or merge time. The PR head contains the manifest, so required CI binds those bytes without a recursive self-hash. After merge, the shared reader combines the manifest with immutable GitHub source head, successful required run, authorized human actor and permission, integration route, merge commit/time, and `develop` ancestry. Direct manual integration requires no active automatic authority. Human-enabled native auto-merge additionally requires a matching final-head enable event and `auto_merge.enabled_by`, with no later candidate commit or disable event. Queue, App/Bot, automation-owned, stale, malformed, or differently authored authority remains invalid. A human enable followed by disable before a valid manual merge is an abandoned attempt, not contradictory provenance. That shared result is the durable version-3 receipt used by audit and cleanup.
240
+ It does not predict source head, merge commit, merger, or merge time. The PR head contains the manifest, so required CI binds those bytes without a recursive self-hash. After merge, the shared reader combines the manifest with immutable GitHub source head, successful required run, authorized human actor and permission, integration route, merge commit/time, and `develop` ancestry. Direct integration, including an explicitly requested agent invocation through the maintainer's credential, requires no active automatic authority and retains the same authenticated-human merge provenance. Human-enabled native auto-merge additionally requires a matching final-head enable event and `auto_merge.enabled_by`, with no later candidate commit or disable event. Queue, App/Bot, automation-owned, stale, malformed, or differently authored authority remains invalid. A human enable followed by disable before a valid direct merge is an abandoned attempt, not contradictory provenance. That shared result is the durable version-3 receipt used by audit and cleanup.
227
241
 
228
242
  Unavailable, stale, automatic, unauthorized, conflicting, or contradictory provenance produces `invalid-provenance`/`blocked`, not acceptance. Post-merge code does not repair it by pushing `develop` or publishing another PR.
229
243
 
@@ -277,7 +291,7 @@ This bootstrap policy itself finishes under deployed version-2 authority. The fi
277
291
  - draft planning remaining unmerged;
278
292
  - explicit plan approval and same-PR implementation;
279
293
  - automated current-target finalization and exact-head CI;
280
- - authorized human integration, including manual merge and personally enabled exact-head native auto-merge, with plain acceptance scenarios;
294
+ - authorized human integration, including direct merge, an explicit exact-head agent-merge request, and personally enabled exact-head native auto-merge, with plain acceptance scenarios;
281
295
  - integrated canonical specs/archive and no generated acceptance/archive PR;
282
296
  - read-only `accepted-and-archived` verification and exact-head branch cleanup;
283
297
  - stale head/body/base/arm, incomplete tasks/evidence, malformed manifest, and automated or unauthorized auto-merge refusal fixtures; and
@@ -48,7 +48,11 @@ Conservative Development retains every pull-request owner but does not schedule
48
48
  node scripts/release/generate-validation-ownership-ledger.mjs --output .artifacts/validation/ownership-ledger.json
49
49
  ```
50
50
 
51
- The `terminal-host` owner is CI-only. A change under `native/terminal-host/` or to its run scripts selects, per platform (Windows x64, Linux x64, macOS arm64), one **Native terminal host** job and three **Native terminal host tests** partition jobs. The partition jobs each build the test profile and run one third of the serialized `role_integration` suite (`run-terminal-host-probe.mjs --partition <i>/3`, partitioned by sorted test name read from the suite's source); the build-and-probe job runs `npm run test:terminal-host` with the role suite skipped (`TERMINAL_HOST_PARTITION=none`), building the binary and Node-API lock artifact with Rust and Zig 0.15.2 for the lane's exact target and baseline CPU model and uploading the debug artifacts and frozen protocol manifest; Linux also runs bounded decoder fuzzing. The protected aggregate requires every partition of every selected platform, keyed by `partition` in the outcome authority and job envelope. Because the role suite no longer warms the resident launch paths in the build-and-probe job, its certification step runs one unmeasured `measure-resident-tabs.mjs` pass before the measured pass whose budgets are unchanged. Locally, `npm run test:terminal-host` without a partition is unchanged and runs everything in one process. Local Windows builds fail in `build.rs` unless `TERMINAL_HOST_LOCAL_BUILD=1` is set, because Zig's package fetch unpacks test data that workstation antivirus quarantines along with the installed process guardian. Native lanes also publish the terminal library they linked; a local build can link it instead of running Zig (see Local native iteration in [`architecture/resident-tabs.md`](architecture/resident-tabs.md)), which is development evidence only and never replaces selected CI. `TERMINAL_HOST_CONTRACTS_ONLY=1` is a library-only diagnostic and cannot substitute for selected CI. See [`native/terminal-host/README.md`](../native/terminal-host/README.md).
51
+ Containment-selected modular jobs build and test the debug `process-guardian.node` addon for their exact platform, export `A1_PROCESS_GUARDIAN_PATH` to that file, and run the launch-owner integration fixtures as Node processes. The tests exercise the real addon-held Windows job or Unix process group, addon-backed identity and terminal operations, and the Node exit watcher; no fixture invokes a process-guardian executable. The Windows startup lane keeps Defender real-time protection enabled while packaged startup loads the release addon.
52
+
53
+ The `terminal-host` owner is CI-only. A change under `native/terminal-host/` or to its run scripts selects, per platform (Windows x64, Linux x64, macOS arm64), one **Native terminal host** job and three **Native terminal host tests** partition jobs. The partition jobs each build the test profile and run one third of the serialized `role_integration` suite (`run-terminal-host-probe.mjs --partition <i>/3`, partitioned by sorted test name read from the suite's source); the feature-gated `role-host` binary stands in only for the fixed role grammar inside Rust integration tests and is never packaged. The build-and-probe job runs `npm run test:terminal-host` with the role suite skipped (`TERMINAL_HOST_PARTITION=none`), building `terminal-host.node` with Rust, Node-API 9, and Zig 0.15.2 for the lane's exact target and baseline CPU model. It loads the addon for its version and non-interactive probes, verifies the shared session-writer lock export, and uploads the debug addon and frozen protocol manifest; Linux also runs bounded decoder fuzzing.
54
+
55
+ The protected aggregate requires every partition of every selected platform, keyed by `partition` in the outcome authority and job envelope. Because the role suite no longer warms the resident launch paths in the build-and-probe job, its certification step runs one unmeasured `measure-resident-tabs.mjs` pass before the measured pass whose budgets are unchanged. Fidelity, latency, chaos, and certification start roles as `process.execPath dist/foundation/resident-tabs/host-entry.js` with `A1_TERMINAL_HOST_PATH` naming the addon. Windows cleanup identifies those Node-hosted residents by command line; it never kills every `node.exe` by image name. Locally, `npm run test:terminal-host` without a partition is unchanged and runs everything in one process. Local Windows builds fail in `build.rs` unless `TERMINAL_HOST_LOCAL_BUILD=1` is set, because Zig's package fetch unpacks test data that workstation antivirus quarantines. Native lanes also publish the terminal library they linked; a local build can link it instead of running Zig (see Local native iteration in [`architecture/resident-tabs.md`](architecture/resident-tabs.md)), which is development evidence only and never replaces selected CI. `TERMINAL_HOST_CONTRACTS_ONLY=1` is a library-only diagnostic and cannot substitute for selected CI. See [`native/terminal-host/README.md`](../native/terminal-host/README.md).
52
56
 
53
57
  ## Windows Full regression shards
54
58
 
@@ -122,7 +126,7 @@ Download these artifacts from the exact workflow run:
122
126
 
123
127
  For selected implementation candidates, record focused and pre-finalization full observations before finalization. Keep final head/run/selection identity in Actions and handoff rather than adding a post-success commit that invalidates that evidence. A successful selected PR run replaces an additional mandatory dispatch; standalone runs remain diagnostic, and numbered-package nightly recovery remains independent.
124
128
 
125
- Draft pull requests run no Development test suites. A ready version-3 PR whose body still identifies an active change defers testing and emits no protected aggregate while `OpenSpec finalization` publishes the candidate. A ready unassociated PR also fails closed when immutable base/head evidence shows that it introduces or restores active delivery content, or combines active-change edits with code or operational paths; it does not fall through to ordinary validation merely because its body fence is absent. Existing active-change documentation-only revisions and ordinary code PRs without active delivery paths retain their established routes. After finalization records the archive and acceptance manifest, the ordinary exact-head workflow runs selected product/governance lanes and `Finalized delivery validation` in parallel, then emits the stable protected `Development validation required` aggregate only when both authorities succeed. Green CI enables maintainer review and manual merge but does not claim human acceptance or merge automatically. The finalization fence update is the validation trigger, so no pre-finalization test run or later lifecycle body edit is required. Any subsequent eligible implementation commit changes the head and reruns applicable validation; any body change reruns readiness and finalized-record validation and must continue to match the committed manifest. Legacy acceptance-record-only PRs retain their separate trusted `Acceptance record validation` route. Queue availability remains explicitly unavailable inside a runner and is calculated from the Actions API during final run analysis rather than guessed.
129
+ Draft pull requests run no Development test suites. A ready version-3 PR whose body still identifies an active change defers testing and emits no protected aggregate while `OpenSpec finalization` publishes the candidate. A ready unassociated PR also fails closed when immutable base/head evidence shows that it introduces or restores active delivery content, or combines active-change edits with code or operational paths; it does not fall through to ordinary validation merely because its body fence is absent. Existing active-change documentation-only revisions and ordinary code PRs without active delivery paths retain their established routes. After finalization records the archive and acceptance manifest, the ordinary exact-head workflow runs selected product/governance lanes and `Finalized delivery validation` in parallel, then emits the stable protected `Development validation required` aggregate only when both authorities succeed. Green CI enables maintainer review and an explicit integration decision but does not claim human acceptance or merge automatically. After green exact-head validation, the maintainer may directly merge, personally arm native auto-merge, or explicitly ask the active agent to perform one expected-head protected squash merge for that currently eligible candidate. The finalization fence update is the validation trigger, so no pre-finalization test run or later lifecycle body edit is required. Any subsequent eligible implementation commit changes the head and reruns applicable validation; any body change reruns readiness and finalized-record validation and must continue to match the committed manifest. Legacy acceptance-record-only PRs retain their separate trusted `Acceptance record validation` route. Queue availability remains explicitly unavailable inside a runner and is calculated from the Actions API during final run analysis rather than guessed.
126
130
 
127
131
  ## Rollback
128
132
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.7-dev.792",
3
+ "version": "0.2.7-dev.806",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",
@@ -23,7 +23,9 @@
23
23
  },
24
24
  "scripts": {
25
25
  "clean": "node scripts/clean.mjs",
26
- "build": "node scripts/development/check-environment.mjs && npm run clean && tsc -p tsconfig.build.json && node scripts/release/generate-release-notes-resource.mjs && node scripts/pi/build-pi-settings-metadata.mjs && node scripts/pi/build-startup-public.mjs && node scripts/development/build-process-guardian.mjs && node scripts/release/generate-runtime-payload-inventory.mjs",
26
+ "build": "node scripts/development/check-environment.mjs && npm run build:managed && node scripts/development/prepare-local-terminal-host.mjs && node scripts/release/generate-runtime-payload-inventory.mjs",
27
+ "build:core": "node scripts/development/check-environment.mjs && npm run build:managed && node scripts/release/generate-runtime-payload-inventory.mjs",
28
+ "build:managed": "npm run clean && tsc -p tsconfig.build.json && node scripts/release/generate-release-notes-resource.mjs && node scripts/pi/build-pi-settings-metadata.mjs && node scripts/pi/build-startup-public.mjs && node scripts/development/build-process-guardian.mjs",
27
29
  "build:process-guardian": "node scripts/development/build-process-guardian.mjs",
28
30
  "build:terminal-host": "node scripts/development/build-terminal-host.mjs",
29
31
  "typecheck": "tsgo -p tsconfig.json --noEmit && tsgo -p tsconfig.bin.json",
@@ -65,7 +67,7 @@
65
67
  "start:pi": "npm run build && node scripts/development/dev-launch.mjs pi",
66
68
  "prepack": "node scripts/release/validate-packaged-release-note.mjs && node scripts/release/prepack-gate.mjs",
67
69
  "prepublishOnly": "npm run test:release",
68
- "prepare": "npm run build",
70
+ "prepare": "npm run build:core",
69
71
  "typecheck:bin": "tsgo -p tsconfig.bin.json"
70
72
  },
71
73
  "dependencies": {
@@ -1,5 +0,0 @@
1
- import { WindowsNativeProcessInspector, type InspectorCommandRunner } from "./windows-process-inspector.js";
2
- /** Verifies Darwin process identity through the bounded native guardian inspection command. */
3
- export declare class DarwinNativeProcessInspector extends WindowsNativeProcessInspector {
4
- constructor(helperPath: string, runner?: InspectorCommandRunner);
5
- }