@timurproko/a1 0.2.7-dev.834 → 0.2.7-dev.835

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.
@@ -2,6 +2,7 @@ export interface TabLaunchCredentials {
2
2
  readonly tabId: string;
3
3
  readonly token: string;
4
4
  readonly endpoint: string;
5
+ readonly releaseId?: string;
5
6
  }
6
7
 
7
8
  export function formatResidentFallbackNotice(reason: string): string;
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
 
3
- const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT"];
3
+ const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT", "A1_TAB_RELEASE_ID"];
4
4
 
5
5
  /** @param {string} reason */
6
6
  export function formatResidentFallbackNotice(reason) {
@@ -83,12 +83,12 @@ export function consumeTabLaunchContext(arguments_, environment) {
83
83
  const incarnationArgument = uiArguments.indexOf("--tab-incarnation");
84
84
  const tabIncarnation = incarnationArgument === -1 ? undefined : Number(uiArguments[incarnationArgument + 1]);
85
85
  if (incarnationArgument !== -1) uiArguments.splice(incarnationArgument, 2);
86
- const [tabId, token, endpoint] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
86
+ const [tabId, token, endpoint, releaseId] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
87
87
  for (const name of TAB_ENVIRONMENT_NAMES) delete environment[name];
88
88
  if (!tabMode) return { uiArguments, tabCredentials: undefined, tabStandby: false };
89
89
  if (!tabId || !token || !endpoint || !tabDataDir || tabDataDir.includes("\0")
90
90
  || typeof tabIncarnation !== "number" || !Number.isSafeInteger(tabIncarnation) || tabIncarnation < 1) {
91
91
  throw new Error("A1 tab mode requires bridge credentials, recovery directory, and incarnation");
92
92
  }
93
- return { uiArguments, tabCredentials: { tabId, token, endpoint }, tabDataDir, tabIncarnation, tabStandby };
93
+ return { uiArguments, tabCredentials: { tabId, token, endpoint, ...(releaseId === undefined ? {} : { releaseId }) }, tabDataDir, tabIncarnation, tabStandby };
94
94
  }
@@ -2,7 +2,7 @@ import { runTerminalHostMaintenance, writeDoctorBundle } from "../foundation/res
2
2
  import type { TabsRequest } from "./tabs-request.js";
3
3
  export type { TabsRequest } from "./tabs-request.js";
4
4
  export interface TabsCommandOptions {
5
- /** The running package root; its terminal host is a candidate after the active release. */
5
+ /** The invoking package, never a different active or retained release. */
6
6
  readonly packageRoot?: string;
7
7
  readonly environment?: NodeJS.ProcessEnv;
8
8
  readonly stdout?: (message: string) => void;
package/dist/cli/tabs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { resolveProductPaths } from "../foundation/lifecycle/index.js";
2
- import { CohortStateStore } from "../foundation/release/index.js";
2
+ import { materializeRelease } from "../foundation/release/index.js";
3
3
  import { readResidentTabLaunchSettings, runTerminalHostMaintenance, writeDoctorBundle, } from "../foundation/resident-tabs/index.js";
4
4
  import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../product-identity.js";
5
5
  /** `a1 tabs`: inspect or stop resident tabs without attaching, and never start a host to report state. */
@@ -9,7 +9,7 @@ export async function runTabsCommand(request, options = {}) {
9
9
  const stderr = options.stderr ?? (message => process.stderr.write(message));
10
10
  const paths = resolveProductPaths(environment);
11
11
  const profileRoot = environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir;
12
- const releaseRoots = await installedReleaseRoots(paths.dataDir, options.packageRoot);
12
+ const releaseRoots = await invokingReleaseRoots(paths.dataDir, options.packageRoot, environment);
13
13
  const maintenance = options.maintenance ?? runTerminalHostMaintenance;
14
14
  const report = await maintenance({
15
15
  action: request.action === "doctor" ? "host-status" : request.action,
@@ -51,22 +51,18 @@ export async function runTabsCommand(request, options = {}) {
51
51
  }
52
52
  return report.exitCode === 0 ? 0 : 1;
53
53
  }
54
- async function installedReleaseRoots(dataDir, packageRoot) {
55
- const roots = [];
54
+ async function invokingReleaseRoots(dataDir, packageRoot, environment) {
55
+ if (!packageRoot)
56
+ return [];
57
+ if (environment[PRODUCT_IDENTITY.environment.developmentInstanceId])
58
+ return [packageRoot];
56
59
  try {
57
- const state = await new CohortStateStore(dataDir).read();
58
- for (const id of [state.references.active, state.references.rollback]) {
59
- if (id && state.releases[id])
60
- roots.push(state.releases[id].releaseRoot);
61
- }
62
- roots.push(...Object.values(state.releases).map(record => record.releaseRoot));
60
+ // Invariant: immutable materialization selects exact payload bytes without activating or starting a host.
61
+ return [(await materializeRelease(packageRoot, dataDir)).releaseRoot];
63
62
  }
64
63
  catch {
65
- // Rationale: unreadable release state still leaves the running package's own host.
64
+ return [];
66
65
  }
67
- if (packageRoot)
68
- roots.push(packageRoot);
69
- return [...new Set(roots)];
70
66
  }
71
67
  const NO_TABS = "No resident tabs are running.\n";
72
68
  function describe(request, report) {
@@ -316,14 +316,29 @@ async function residentReleaseHolds(dataDir, reader) {
316
316
  const everything = () => [...new Set(records.map(record => record.releaseId))].map(releaseId => ({ authority: "resident", releaseId }));
317
317
  if (!report.complete)
318
318
  return everything();
319
- const byRoot = new Map(records.map(record => [comparablePath(record.releaseRoot), record.releaseId]));
320
- const store = comparablePath(resolve(dataDir, "releases"));
319
+ if (report.releaseRoots.length === 0)
320
+ return [];
321
+ // Rationale: native Windows extended paths and Unix aliases must match the catalog's spelling.
322
+ const byRoot = new Map();
323
+ for (const record of records) {
324
+ const canonical = await realpath(record.releaseRoot).catch(() => record.releaseRoot);
325
+ byRoot.set(comparablePath(canonical), record.releaseId);
326
+ }
327
+ const directory = await realpath(resolve(dataDir, "releases")).catch(() => null);
328
+ if (directory === null)
329
+ return everything();
330
+ const store = comparablePath(directory);
321
331
  const holds = new Set();
322
332
  for (const root of report.releaseRoots) {
323
- const releaseId = byRoot.get(comparablePath(root));
333
+ // Security: an unresolved live root may alias managed content; uncertainty must retain it.
334
+ const canonical = await realpath(root).catch(() => null);
335
+ if (canonical === null)
336
+ return everything();
337
+ const key = comparablePath(canonical);
338
+ const releaseId = byRoot.get(key);
324
339
  if (releaseId)
325
340
  holds.add(releaseId);
326
- else if (comparablePath(root).startsWith(`${store}${sep}`))
341
+ else if (key.startsWith(`${store}${sep}`))
327
342
  return everything();
328
343
  }
329
344
  return [...holds].sort().map(releaseId => ({ authority: "resident", releaseId }));
@@ -5,6 +5,7 @@ export interface TabBridgeCredentials {
5
5
  readonly tabId: string;
6
6
  readonly token: string;
7
7
  readonly endpoint: string;
8
+ readonly releaseId?: string;
8
9
  }
9
10
  export interface TabAttachment {
10
11
  readonly clientId: string;
@@ -70,7 +70,7 @@ export class TabBridgeClient {
70
70
  this.#startWaiter = finish;
71
71
  transport.once("connect", () => {
72
72
  this.#send("hello", {
73
- role: "bridge", generation: 1, build: this.#options.build ?? "a1",
73
+ role: "bridge", generation: 1, build: this.#options.build ?? "a1", releaseId: this.#options.credentials.releaseId,
74
74
  features: ["bridge-status", "bridge-detach", "bridge-heartbeat", "bridge-restart", "bridge-visibility", "bridge-rename", "bridge-route", "bridge-stop", "bridge-shortcuts", "controller-boundary", "recovery-journal"],
75
75
  token: [...Buffer.from(this.#options.credentials.token, "base64")], tabId: this.#options.credentials.tabId,
76
76
  pid: this.#options.pid ?? process.pid, startIdentity: this.#options.startIdentity ?? `${process.pid}:node`,
@@ -208,6 +208,11 @@ export class TabBridgeClient {
208
208
  }
209
209
  #receive(message) {
210
210
  if (message.type === "welcome" && message.body["generation"] === 1) {
211
+ const id = this.#options.credentials.releaseId;
212
+ if (id !== undefined && message.body["releaseId"] !== id) {
213
+ this.#fail(false);
214
+ return;
215
+ }
211
216
  const advertised = message.body["features"];
212
217
  this.#features = new Set(Array.isArray(advertised) ? advertised.filter((feature) => typeof feature === "string") : []);
213
218
  this.#connected = true;
@@ -21,7 +21,7 @@ export interface MaintenanceOptions {
21
21
  readonly profileRoot: string;
22
22
  readonly dataDir: string;
23
23
  readonly runtimeDir: string;
24
- /** Installed release roots whose verified terminal host may run maintenance, in preference order. */
24
+ /** The first root is the invoking release; other roots never substitute for it. */
25
25
  readonly releaseRoots: readonly string[];
26
26
  readonly environment: NodeJS.ProcessEnv;
27
27
  readonly platform?: NodeJS.Platform;
@@ -33,11 +33,7 @@ export interface MaintenanceOptions {
33
33
  readonly stdout: string;
34
34
  }>;
35
35
  }
36
- /**
37
- * Runs one maintenance form through a verified terminal host. When the running host speaks
38
- * another protocol generation, it retries once with the addon from the release that host
39
- * recorded, after verifying that release's manifest; it never loads an unverified addon.
40
- */
36
+ /** Runs maintenance only through the invoking release; another cohort is never a retry target. */
41
37
  export declare function runTerminalHostMaintenance(options: MaintenanceOptions): Promise<MaintenanceReport>;
42
38
  /** The largest maintenance report accepted from the terminal host. */
43
39
  export declare const MAX_MAINTENANCE_REPORT_BYTES: number;
@@ -4,26 +4,14 @@ import { PRODUCT_IDENTITY } from "../../product-identity.js";
4
4
  import { runBoundedHostCommand } from "./bounded-host-command.js";
5
5
  import { resolveTerminalHostEntryPath, resolveTerminalHostPath } from "./ensure-terminal-host.js";
6
6
  import { verifyTerminalHostArtifact } from "./terminal-host-artifact.js";
7
- const INCOMPATIBLE_EXIT = 4;
8
- /**
9
- * Runs one maintenance form through a verified terminal host. When the running host speaks
10
- * another protocol generation, it retries once with the addon from the release that host
11
- * recorded, after verifying that release's manifest; it never loads an unverified addon.
12
- */
7
+ /** Runs maintenance only through the invoking release; another cohort is never a retry target. */
13
8
  export async function runTerminalHostMaintenance(options) {
14
9
  const platform = options.platform ?? process.platform;
15
10
  const architecture = options.architecture ?? process.arch;
16
- const command = await firstVerifiedHost(options, options.releaseRoots, platform, architecture);
11
+ const command = await firstVerifiedHost(options, options.releaseRoots.slice(0, 1), platform, architecture);
17
12
  if (command === undefined)
18
- return unavailable(options.action, "no verified terminal host is installed");
19
- const report = await invoke(options, command);
20
- if (report.exitCode !== INCOMPATIBLE_EXIT || typeof report.releaseRoot !== "string")
21
- return report;
22
- const cohort = await firstVerifiedHost({ ...options, environment: {} }, [report.releaseRoot], platform, architecture);
23
- if (cohort === undefined || cohort.entry === command.entry) {
24
- return { ...report, message: "the running tabs use another version whose terminal host cannot be verified" };
25
- }
26
- return await invoke({ ...options, environment: installedEnvironment(options.environment) }, cohort);
13
+ return unavailable(options.action, "the invoking release has no verified terminal host");
14
+ return await invoke(options, command);
27
15
  }
28
16
  function arguments_(options) {
29
17
  const command = options.action === "stop" ? ["stop", options.tabId ?? ""] : [options.action];
@@ -67,17 +55,11 @@ async function firstVerifiedHost(options, roots, platform, architecture) {
67
55
  return { executable: process.execPath, entry: resolveTerminalHostEntryPath(root) };
68
56
  }
69
57
  catch {
70
- // Rationale: an older retained release may predate the packaged host; try the next one.
58
+ // Invariant: a missing or altered invoking artifact fails closed.
71
59
  }
72
60
  }
73
61
  return undefined;
74
62
  }
75
- function installedEnvironment(environment) {
76
- const sanitized = { ...environment };
77
- delete sanitized[PRODUCT_IDENTITY.environment.developmentInstanceId];
78
- delete sanitized[PRODUCT_IDENTITY.environment.terminalHostPath];
79
- return sanitized;
80
- }
81
63
  function unavailable(action, message) {
82
64
  return { schema: "a1-tabs-maintenance-v1", command: action, host: "unavailable", message, exitCode: 3 };
83
65
  }
@@ -1,4 +1,4 @@
1
- import { readdir, stat } from "node:fs/promises";
1
+ import { readdir } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
3
  import { PRODUCT_IDENTITY } from "../../product-identity.js";
4
4
  import { runBoundedHostCommand } from "./bounded-host-command.js";
@@ -51,19 +51,8 @@ async function hasResidentRecords(dataDir) {
51
51
  catch (error) {
52
52
  return !(error instanceof Error && "code" in error && error.code === "ENOENT");
53
53
  }
54
- for (const profile of profiles) {
55
- for (const record of ["server.json", "registry.json"]) {
56
- try {
57
- await stat(resolve(dataDir, "tabs", profile, record));
58
- return true;
59
- }
60
- catch (error) {
61
- if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
62
- return true;
63
- }
64
- }
65
- }
66
- return false;
54
+ // Invariant: native inspection, not a following stat, judges unknown or linked namespace entries.
55
+ return profiles.length > 0;
67
56
  }
68
57
  function incomplete(reason) {
69
58
  return { complete: false, releaseRoots: [], uncertain: [reason] };
@@ -7,7 +7,7 @@
7
7
  "platform": "darwin",
8
8
  "architecture": "arm64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-10-26T04:56:17.941Z",
10
+ "builtAt": "2026-10-26T12:38:01.432Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
13
  "sha256": "a87eaaffcd986b624c8b044b756446cc1bdaf4334a1775b97820542fe34f9fb7",
@@ -9,11 +9,11 @@
9
9
  "architecture": "arm64",
10
10
  "target": "aarch64-apple-darwin",
11
11
  "capability": "supported",
12
- "builtAt": "2026-10-26T04:59:18.364Z",
12
+ "builtAt": "2026-10-26T12:40:53.110Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "54bdccc28f03d9e974efb691f99ec2645c528748b1ea9832c9c1e059e111ce92",
16
- "size": 5941872
15
+ "sha256": "c42ed0b065515794627cd0064a5da4de4c84396460315615356aab340e73eef0",
16
+ "size": 5962320
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,7 +7,7 @@
7
7
  "platform": "darwin",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-10-26T04:56:23.732Z",
10
+ "builtAt": "2026-10-26T12:38:07.232Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
13
  "sha256": "783a65f0de287a1c02cd5429dbe97bc8633f4cc8a7b92e3e961180b9bdcff814",
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-apple-darwin",
11
11
  "capability": "supported",
12
- "builtAt": "2026-10-26T05:01:25.953Z",
12
+ "builtAt": "2026-10-26T12:43:26.281Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "048fb157fd7f98770d7256ef66638ef5ed225ae42225af8f3095413f95d4957b",
16
- "size": 6476992
15
+ "sha256": "65f9a9556babcf6f361f643699142a854e564ef0c487d6709a48615a98dc8d75",
16
+ "size": 6497320
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,7 +7,7 @@
7
7
  "platform": "linux",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-10-26T04:56:11.560Z",
10
+ "builtAt": "2026-10-26T12:37:52.900Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
13
  "sha256": "118e5954e3864672bd8482ab7edf419cb27dcdf2533720316979b7f5cc5a53a5",
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-unknown-linux-gnu",
11
11
  "capability": "supported",
12
- "builtAt": "2026-10-26T04:59:27.220Z",
12
+ "builtAt": "2026-10-26T12:40:25.329Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "9e59caca78d8e966e1a1d468e47ebba59fb242e43b86e4ee8893a8dd20cb9ab8",
16
- "size": 7212552
15
+ "sha256": "234f73020b8f8cb31fd9a172f13c437255bb575a63466a3b369065e8866acead",
16
+ "size": 7236184
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,10 +7,10 @@
7
7
  "platform": "win32",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-10-26T04:57:24.731Z",
10
+ "builtAt": "2026-10-26T12:38:48.590Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
- "sha256": "a51e98cbd97f0abfcd99b00101133bfe45509f4a620f275a696985a16f4e2312",
13
+ "sha256": "4136caabb0a807ff5b7c10e4b2cb1dd5e326c72bd3d7c61429e970879ac46f77",
14
14
  "size": 402432
15
15
  },
16
16
  "provenance": {
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-pc-windows-msvc",
11
11
  "capability": "supported",
12
- "builtAt": "2026-10-26T05:00:51.094Z",
12
+ "builtAt": "2026-10-26T12:40:52.007Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "2be0eb9858bbfde8dd6dddb8bfc5881918f6c7804f74cd51cc97b56112b1adad",
16
- "size": 6294016
15
+ "sha256": "a48fa7c8bdb979c8ca2834b280b590174b1574b16fc263aa5ad12ba1361a44a5",
16
+ "size": 6316032
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -1,4 +1,4 @@
1
- declare const ENVIRONMENT_KEYS: readonly ["clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentHistoryDataDir", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath", "paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId"];
1
+ declare const ENVIRONMENT_KEYS: readonly ["clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentHistoryDataDir", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath", "paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabReleaseId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId"];
2
2
  declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
3
3
  declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile", "sessionRepositoryDirectory", "sessionWorktreeClaimDirectory"];
4
4
  declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
@@ -1,7 +1,7 @@
1
1
  import rawIdentity from "./product-identity.json" with { type: "json" };
2
2
  const ENVIRONMENT_KEYS = [
3
3
  "clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentHistoryDataDir", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath",
4
- "paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId",
4
+ "paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabReleaseId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId",
5
5
  ];
6
6
  const FILESYSTEM_KEYS = ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
7
7
  const STATE_KEYS = ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile", "sessionRepositoryDirectory", "sessionWorktreeClaimDirectory"];
@@ -36,6 +36,7 @@
36
36
  "suggestionDiagnostics": "A1_SUGGESTION_DIAGNOSTICS",
37
37
  "tabEndpoint": "A1_TAB_ENDPOINT",
38
38
  "tabId": "A1_TAB_ID",
39
+ "tabReleaseId": "A1_TAB_RELEASE_ID",
39
40
  "tabToken": "A1_TAB_TOKEN",
40
41
  "tabsEndpoint": "A1_TABS_ENDPOINT",
41
42
  "terminalHostPath": "A1_TERMINAL_HOST_PATH",
@@ -175,13 +175,13 @@ Transport correctness does not rely on `timeBeginPeriod`. The existing per-role
175
175
 
176
176
  The existing opt-in progress route (`A1_RESIDENT_PROGRESS_OBSERVATION_DIR` plus `A1_RESIDENT_PROGRESS_RUN_ID`) adds `pipe-frame-read`, `pipe-write-complete`, `outer-write-complete`, `wake-gap`, and `wake-repaint-requested` stages. Connection-level records may omit tab identity. `durationUs` is local monotonic completion/assembly time; `idleWaitUs` separately describes waiting for the next frame and is not delivery latency. Native records include `monotonicUs`, `clockEpoch`, and optional `clockDiscontinuity`. Consumers exclude negative, epoch-crossing, and clock-discontinuity-crossing derived samples, while retaining valid local completion durations. The queue remains bounded and asynchronous, with loss accounting and no terminal content or credentials. Outer-write completion is not proof of physical pixels.
177
177
 
178
- For recurring post-wake stalls, compare an existing resident client, a new client on the same resident host, and direct `a1 solo` in the same outer terminal. Use matched holder/attach revisions and local I/O durations to distinguish child progress, transport delay, and outer writes. If solo and other terminal applications also stall, do not assign the failure to resident pipes without further evidence. Simulated-gap fixtures do not certify physical sleep/wake recovery; do not automate workstation suspend. Updates retain old resident cohorts, so new endpoint code takes effect only as those roles reach their ordinary lifecycle or the maintainer explicitly chooses to stop them. Installing the update never recycles running tabs.
178
+ For recurring post-wake stalls, compare an existing resident client, a new client on the same resident host, and direct `a1 solo` in the same outer terminal. Use matched holder/attach revisions and local I/O durations to distinguish child progress, transport delay, and outer writes. If solo and other terminal applications also stall, do not assign the failure to resident pipes without further evidence. Simulated-gap fixtures do not certify physical sleep/wake recovery; do not automate workstation suspend. Updates retain old resident cohorts, but a new launch selects its own immutable release namespace rather than reusing their host. Existing roles keep their original code and lifecycle; installing an update never recycles running tabs.
179
179
 
180
180
  ### 3. Host scope, endpoints, writer authority, and credentials
181
181
 
182
- - There is **one server per OS user × canonical A1 profile root**, independent of the release cohort. Tabs must outlive release changes, and the supervisor is forbidden from retaining runtime processes.
183
- - Windows endpoint: `\\.\pipe\a1-tabs-<sha256(canonicalProfileHome)[0:20]>`.
184
- - macOS and Linux endpoint: a Unix domain socket `a1-tabs-<same profile hash>.sock` inside an owner-only (`0700`) per-user runtime directory (`$XDG_RUNTIME_DIR` when set, otherwise a private directory under the profile's runtime dir), kept short enough for the platform's socket path limit.
182
+ - There is **one server per OS user, canonical A1 profile root and immutable release identity**. Different releases coexist without migration; the supervisor remains forbidden from retaining runtime processes. A domain-separated hash of canonical profile and fixed originating release/addon identity selects the flat `<data>/tabs/<scope>` directory, credential files, registry, epochs and `server-start-<scope>.lock`.
183
+ - Windows endpoint: `\\.\pipe\a1-tabs-<scope[0:20]>`.
184
+ - macOS and Linux endpoint: a Unix domain socket `a1-tabs-<same scope>.sock` inside an owner-only (`0700`) per-user runtime directory (`$XDG_RUNTIME_DIR` when set, otherwise a private directory under the profile's runtime dir), kept short enough for the platform's socket path limit.
185
185
  - Generation 1 admits holders over the authenticated owner endpoint. Dedicated `…-h-<tabId>` endpoints remain an optional later optimization.
186
186
  - Hermetic override: `A1_TABS_ENDPOINT`, together with the `A1_*_DIR` overrides.
187
187
  - **The endpoint is discovery; the writer lease is authority.** A starting server probes with a real handshake. If a live server answers, it exits as `already-running` and joins the winner. A failed probe does not authorize blind endpoint reclaim: the starter reads the owner marker, verifies pid and native start identity, requests termination, and waits for the owner-only OS-exclusive registry-writer lease. If the owner cannot be verified or the lease cannot be acquired, startup fails closed instead of creating split brain.
@@ -189,7 +189,7 @@ For recurring post-wake stalls, compare an existing resident client, a new clien
189
189
  - **Access and authentication:**
190
190
  - The endpoint, token files, secret file, registry, and writer lease are owner-only: Windows ACLs on the named pipe and files, `0700` directories and `0600` files on macOS and Linux, plus a peer-credential check (`SO_PEERCRED` / `getpeereid`) on every Unix socket connection.
191
191
  - Clients present a random 32-byte client token from an owner-only file.
192
- - One random owner-only profile secret persists outside the registry. Per-tab/per-incarnation holder and bridge credentials are derived from that secret and the non-secret tab/incarnation identity, so a replacement server can authenticate surviving processes without storing tab credentials.
192
+ - One random owner-only profile/release secret persists outside its registry. Per-tab/per-incarnation holder and bridge credentials are derived from that secret and the non-secret tab/incarnation identity, so a replacement server can authenticate surviving processes without storing tab credentials.
193
193
  - Holders and bridges also present pid and native start identity. Nothing unverifiable is adopted, signalled, terminated, or allowed to mutate state.
194
194
 
195
195
  ### 4. Detached start
@@ -326,10 +326,10 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
326
326
  ### 10. Updates and version skew
327
327
 
328
328
  - **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.
329
- - **Same generation, newer client.** The client attaches normally, and features the server lacks are disabled per method.
330
- - **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.
331
- - **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.
332
- - **Incompatibility.** A generation mismatch never kills resident processes. The client reports that the resident host must be stopped explicitly before the preview can restart on the newer generation. Automatic server handoff and idle tab recycling require a follow-up change.
329
+ - **Exact release admission.** Clients, holders and bridges present a release identity before capability negotiation. Matching protocol generation or display version does not permit cross-release attachment. Native clients also check the welcome identity; bridge identity is consumed from holder-private launch context before extensions load.
330
+ - **Independent activation.** A newer release starts or reuses its own host. Existing resident and solo processes remain untouched. Server recovery and child launches retain the originating fixed Node, entry and addon; the active-release pointer never retargets them. No automatic migration, handoff or idle recycling is performed.
331
+ - **Retention.** Read-only bounded collection scans every flat release namespace and legacy profile namespace. It keeps server, holder and child releases while identities verify and retains all candidates on unreadable, linked, oversized or unverifiable state. Package activation never overwrites loaded artifacts.
332
+ - **Maintenance and legacy state.** Maintenance resolves the invoking package's immutable materialization without activation or host startup, uses only that release's verified entry, and never retries another recorded cohort. List, stop, status and doctor remain release-local, including unresponsive-host termination. Legacy unversioned state is neither migrated nor adopted; it is read only for conservative retention. Shared configuration and session file locks remain profile-neutral.
333
333
 
334
334
  ### 11. Foreground UX
335
335
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.7-dev.834",
3
+ "version": "0.2.7-dev.835",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",