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

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 (66) hide show
  1. package/dist/app/session-shell/session-shell-root.d.ts +0 -1
  2. package/dist/app/session-shell/session-shell-root.js +41 -15
  3. package/dist/app/session-shell/session-shell.js +5 -4
  4. package/dist/app/session-shell/session-viewport-controller.d.ts +2 -2
  5. package/dist/app/session-shell/session-viewport-controller.js +24 -12
  6. package/dist/composition/owned-ui.d.ts +1 -0
  7. package/dist/composition/owned-ui.js +27 -2
  8. package/dist/contracts/owned-ui/index.d.ts +1 -1
  9. package/dist/contracts/owned-ui/model.d.ts +3 -0
  10. package/dist/features/owned-ui/settings-app.js +9 -6
  11. package/dist/features/resident-tab/index.d.ts +3 -1
  12. package/dist/features/resident-tab/index.js +5 -1
  13. package/dist/features/resident-tab/shortcut-bindings-file.d.ts +8 -0
  14. package/dist/features/resident-tab/shortcut-bindings-file.js +40 -0
  15. package/dist/features/resident-tab/{shortcuts.d.ts → shortcut-declarations.d.ts} +5 -2
  16. package/dist/features/resident-tab/{shortcuts.js → shortcut-declarations.js} +5 -17
  17. package/dist/features/resident-tab/shortcut-projection.d.ts +29 -0
  18. package/dist/features/resident-tab/shortcut-projection.js +64 -0
  19. package/dist/foundation/release/bootstrap.js +1 -0
  20. package/dist/foundation/resident-tabs/bridge-client.d.ts +5 -0
  21. package/dist/foundation/resident-tabs/bridge-client.js +17 -1
  22. package/dist/foundation/resident-tabs/prompt-journal.d.ts +0 -2
  23. package/dist/foundation/resident-tabs/prompt-journal.js +0 -1
  24. package/dist/integrations/pi/components/owned-editor-ux.d.ts +2 -0
  25. package/dist/integrations/pi/components/owned-editor-ux.js +13 -0
  26. package/dist/integrations/pi/components/shell-editor-autocomplete.js +1 -0
  27. package/dist/integrations/pi/components/shell-presenters-info.d.ts +1 -0
  28. package/dist/integrations/pi/components/shell-presenters-info.js +10 -30
  29. package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -0
  30. package/dist/native/darwin-arm64/a1_session_lock.node +0 -0
  31. package/dist/native/darwin-arm64/manifest.json +3 -2
  32. package/dist/native/darwin-arm64/terminal-host +0 -0
  33. package/dist/native/darwin-arm64/terminal-host.manifest.json +7 -6
  34. package/dist/native/darwin-x64/a1_session_lock.node +0 -0
  35. package/dist/native/darwin-x64/manifest.json +5 -4
  36. package/dist/native/darwin-x64/process-guardian +0 -0
  37. package/dist/native/darwin-x64/terminal-host +0 -0
  38. package/dist/native/darwin-x64/terminal-host.manifest.json +7 -6
  39. package/dist/native/linux-x64/a1_session_lock.node +0 -0
  40. package/dist/native/linux-x64/manifest.json +3 -2
  41. package/dist/native/linux-x64/terminal-host +0 -0
  42. package/dist/native/linux-x64/terminal-host.manifest.json +7 -6
  43. package/dist/native/win32-x64/a1_session_lock.node +0 -0
  44. package/dist/native/win32-x64/manifest.json +4 -3
  45. package/dist/native/win32-x64/process-guardian.exe +0 -0
  46. package/dist/native/win32-x64/terminal-host.exe +0 -0
  47. package/dist/native/win32-x64/terminal-host.manifest.json +6 -5
  48. package/dist/ui/components/index.d.ts +2 -2
  49. package/dist/ui/components/index.js +1 -1
  50. package/dist/ui/components/selection-copy.d.ts +2 -0
  51. package/dist/ui/components/selection-copy.js +13 -0
  52. package/dist/ui/components/submitted-prompt.d.ts +7 -4
  53. package/dist/ui/components/submitted-prompt.js +18 -10
  54. package/dist/ui/components/transcript-viewport.d.ts +10 -6
  55. package/dist/ui/components/transcript-viewport.js +28 -13
  56. package/dist/ui/settings/declarations.d.ts +13 -1
  57. package/dist/ui/settings/declarations.js +12 -1
  58. package/dist/ui/settings/migrations.js +7 -0
  59. package/docs/architecture/evidence/resident-tabs/macos-x64.md +1 -1
  60. package/docs/architecture/process-guardian-provenance.md +1 -1
  61. package/docs/architecture/project-structure.md +8 -0
  62. package/docs/architecture/resident-tabs.md +18 -2
  63. package/docs/architecture/terminal-host-provenance.md +1 -1
  64. package/docs/architecture/toolchain.md +1 -1
  65. package/docs/validation.md +3 -3
  66. package/package.json +1 -1
@@ -6,6 +6,8 @@ export interface TranscriptPromptAnchor {
6
6
  readonly lastRow: number;
7
7
  /** The source first row, including its submitted-prompt styling and timestamp. */
8
8
  readonly sourceRow: string;
9
+ /** Right-edge metadata cells omitted from semantic prompt copies. */
10
+ readonly copySuffixWidth?: number;
9
11
  /** Optional source-styled row used only while prominent and not hovered. */
10
12
  readonly prominentSourceRow?: string;
11
13
  }
@@ -36,7 +38,11 @@ export interface TranscriptViewportFrameInput {
36
38
  /** Final transient rows to bottom-align with unused viewport space while content fits. */
37
39
  readonly bottomAlignedTailRowCount?: number;
38
40
  readonly dockRows: readonly string[];
39
- readonly explicitCopyDockRows?: number;
41
+ /** Zero-based dock rows admitted to frame copying, classified before dock composition. */
42
+ readonly copyableDockRows?: readonly {
43
+ readonly row: number;
44
+ readonly prompt?: "first" | "continuation";
45
+ }[];
40
46
  readonly promptAnchors: readonly TranscriptPromptAnchor[];
41
47
  readonly width: number;
42
48
  readonly height: number;
@@ -142,7 +148,6 @@ export interface TranscriptViewportFrame {
142
148
  readonly cacheEntries: number;
143
149
  };
144
150
  }
145
- type SelectionCopyIntent = "automatic" | "explicit";
146
151
  /**
147
152
  * A1-owned viewport state. It knows only rows and semantic prompt anchors; Pi
148
153
  * components and transcript payloads remain outside this neutral boundary.
@@ -172,9 +177,9 @@ export declare class TranscriptViewport {
172
177
  extendSelection(column: number, frameRow: number, now?: number, autoScroll?: boolean): boolean;
173
178
  releaseSelection(): boolean;
174
179
  clearSelection(): boolean;
175
- selectedText(intent?: SelectionCopyIntent): string | null;
180
+ selectedText(): string | null;
176
181
  /** Captures visible selected text, not off-screen history. */
177
- captureSelectedText(intent?: SelectionCopyIntent): SelectionCopySnapshot | null;
182
+ captureSelectedText(): SelectionCopySnapshot | null;
178
183
  scrollBy(lines: number, now?: number): boolean;
179
184
  scrollTo(position: number, now?: number): boolean;
180
185
  scrollToEnd(now?: number): boolean;
@@ -186,7 +191,6 @@ export declare class TranscriptViewport {
186
191
  clearTransient(): void;
187
192
  compose(input: TranscriptViewportFrameInput): TranscriptViewportFrame;
188
193
  /** Reuses an established transcript frame when only same-height dock rows changed. */
189
- composeDockOnly(dockRows: readonly string[], width: number, height: number, explicitCopyDockRows?: number): TranscriptViewportFrame | null;
194
+ composeDockOnly(dockRows: readonly string[], width: number, height: number, copyableDockRows?: TranscriptViewportFrameInput["copyableDockRows"]): TranscriptViewportFrame | null;
190
195
  }
191
196
  export declare function assertTranscriptViewportFrameDescriptor(descriptor: TranscriptViewportFrameDescriptor): void;
192
- export {};
@@ -63,7 +63,7 @@ export class TranscriptViewport {
63
63
  #contentWidth = 0;
64
64
  #selectionWidth = 0;
65
65
  #viewportHeight = 0;
66
- #explicitCopyDockRows = 0;
66
+ #copyableDockRows = new Map();
67
67
  #firstSelectableRow = 0;
68
68
  #selectionRowAnchors = [];
69
69
  #frameId = 0;
@@ -179,14 +179,14 @@ export class TranscriptViewport {
179
179
  this.#selectionRevision += 1;
180
180
  return true;
181
181
  }
182
- selectedText(intent = "automatic") {
182
+ selectedText() {
183
183
  const selection = this.#visibleSelection();
184
184
  if (selection === undefined)
185
185
  return null;
186
186
  const copiedLines = Array.from({ length: Math.max(0, selection.end.line - selection.start.line + 1) }, (_value, index) => selection.start.line + index).filter(line => {
187
187
  const anchor = this.#selectionRowAnchors[line];
188
188
  return anchor?.kind === "document" && anchor.row < this.#selectableDocumentRowCount
189
- || intent === "explicit" && anchor?.kind === "dock" && anchor.fromBottom < this.#explicitCopyDockRows;
189
+ || anchor?.kind === "dock" && this.#copyableDockRows.has(anchor.fromBottom);
190
190
  });
191
191
  if (copiedLines.length === 0)
192
192
  return null;
@@ -199,18 +199,24 @@ export class TranscriptViewport {
199
199
  const text = textSelectionText(selection, this.#selectionRows, line => {
200
200
  const row = this.#selectionRows[line] ?? "";
201
201
  const anchor = this.#selectionRowAnchors[line];
202
- return prompt !== undefined && anchor?.kind === "document"
203
- ? selectionCopyLineContent(row, anchor.row === prompt.firstRow ? "first" : "continuation")
204
- : usefulTextLineContent(row);
202
+ const dock = anchor?.kind === "dock" ? this.#copyableDockRows.get(anchor.fromBottom) : undefined;
203
+ if (prompt !== undefined && anchor?.kind === "document") {
204
+ const first = anchor.row === prompt.firstRow;
205
+ const copyRow = first && (prompt.copySuffixWidth ?? 0) > 0
206
+ ? truncateToWidth(row, Math.max(0, displayWidth(row) - (prompt.copySuffixWidth ?? 0)))
207
+ : row;
208
+ return selectionCopyLineContent(copyRow, first ? "first" : "continuation");
209
+ }
210
+ return dock?.prompt !== undefined ? selectionCopyLineContent(row, dock.prompt) : usefulTextLineContent(row);
205
211
  }, line => included.has(line));
206
212
  return text.length === 0 ? null : text;
207
213
  }
208
214
  /** Captures visible selected text, not off-screen history. */
209
- captureSelectedText(intent = "automatic") {
210
- if (intent === "automatic" && !this.#copyableSelection)
215
+ captureSelectedText() {
216
+ if (!this.#copyableSelection)
211
217
  return null;
212
218
  const started = performance.now();
213
- let text = this.selectedText(intent);
219
+ let text = this.selectedText();
214
220
  if (text === null)
215
221
  return null;
216
222
  text = text.trim();
@@ -313,7 +319,7 @@ export class TranscriptViewport {
313
319
  this.#contentWidth = 0;
314
320
  this.#selectionWidth = 0;
315
321
  this.#viewportHeight = 0;
316
- this.#explicitCopyDockRows = 0;
322
+ this.#copyableDockRows.clear();
317
323
  this.clearTransient();
318
324
  this.#frame = null;
319
325
  }
@@ -329,7 +335,7 @@ export class TranscriptViewport {
329
335
  const theme = input.theme ?? PLAIN_THEME;
330
336
  const previousDescriptor = this.#frame?.descriptor ?? null;
331
337
  const dock = input.dockRows.length > height ? input.dockRows.slice(-height) : [...input.dockRows];
332
- this.#explicitCopyDockRows = clamp(input.explicitCopyDockRows ?? 0, 0, dock.length);
338
+ this.#copyableDockRows = copyableDockRowsBySuffix(input.dockRows, input.copyableDockRows);
333
339
  const viewportHeight = Math.max(0, height - dock.length);
334
340
  const bottomAlignedTailRowCount = clamp(input.bottomAlignedTailRowCount ?? 0, 0, input.documentRows.length);
335
341
  const transientAlignmentGapRows = bottomAlignedTailRowCount > 0
@@ -606,7 +612,7 @@ export class TranscriptViewport {
606
612
  return frame;
607
613
  }
608
614
  /** Reuses an established transcript frame when only same-height dock rows changed. */
609
- composeDockOnly(dockRows, width, height, explicitCopyDockRows = 0) {
615
+ composeDockOnly(dockRows, width, height, copyableDockRows = []) {
610
616
  const previous = this.#frame;
611
617
  const boundedWidth = Math.max(1, width);
612
618
  const boundedHeight = Math.max(0, height);
@@ -642,7 +648,7 @@ export class TranscriptViewport {
642
648
  };
643
649
  assertTranscriptViewportFrameDescriptor(descriptor);
644
650
  this.#selectionRows = [...this.#selectionRows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight);
645
- this.#explicitCopyDockRows = clamp(explicitCopyDockRows, 0, dock.length);
651
+ this.#copyableDockRows = copyableDockRowsBySuffix(dockRows, copyableDockRows);
646
652
  const frame = {
647
653
  rows: [...previous.rows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight),
648
654
  contentWidth: previous.contentWidth,
@@ -961,6 +967,15 @@ function assertDocumentRange(range, label) {
961
967
  function sameRectangle(left, right) {
962
968
  return left === null ? right === null : right !== null && left.rowStart === right.rowStart && left.rowEnd === right.rowEnd;
963
969
  }
970
+ function copyableDockRowsBySuffix(dockRows, declarations) {
971
+ const result = new Map();
972
+ for (const declaration of declarations ?? []) {
973
+ if (!Number.isSafeInteger(declaration.row) || declaration.row < 0 || declaration.row >= dockRows.length)
974
+ continue;
975
+ result.set(dockRows.length - declaration.row - 1, declaration.prompt === undefined ? {} : { prompt: declaration.prompt });
976
+ }
977
+ return result;
978
+ }
964
979
  function clamp(value, minimum, maximum) {
965
980
  return Math.min(Math.max(value, minimum), maximum);
966
981
  }
@@ -1,4 +1,4 @@
1
- export declare const OWNED_UI_SETTINGS_VERSION = 16;
1
+ export declare const OWNED_UI_SETTINGS_VERSION = 17;
2
2
  export type OwnedUiSettingValue = string | number | boolean;
3
3
  export type OwnedUiSettingApplication = "live" | "restart";
4
4
  export interface OwnedUiSettingDeclaration {
@@ -32,6 +32,18 @@ export declare const OWNED_SETTING_DECLARATIONS: Readonly<{
32
32
  defaultValue: true;
33
33
  allowedValues: readonly [true, false];
34
34
  }>;
35
+ readonly timeFormat: Readonly<{
36
+ id: "timeFormat";
37
+ label: "Time format";
38
+ section: Readonly<{
39
+ id: "generic";
40
+ title: "Generic";
41
+ }>;
42
+ description: "Format used by submitted-prompt timestamps and the resident tab clock.";
43
+ application: "live";
44
+ defaultValue: "8:00 PM";
45
+ allowedValues: readonly ["20:00", "8:00 PM"];
46
+ }>;
35
47
  readonly accentColor: Readonly<{
36
48
  id: "accentColor";
37
49
  label: "Accent color";
@@ -1,4 +1,4 @@
1
- export const OWNED_UI_SETTINGS_VERSION = 16;
1
+ export const OWNED_UI_SETTINGS_VERSION = 17;
2
2
  const UI_ACCENT_COLOR_CHOICES = Object.freeze([
3
3
  "purple", "blue", "cyan", "green", "orange", "pink",
4
4
  ]);
@@ -25,6 +25,17 @@ export const OWNED_SETTING_DECLARATIONS = Object.freeze({
25
25
  defaultValue: true,
26
26
  allowedValues: Object.freeze([true, false]),
27
27
  }),
28
+ timeFormat: Object.freeze({
29
+ id: "timeFormat",
30
+ label: "Time format",
31
+ section: GENERIC_SECTION,
32
+ description: "Format used by submitted-prompt timestamps and the resident tab clock.",
33
+ application: "live",
34
+ defaultValue: "8:00 PM",
35
+ allowedValues: Object.freeze([
36
+ "20:00", "8:00 PM",
37
+ ]),
38
+ }),
28
39
  accentColor: Object.freeze({
29
40
  id: "accentColor",
30
41
  label: "Accent color",
@@ -130,6 +130,13 @@ export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
130
130
  return migrated;
131
131
  },
132
132
  }),
133
+ Object.freeze({
134
+ to: 17,
135
+ description: "Introduce the shared twelve-hour time format default.",
136
+ migrate(values) {
137
+ return { ...values };
138
+ },
139
+ }),
133
140
  ]);
134
141
  /** Shape-selected compatibility for the metadata-free product object. */
135
142
  export function normalizeOwnedUiSettingsValues(source) {
@@ -15,7 +15,7 @@ Physical acceptance against a published candidate package is pending; the operat
15
15
 
16
16
  ## Automated evidence
17
17
 
18
- - Publication `terminal_hosts` and `guardians` jobs on `macos-15-intel`: the release terminal host and process guardian are built for Intel macOS, the host's `--version` identity is checked, and packing refuses a missing or altered artifact.
18
+ - Publication `terminal_hosts` and `guardians` jobs on `macos-15` (arm64): the release terminal host and process guardian are cross-compiled for Intel macOS with `--target x86_64-apple-darwin`, the host's `--version` identity is checked through Rosetta 2 when the runner can execute it (otherwise the manifest records a Mach-O architecture check), and packing refuses a missing or altered artifact. Before 2026-10-16 the same builds ran natively on `macos-15-intel`.
19
19
  - Known gap: the pull-request `terminal-host` and `resident-chaos` suites run on macOS arm64 only, because modular validation outcomes are keyed by platform without architecture. Intel macOS has build and identity evidence but no automated behavior suite of its own.
20
20
 
21
21
  ## Physical verdicts
@@ -21,7 +21,7 @@ dist/native/darwin-x64/process-guardian
21
21
  dist/native/darwin-arm64/process-guardian
22
22
  ```
23
23
 
24
- The publication `guardians` job builds them on `windows-2025`, `ubuntu-24.04`, `macos-15` (arm64), and `macos-15-intel` (x64).
24
+ The publication `guardians` job builds them on `windows-2025`, `ubuntu-24.04`, and `macos-15` (arm64); the macOS arm64 job also cross-compiles the x64 guardian with `--target x86_64-apple-darwin`, and its manifest records whether the identity was observed by execution or only from the Mach-O header.
25
25
 
26
26
  Each supported artifact must be named in the release integrity manifest with target triple, crate version, guardian protocol version, SHA-256, byte size, build provenance, and signature/attestation status. The build marks a platform supported only after its native containment integration runs on that platform. A missing, altered, incompatible, unsupported, or wrong-platform artifact is a concise containment launch error; A1 never silently falls back to PID-only cleanup.
27
27
 
@@ -103,6 +103,14 @@ a1 session link-worktree <absolute-worktree>
103
103
  git -C .worktrees/<task-id> status
104
104
  ```
105
105
 
106
+ Claude Code sessions have no A1 shell-tool session, so they link with Claude Code's `EnterWorktree` tool by task name. The tracked `.claude/settings.json` wires a `WorktreeCreate` hook, `scripts/governance/claude-worktree-create.mjs`, that creates `.worktrees/<task-id>` detached at fresh `origin/develop`, or returns it when it is already registered, so Claude Code anchors the session there without an approval prompt. The agent then creates the task branch inside it:
107
+
108
+ ```sh
109
+ git -C .worktrees/<task-id> switch -c <type>/<short-description>
110
+ ```
111
+
112
+ Passing an absolute `path` to `EnterWorktree` is reserved for a worktree created by hand, from the primary checkout, before any shell command in that session; Claude Code otherwise keeps its shell tools anchored to the primary checkout and refuses their commands. The matching `WorktreeRemove` hook declines removal, so sessions leave with `ExitWorktree` `keep` and worktrees are removed only by the cleanup tool.
113
+
106
114
  Inventory is read-only and advisory because another runtime may win a later race. A successful link response confirming the exact canonical worktree is required before planning, implementation, test, or delivery-documentation edits. If inventory or linking fails, reports a live/unverifiable owner, loses a race, or confirms another path, stop task edits and report the blocker; use a separate fresh worktree rather than overriding, unlinking, or recovering that owner. A session resuming an existing delivery or switching streams atomically claims and links the exact worktree before editing it.
107
115
 
108
116
  The link acquires a cooperative live-session editing claim and switches bare A1's footer repository context and pull-request discovery. It does not change process or tool cwd, create a Git worktree lock, or register, release, or authorize cleanup. Clean unlink/disposal releases live editing authority; a crashed owner becomes reusable only after exact process death is verified. Repository commands therefore keep an explicit worktree path. The primary worktree stays on `develop` for integration and must not be used for task edits. Do not edit, adopt, move, or remove another session's worktree.
@@ -238,6 +238,18 @@ The channels carry bounded generation-1 frames: a u32 little-endian length follo
238
238
  - Image protocols are not forwarded in the first version; the terminal identity the child sees disables them and existing text fallback applies. Extension parity is claimed only for the certified text-terminal contract.
239
239
  - **Detach.** On detach the client restores the outer terminal's keyboard and mouse modes exactly. Enhanced keyboard sequences must not leak into the parent shell (herdr CHANGELOG lesson). Preserve A1's surviving-owner restoration for a killed attach process; an in-process panic/fatal hook cannot run after forced termination. The outer bootstrap/exit-notice owner performs bounded emergency restoration without giving either guardian terminal-byte relay authority.
240
240
 
241
+ ### 6a. Solo interaction parity (2026-10-17)
242
+
243
+ Direct `a1 solo` is the behavioural oracle for every viewed resident child. Only the tab strip row and its tab actions, detach instead of exit, the hidden session name, process residency, and the text fallback for unsupported image protocols may differ.
244
+
245
+ - **Semantic input.** Attach resolves tab actions first, then names every other outer event as a bounded generation-1 `input.event`: key identity from the libghostty key table, `MOD_*` modifiers, produced text, repeat, focus, or a pointer gesture already translated to child rows. The server validates and fences it like byte input. The holder encodes it with libghostty against the child's live key, mouse, focus, and bracketed-paste modes, and falls back to the CSI u bytes direct terminals send for modified Enter, Tab, Backspace, and Escape. Double `Ctrl+C` is recognized from the key identity before any encoding; a paste or pointer activation completes another input sequence and resets the chord.
246
+ - **Compatibility.** `semantic-input` and `terminal-effects` are advertised features. A same-generation holder without semantic encoding answers `legacy-holder`; attach re-sends the refused event as bytes and keeps the byte path. That fallback is not certified against the parity contract.
247
+ - **Typed effects.** The holder registers libghostty's clipboard-write callback, which normalizes OSC 52 and OSC 1337. A write from the viewed tab crosses as one bounded `terminal.effect`; attach writes one OSC 52 for it. A hidden tab's write is dropped, never replayed into a later view. Payloads are never logged.
248
+ - **Modes and cursor.** Each surface carries the child's cursor cell and modes. Attach mirrors a cursor shape the child chose and resets it on detach or failure; a default block cursor is left to the outer terminal, as direct A1 leaves it.
249
+ - **Shortcut projection.** The owned child projects the effective A1 keybindings plus `keybindings.json` into one assignment per tab action: a key an A1 action holds never becomes a tab shortcut, and the earlier declaration keeps a duplicated key. It publishes the projection as `bridge.shortcuts`; the server relays it as `tabs.shortcuts`, and attach dispatches and labels exactly that projection. `/hotkeys` renders the same projection. A shared case file is replayed by both resolvers.
250
+ - **Committed presentation.** A requested view commits only with a topology that lists it and a complete target surface. Surfaces of the committed view apply in revision order, and revisions rise across holder incarnations. A retained re-offer is marked stale and parks for at most 120 ms while the fresh frame arrives. One compositor writes strip, body, overlay, and final cursor inside a single synchronized update, and refuses clear-only, blank, and cursor-less strip-only frames. Clock, hover, and title-animation damage repaints only the strip and restores the child's cursor.
251
+ - **Evidence.** One interaction matrix drives three layers: an owned-shell oracle that records content-free observations, role fixtures that drive real server, holder, and attach processes with a certified parity child, and an exact-artifact harness, `verify-interaction-parity.mjs`, that compares the bytes the same child observes directly and through the built host on each platform. Observations carry digests and lengths, never content. Each platform's verdict stands alone.
252
+
241
253
  ### 7. Status and attention come from the tab bridge
242
254
 
243
255
  Tab status is an A1-owned state machine fed by `bridge.status`:
@@ -278,7 +290,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
278
290
  - `registryRevision`, `epoch`, and `bootId`
279
291
  - **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.
280
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.
281
- - **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 awaits a successful durable journal commit before dispatch; timeout/failure leaves the prompt in the editor with a visible failure and dispatches nothing. Correlate it with the committed session entry, and retire it only after the corresponding session data is synchronized; 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. 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.
293
+ - **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.
282
294
  - **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.
283
295
 
284
296
  ### 9. Failure and recovery
@@ -389,7 +401,7 @@ Every queue, buffer, file, and collection the resident roles keep has a declared
389
401
  | Diagnostic logs | 5 MiB × 3 generations per role | `roles/diagnostics.rs` | `logs_rotate_at_their_cap_and_keep_a_bounded_number_of_generations` |
390
402
  | Crash records | 64 KiB each, 50 records, 30 days | `roles/diagnostics.rs`, `shell/watchdog.rs` | `crash_records_are_distinct_bounded_and_allowlisted`, `crash_record_is_bounded_and_owner_only` |
391
403
  | Last screen and private stderr sink | 2 MiB; 5 MiB × 3 generations, 7 days | `roles/recovery_artifacts.rs` | `recovery_files_are_plain_bounded_private_and_rotated`, `fixture_stderr_sink_caps_content` |
392
- | Prompt journal | 8 MiB, 2 MiB per record | `src/foundation/resident-tabs/prompt-journal.ts` | `bounds append size` |
404
+ | Prompt journal | 8 MiB, 2 MiB per bounded metadata/text record; attachment payloads excluded | `src/foundation/resident-tabs/prompt-journal.ts` | `bounds append size`; `keeps a legal multi-image payload out of bounded journal admission and sends every image` |
393
405
  | Session reconciliation read | 64 MiB | `src/foundation/resident-tabs/recovery.ts` | `refuses to scan a session past the 64 MiB reconciliation cap` |
394
406
  | Release-hold report | 32 uncertainty reasons; 1 MiB read | `roles/release_holds.rs`, `release-holds.ts`, `bounded-host-command.ts` | `uncertainty_reasons_stay_within_their_cap`; `fails a release-hold report that pushes past its byte cap without returning a prefix` |
395
407
  | Maintenance exchange | 5 s reads, 60 s stops, 4 MiB report | `roles/maintain.rs`, `maintenance.ts`, `bounded-host-command.ts` | `fails a maintenance report that pushes past its byte cap without returning a prefix`, `kills and rejects a command that outlives its deadline` |
@@ -570,6 +582,10 @@ The user made these decisions on 2026-09-24:
570
582
  - `Ctrl+C` twice leaves `a1` from any tab while tabs keep running. There is no separate detach key.
571
583
  - Reliability is a first-class, release-gating requirement (Decision 16).
572
584
 
585
+ On 2026-10-16 the `ci-critical-path` change replaced the Intel runner for the release builds:
586
+
587
+ - The macOS x64 process guardian and terminal host are cross-compiled on the `macos-15` arm64 runner (`npm run build:terminal-host -- --target x86_64-apple-darwin`, same for the guardian) and uploaded beside the arm64 artifacts; the Intel runner took 6 to 11 minutes for builds the arm64 runner finishes in about 4, and it gated packing in every publication. The x64 executable's `--version` identity is read by executing it through Rosetta 2 when the runner can; otherwise the manifest records a Mach-O architecture check as `provenance.identityCheck: "mach-o-header"` instead of a report nobody observed. Intel macOS stays certified separately and its physical acceptance record is unchanged; no publication lane ran the x64 bytes beyond that identity check before either, because the published-pair smoke matrix has no Intel lane.
588
+
573
589
  On 2026-10-14 the user resolved the milestone-6 `darwin-x64` question:
574
590
 
575
591
  - Add an Intel macOS runner (`macos-15-intel`) for both the process guardian and the terminal host, so every release carries `darwin-x64` binaries beside `darwin-arm64`. Intel macOS is certified separately from Apple silicon: it needs its own physical acceptance record, and the chaos group runs on macOS arm64 only.
@@ -53,7 +53,7 @@ The console proof requires:
53
53
  - Rust/Cargo for the A1 terminal host;
54
54
  - Zig `0.15.x` with patch `>= 0.15.2` to build `libghostty-vt`;
55
55
  - the platform C/C++ build toolchain required by Rust and the PTY/input crates (Visual Studio 2022 Build Tools/MSVC on Windows, the runner image's Xcode Clang on macOS, GCC on Linux);
56
- - release builds on `windows-2025`, `ubuntu-24.04`, `macos-15` (arm64), and `macos-15-intel` (x64);
56
+ - release builds on `windows-2025`, `ubuntu-24.04`, and `macos-15` (arm64, which also cross-compiles the x64 artifact with `--target x86_64-apple-darwin`);
57
57
  - an existing terminal emulator for manual validation;
58
58
  - an isolated disposable worker for automated physical validation.
59
59
 
@@ -36,7 +36,7 @@ It contains no PTY, terminal emulator, browser/desktop GUI, custom renderer, inp
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. `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, macOS arm64, and macOS x64 runners 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. `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.
40
40
 
41
41
  The process guardian inherits terminal handles but transports no terminal bytes. See [`process-guardian-provenance.md`](process-guardian-provenance.md).
42
42
 
@@ -48,14 +48,14 @@ 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 Windows x64, Linux x64, and macOS arm64 **Native terminal host** jobs. Each builds the binary and Node-API lock artifact with Rust and Zig 0.15.2 for the lane's exact target and baseline CPU model, runs `npm run test:terminal-host`, and uploads the debug artifacts and frozen protocol manifest; Linux also runs bounded decoder fuzzing. 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
+ 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).
52
52
 
53
53
  ## Windows Full regression shards
54
54
 
55
- Standalone and PR-attached Full regression run each Windows runtime (Node 22 and Node 24) as four hosted-runner shards derived from the one canonical `full-release` plan: `core` (command gates and the bounded-parallel ordinary remainder), `resource` (the serial resource-sensitive invocation with its unchanged explicit bound), `rendering` (the serial isolated rendering invocation), and `package` (packing, the shared exact-package installation, Defender-backed first-attempt startup before package contracts, package smoke, update timing, and the exhaustive published-predecessor invocation). Each shard checks out, installs, builds, and records its own build receipt; only the package shard packs and installs the candidate. Linux, macOS, local `npm run test:full`, and publication validation keep one sequential complete plan.
55
+ Standalone and PR-attached Full regression run each Windows runtime (Node 22 and Node 24) as five hosted-runner shards derived from the one canonical `full-release` plan: `core` (command gates and the bounded-parallel ordinary remainder), `resource` (the serial resource-sensitive invocation with its unchanged explicit bound), `rendering` (the serial isolated rendering invocation), `package` (packing, its own exact-package installation, Defender-backed first-attempt startup before package contracts, package smoke, and update timing), and `predecessor` (packing, its own exact-package installation, and the exhaustive published-predecessor invocation, which took 11 to 12 minutes of the former package shard on Windows). Each shard checks out, installs, builds, and records its own build receipt; the package and predecessor shards each pack and install their own candidate, and no installed state crosses shards. Linux, macOS, local `npm run test:full`, and the sequential publication lanes keep one complete plan with one shared installation.
56
56
 
57
57
  ```
58
- node scripts/release/run-validation-tier.mjs --full-shard <core|resource|rendering|package> --result <path>
58
+ node scripts/release/run-validation-tier.mjs --full-shard <core|resource|rendering|package|predecessor> --result <path>
59
59
  ```
60
60
 
61
61
  `partitionFullRegressionPlan` assigns every canonical command and Vitest invocation to exactly one shard and rejects unknown or duplicate work; the build is the only per-shard prerequisite. Each shard result carries its shard identity, assigned work, and the canonical-plan digest. A per-runtime collector job reconstructs one ordinary `a1-validation-outcomes-v1` result only when all four current-run shards for the exact source, runtime, run, attempt, and plan passed and each reported exactly its assigned work; the reconstructed result then binds the unchanged `windows-2025-node22` or `windows-2025-node24` lane, and the required aggregate still demands exactly four lanes. The collector's summary reports each shard's time and the elapsed span across overlapping shards. Nightly triage maps a failed shard's owners to the canonical Windows lane and names the shard job, and it rejects more than one startup evidence file for a lane.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.7-dev.788",
3
+ "version": "0.2.7-dev.792",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",