@timurproko/a1 0.1.8-dev.560 → 0.1.8-dev.563

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.
package/bin/guardian.js CHANGED
@@ -26,6 +26,11 @@ Promise.resolve().then(() => runLaunchGuardian({
26
26
  })).then(
27
27
  code => { process.exitCode = code; },
28
28
  error => {
29
+ if (error instanceof Error && "code" in error && error.code === "release-superseded" && typeof process.send === "function") {
30
+ process.send({ type: "a1-release-reselection" });
31
+ process.exitCode = 1;
32
+ return;
33
+ }
29
34
  console.error(error instanceof Error ? error.message : String(error));
30
35
  process.exitCode = 1;
31
36
  },
@@ -687,11 +687,13 @@ export class OwnedUiSessionShellRoot {
687
687
  .flatMap(diagnostic => renderPiShellPackageUpdateNotice(diagnostic.message.split("\n").filter(line => line.startsWith("- ")).map(line => line.slice(2)), width)));
688
688
  rows.push(...diagnostics
689
689
  .filter(diagnostic => diagnostic.code === "changelog-collapsed" || diagnostic.code === "changelog-expanded")
690
- // Rationale: the custom viewport shows the new entries on the What's New screen, so its feed
691
- // carries only the hint for either diagnostic; the pinned layout keeps the full document.
692
- .flatMap(diagnostic => diagnostic.code === "changelog-collapsed" || this.#customViewport
693
- ? createPiShellCollapsedChangelog().render(width)
694
- : createPiShellChangelog(diagnostic.message).render(width)));
690
+ // Rationale: bare A1 translates either startup diagnostic into transient dock chrome;
691
+ // the pinned layout retains Pi's collapsed hint or expanded document.
692
+ .flatMap(diagnostic => this.#customViewport
693
+ ? []
694
+ : diagnostic.code === "changelog-collapsed"
695
+ ? createPiShellCollapsedChangelog().render(width)
696
+ : createPiShellChangelog(diagnostic.message).render(width)));
695
697
  rows.push(...diagnostics
696
698
  .filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "package-updates"
697
699
  && diagnostic.code !== "changelog-collapsed" && diagnostic.code !== "changelog-expanded")
@@ -16,7 +16,6 @@ import { SKILLS_COMMAND_NAME, findSkillByArgument, rewriteSkillsTunnelSubmission
16
16
  import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
17
17
  import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellSkillsSelector, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
18
18
  import { createPiShellChangelog, createPiShellHotkeys, createPiShellSessionInfo, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
19
- import { CHANGELOG_ROUTE } from "../../features/owned-ui/reference-routes.js";
20
19
  import { createPiShellTranscriptComponent, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellTranscriptBlock, } from "../../integrations/pi/components/shell-presenters-transcript.js";
21
20
  import { onPiThemeChange, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
22
21
  import { DamageAwareTerminalAdapter } from "../../integrations/pi/tui-runtime/damage-aware-terminal.js";
@@ -56,7 +55,7 @@ export class OwnedUiSessionShell {
56
55
  #shutdownPromise;
57
56
  #disposePromise;
58
57
  #pointerReporting = false;
59
- // Invariant: the startup release notes open at most once per shell, whatever the view does later.
58
+ // Invariant: the startup release-note diagnostic creates at most one dock notice per shell.
60
59
  #startupChangelogHandled = false;
61
60
  #customViewport;
62
61
  #responseCopy;
@@ -1689,22 +1688,17 @@ export class OwnedUiSessionShell {
1689
1688
  listener(view);
1690
1689
  return view;
1691
1690
  }
1692
- // Rationale: the engine has already stored the current version when the diagnostic arrives, so the
1693
- // entries newer than the last acknowledged one exist only in its message; the screen shows exactly those.
1691
+ // Rationale: release details stay one explicit command away; startup needs only the same quiet,
1692
+ // non-transcript acknowledgement that bare A1 uses for model and thinking changes.
1694
1693
  #presentStartupChangelog(view) {
1695
- if (this.#startupChangelogHandled || !this.#customViewport || this.#routeHost === null)
1694
+ if (this.#startupChangelogHandled || !this.#customViewport)
1696
1695
  return;
1697
- const diagnostic = view.diagnostics.find(candidate => candidate.code === "changelog-expanded");
1696
+ const diagnostic = view.diagnostics.find(candidate => candidate.code === "changelog-collapsed" || candidate.code === "changelog-expanded");
1698
1697
  // Invariant: a view synchronized before the runtime starts is not the moment of arrival; start() replays it.
1699
1698
  if (diagnostic === undefined || !this.runtime.active)
1700
1699
  return;
1701
1700
  this.#startupChangelogHandled = true;
1702
- // Invariant: a dialog, selector, or owned screen already up keeps the screen; the hint stays in the feed.
1703
- if (this.#dialogHandle !== undefined || !this.root.usesDefaultInputSurface())
1704
- return;
1705
- if (!this.#routeHost.claims(CHANGELOG_ROUTE))
1706
- return;
1707
- this.#openOwnedRoute(CHANGELOG_ROUTE, { document: diagnostic.message });
1701
+ this.root.appendWorkflowStatus(STARTUP_CHANGELOG_NOTICE);
1708
1702
  }
1709
1703
  #openOwnedRoute(route, input) {
1710
1704
  const surface = this.#routeHost?.open(route, input) ?? null;
@@ -2117,6 +2111,7 @@ function workflowAdapterResult(result) {
2117
2111
  const INTERRUPT = "\u0003";
2118
2112
  const INTERRUPT_CHORD_MS = 1_500;
2119
2113
  const RELOAD_SURFACE_MIN_VISIBLE_MS = 400;
2114
+ const STARTUP_CHANGELOG_NOTICE = "Run /changelog to view the full release notes.";
2120
2115
  // Rationale: the outro is not configurable; the switch only decides whether this plan plays.
2121
2116
  const QUIT_OUTRO_EFFECT = "fall";
2122
2117
  const QUIT_OUTRO_DURATION_MS = 800;
@@ -2,10 +2,9 @@ import { randomUUID } from "node:crypto";
2
2
  import { PRODUCT_TEXT } from "../../product-identity.js";
3
3
  import { readLaunchContext } from "../launch-context/index.js";
4
4
  import { resolve } from "node:path";
5
- import { assertLaunchProfileId, sessionSelectionArguments } from "../lifecycle/index.js";
5
+ import { assertLaunchProfileId, resolveCohortEndpoint, resolveProductPaths, sessionSelectionArguments } from "../lifecycle/index.js";
6
6
  import { closeVerifiedContainment, DarwinNativeProcessInspector, LinuxNativeProcessInspector, NativeGuardianContainment, resolveProcessGuardianPath, verifyProcessGuardianArtifact, WindowsNativeProcessInspector, } from "../process-containment/index.js";
7
7
  import { SupervisorClient } from "../protocol/index.js";
8
- import { resolveCohortEndpoint, resolveProductPaths } from "../supervision/index.js";
9
8
  import { markStartupPhase } from "../startup/index.js";
10
9
  export async function runLaunchGuardian(options) {
11
10
  assertLaunchProfileId(options.profileId);
@@ -54,7 +54,7 @@ export interface CommandResult {
54
54
  readonly ok: boolean;
55
55
  readonly revision: number;
56
56
  readonly error?: {
57
- readonly code: "invalid-command" | "not-found" | "stale-generation" | "capability-error" | "containment-unsupported" | "ownership-error" | "driver-error";
57
+ readonly code: "invalid-command" | "not-found" | "stale-generation" | "capability-error" | "containment-unsupported" | "ownership-error" | "release-superseded" | "driver-error";
58
58
  readonly message: string;
59
59
  };
60
60
  }
@@ -5,6 +5,7 @@ import { connect } from "node:net";
5
5
  import { platform } from "node:os";
6
6
  import { resolve } from "node:path";
7
7
  import { selectCohortLaunch } from "./cohort-selection.js";
8
+ import { selectOrdinaryLaunchReleaseId } from "./ordinary-launch-selection.js";
8
9
  import { CohortStateStore } from "./cohort-state.js";
9
10
  import { assertLaunchProfileId, createSupervisorStartupAttempt, readSupervisorStartupResult, resolveCohortEndpoint, resolveProductPaths, sessionSelectionArguments } from "../lifecycle/index.js";
10
11
  import { encodeFrame, LineFrameDecoder } from "../protocol/index.js";
@@ -18,7 +19,11 @@ import { createRestartSeal, readRestartCertifiedRelease, releaseCertificationDoc
18
19
  import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
19
20
  import { markStartupPhase } from "../startup/index.js";
20
21
  import { launchContractTarget, readLaunchContext, withLaunchContext } from "../launch-context/index.js";
22
+ const RELEASE_RESELECTION_MESSAGE = "a1-release-reselection";
21
23
  export async function runBootstrap(options) {
24
+ return await runBootstrapAttempt(options, 1);
25
+ }
26
+ async function runBootstrapAttempt(options, retriesRemaining) {
22
27
  const environment = withLaunchContext(options.environment ?? process.env, { launchProfile: options.launchIntent?.profileId ?? "a1" });
23
28
  await markStartupPhase(environment, "bootstrap-start");
24
29
  const launchProfileId = options.launchIntent?.profileId ?? "a1";
@@ -29,6 +34,15 @@ export async function runBootstrap(options) {
29
34
  const output = options.output ?? process.stderr;
30
35
  const paths = resolveProductPaths(environment);
31
36
  await mkdir(paths.runtimeDir, { recursive: true, mode: 0o700 });
37
+ const launchSelected = async (release) => {
38
+ const result = await launchUi(release, environment, sessionArgs);
39
+ if (!result.releaseSuperseded)
40
+ return result.code;
41
+ if (retriesRemaining > 0)
42
+ return await runBootstrapAttempt(options, retriesRemaining - 1);
43
+ output.write(`${PRODUCT_TEXT.diagnostic("active release changed repeatedly during startup; launch could not converge")}\n`);
44
+ return 1;
45
+ };
32
46
  const stateStore = new CohortStateStore(paths.dataDir);
33
47
  let state = await stateStore.read();
34
48
  const launchDuringUpdate = async () => {
@@ -41,7 +55,7 @@ export async function runBootstrap(options) {
41
55
  return null;
42
56
  const retained = await readCertifiedReleaseManifest(prior, resolve(paths.dataDir, "releases"));
43
57
  await ensureSupervisor(retained, environment);
44
- return await launchUi(retained, environment, sessionArgs);
58
+ return await launchSelected(retained);
45
59
  };
46
60
  const duringUpdate = await launchDuringUpdate();
47
61
  if (duringUpdate !== null)
@@ -82,7 +96,7 @@ export async function runBootstrap(options) {
82
96
  && endpoint.contentDigest === active.contentDigest;
83
97
  if (endpointMatches && probe === "live-verified") {
84
98
  const retained = await readCertifiedReleaseManifest(active, resolve(paths.dataDir, "releases"));
85
- return await launchUi(retained, environment, sessionArgs);
99
+ return await launchSelected(retained);
86
100
  }
87
101
  if (endpoint === null || probe === "dead") {
88
102
  if (endpoint)
@@ -100,7 +114,7 @@ export async function runBootstrap(options) {
100
114
  await markStartupPhase(environment, "replacement-supervisor-start");
101
115
  await ensureSupervisor(retained, environment);
102
116
  await markStartupPhase(environment, "replacement-supervisor-ready");
103
- return await launchUi(retained, environment, sessionArgs);
117
+ return await launchSelected(retained);
104
118
  }
105
119
  }
106
120
  // Concurrency: an installation being replaced is not a reason to refuse a launch. While
@@ -112,7 +126,7 @@ export async function runBootstrap(options) {
112
126
  candidate = await materializeRelease(options.packageRoot, paths.dataDir);
113
127
  }
114
128
  catch (error) {
115
- const fallback = await launchRetainedActive(state, paths, environment, sessionArgs, output);
129
+ const fallback = await launchRetainedActive(state, paths, environment, launchSelected);
116
130
  if (fallback === null)
117
131
  throw error;
118
132
  return fallback;
@@ -121,24 +135,32 @@ export async function runBootstrap(options) {
121
135
  const afterMaterialization = await launchDuringUpdate();
122
136
  if (afterMaterialization !== null)
123
137
  return afterMaterialization;
138
+ state = await stateStore.read();
139
+ const ordinarySelection = !activeIsLaunchable
140
+ ? candidate.releaseId
141
+ : selectOrdinaryLaunchReleaseId(candidate, state.references.active === null ? undefined : state.releases[state.references.active]);
142
+ if (ordinarySelection !== candidate.releaseId) {
143
+ const retainedRecord = state.releases[ordinarySelection];
144
+ if (!retainedRecord || retainedRecord.approval !== "approved")
145
+ throw new Error("selected active release is not approved");
146
+ const retained = await readCertifiedReleaseManifest(retainedRecord, resolve(paths.dataDir, "releases"));
147
+ await ensureSupervisor(retained, environment);
148
+ return await launchSelected(retained);
149
+ }
124
150
  await stateStore.recordCandidate(candidate);
125
151
  state = await stateStore.read();
126
- if (!state.references.active) {
152
+ if (state.releases[candidate.releaseId]?.approval !== "approved") {
127
153
  const diagnosticsPath = await certifyMaterializedRelease(candidate, paths.dataDir);
128
154
  await stateStore.approve(candidate.releaseId, diagnosticsPath);
129
- await stateStore.activate(candidate.releaseId);
130
- state = await stateStore.read();
131
155
  }
132
- else if (!activeIsLaunchable && candidate.releaseId !== activeId
133
- && state.releases[candidate.releaseId]?.approval !== "approved") {
134
- // Invariant: the active reference points at a copy that cannot launch, so reusing it
135
- // is off the table — but an unapproved candidate would lose the selection
136
- // below to that same broken active (`start-active`). Approving the healed
137
- // candidate here lets ordinary cohort selection activate it, while a live
138
- // busy cohort still wins the endpoint checks and keeps its sessions.
139
- const diagnosticsPath = await certifyMaterializedRelease(candidate, paths.dataDir);
140
- await stateStore.approve(candidate.releaseId, diagnosticsPath);
141
- state = await stateStore.read();
156
+ state = await stateStore.activateForLaunch(candidate.releaseId, !activeIsLaunchable ? activeId ?? undefined : undefined);
157
+ if (state.references.active !== candidate.releaseId) {
158
+ const retainedRecord = state.references.active === null ? undefined : state.releases[state.references.active];
159
+ if (!retainedRecord || retainedRecord.approval !== "approved")
160
+ throw new Error("ordinary launch has no approved active release");
161
+ const retained = await readCertifiedReleaseManifest(retainedRecord, resolve(paths.dataDir, "releases"));
162
+ await ensureSupervisor(retained, environment);
163
+ return await launchSelected(retained);
142
164
  }
143
165
  // Protocol: the candidate's own endpoint decides whether this launch attaches or starts a supervisor.
144
166
  // A cohort other than this one is not in the way: it listens somewhere else.
@@ -146,7 +168,7 @@ export async function runBootstrap(options) {
146
168
  endpoint = await readEndpointMetadata(endpointPaths.endpointMetadataPath);
147
169
  if (endpoint === null) {
148
170
  const legacy = await readEndpointMetadata(paths.endpointMetadataPath);
149
- if (legacy !== null) {
171
+ if (legacy?.releaseId === candidate.releaseId) {
150
172
  endpoint = legacy;
151
173
  endpointPaths = { endpoint: paths.endpoint, endpointMetadataPath: paths.endpointMetadataPath };
152
174
  }
@@ -170,11 +192,14 @@ export async function runBootstrap(options) {
170
192
  await removeEndpointArtifacts(endpointPaths.endpointMetadataPath, endpointPaths.endpoint);
171
193
  }
172
194
  if (decision.action === "launch-retained-ui") {
173
- const retained = await verifyMaterializedRelease(decision.releaseRoot, undefined, resolve(paths.dataDir, "releases"));
195
+ const retainedRecord = state.releases[decision.releaseId];
196
+ const retained = retainedRecord?.approval === "approved"
197
+ ? await readCertifiedReleaseManifest(retainedRecord, resolve(paths.dataDir, "releases"))
198
+ : await verifyMaterializedRelease(decision.releaseRoot, undefined, resolve(paths.dataDir, "releases"));
174
199
  if (decision.recordPending && endpoint) {
175
200
  await stateStore.blockPending("candidate activation deferred by live non-resumable instances", endpoint.ownership.nonResumableInstanceIds);
176
201
  }
177
- const code = await launchUi(retained, environment, sessionArgs);
202
+ const code = await launchSelected(retained);
178
203
  if (decision.recordPending)
179
204
  await activatePendingAfterBlockerExit(candidate, stateStore, paths, environment);
180
205
  return code;
@@ -204,15 +229,23 @@ export async function runBootstrap(options) {
204
229
  const diagnosticsPath = await certifyMaterializedRelease(candidate, paths.dataDir);
205
230
  await stateStore.approve(candidate.releaseId, diagnosticsPath);
206
231
  }
207
- if ((await stateStore.read()).references.active !== candidate.releaseId)
208
- await stateStore.activate(candidate.releaseId);
209
- selected = candidate;
232
+ const activated = (await stateStore.read()).references.active === candidate.releaseId
233
+ ? await stateStore.read()
234
+ : await stateStore.activateForLaunch(candidate.releaseId);
235
+ if (activated.references.active === candidate.releaseId)
236
+ selected = candidate;
237
+ else {
238
+ const retainedRecord = activated.references.active === null ? undefined : activated.releases[activated.references.active];
239
+ if (!retainedRecord || retainedRecord.approval !== "approved")
240
+ throw new Error("ordinary launch has no approved active release");
241
+ selected = await readCertifiedReleaseManifest(retainedRecord, resolve(paths.dataDir, "releases"));
242
+ }
210
243
  }
211
244
  else {
212
245
  selected = await readMaterializedRelease(decision.releaseRoot);
213
246
  }
214
247
  await ensureSupervisor(selected, environment);
215
- return await launchUi(selected, environment, sessionArgs);
248
+ return await launchSelected(selected);
216
249
  }
217
250
  /**
218
251
  * Start the retained active cohort when the installed payload cannot be taken. The
@@ -220,7 +253,7 @@ export async function runBootstrap(options) {
220
253
  * certifying anything new, and reports no selection when there is nothing safe
221
254
  * to fall back to.
222
255
  */
223
- async function launchRetainedActive(state, paths, environment, sessionArgs, output) {
256
+ async function launchRetainedActive(state, paths, environment, launch) {
224
257
  const activeId = state.references.active;
225
258
  const active = activeId === null ? undefined : state.releases[activeId];
226
259
  if (!active || active.approval !== "approved")
@@ -228,8 +261,7 @@ async function launchRetainedActive(state, paths, environment, sessionArgs, outp
228
261
  try {
229
262
  const retained = await readCertifiedReleaseManifest(active, resolve(paths.dataDir, "releases"));
230
263
  await ensureSupervisor(retained, environment);
231
- output.write(`${PRODUCT_TEXT.diagnostic(`installation is being replaced; starting the retained release ${retained.packageVersion}`)}\n`);
232
- return await launchUi(retained, environment, sessionArgs);
264
+ return await launch(retained);
233
265
  }
234
266
  catch {
235
267
  return null;
@@ -300,13 +332,24 @@ async function launchUi(release, environment, sessionArgs) {
300
332
  await markStartupPhase(environment, "bootstrap-selected");
301
333
  const entry = await resolveReleaseEntryPoint(release, "bin/guardian.js");
302
334
  return await new Promise((resolvePromise, rejectPromise) => {
335
+ let releaseSuperseded = false;
303
336
  const child = spawn(process.execPath, [entry, ...sessionArgs], {
304
337
  env: releaseEnvironment(environment, release, readLaunchContext(environment, "profile").launchProfile),
305
- stdio: "inherit",
338
+ stdio: ["inherit", "inherit", "inherit", "ipc"],
306
339
  windowsHide: false,
307
340
  });
341
+ child.on("message", message => {
342
+ if (message && typeof message === "object" && "type" in message && message.type === RELEASE_RESELECTION_MESSAGE) {
343
+ releaseSuperseded = true;
344
+ }
345
+ });
308
346
  child.once("error", rejectPromise);
309
- child.once("close", (code, signal) => resolvePromise(restoreAfterOwnedExit(readLaunchContext(environment).launchProfile === "a1", code, signal)));
347
+ child.once("close", (code, signal) => resolvePromise({
348
+ code: releaseSuperseded
349
+ ? code ?? 1
350
+ : restoreAfterOwnedExit(readLaunchContext(environment).launchProfile === "a1", code, signal),
351
+ releaseSuperseded,
352
+ }));
310
353
  });
311
354
  }
312
355
  /**
@@ -140,6 +140,8 @@ export declare class CohortStateStore {
140
140
  recordCandidate(release: MaterializedRelease): Promise<CohortState>;
141
141
  approve(releaseId: string, diagnosticsPath: string): Promise<CohortState>;
142
142
  activate(releaseId: string): Promise<CohortState>;
143
+ /** Atomically advances ordinary launch without allowing an older installation to roll active state back. */
144
+ activateForLaunch(releaseId: string, replaceUnlaunchableActiveId?: string): Promise<CohortState>;
143
145
  blockPending(reason: string, blockerGenerationIds: readonly string[]): Promise<CohortState>;
144
146
  rollback(ownershipReleased: boolean): Promise<CohortState>;
145
147
  setRetention(releaseIds: readonly string[]): Promise<CohortState>;
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
3
3
  import { dirname, resolve } from "node:path";
4
+ import { selectOrdinaryLaunchReleaseId } from "./ordinary-launch-selection.js";
4
5
  import { PRODUCT_IDENTITY } from "../../product-identity.js";
5
6
  export const RELEASE_COHORT_SCHEMA = PRODUCT_IDENTITY.protocol.releaseCohortSchema;
6
7
  const RELEASE_ID_PATTERN = /^[0-9A-Za-z.+_-]+-[a-f0-9]{20}$/;
@@ -135,6 +136,40 @@ export class CohortStateStore {
135
136
  };
136
137
  });
137
138
  }
139
+ /** Atomically advances ordinary launch without allowing an older installation to roll active state back. */
140
+ async activateForLaunch(releaseId, replaceUnlaunchableActiveId) {
141
+ return await this.update(current => {
142
+ const release = requiredRelease(current, releaseId);
143
+ if (release.approval !== "approved")
144
+ throw new Error(`cannot activate unverified release ${releaseId}`);
145
+ const selectedId = current.references.active === replaceUnlaunchableActiveId
146
+ ? releaseId
147
+ : selectOrdinaryLaunchReleaseId(release, current.references.active === null ? undefined : current.releases[current.references.active]);
148
+ if (selectedId !== releaseId) {
149
+ return {
150
+ ...current,
151
+ references: {
152
+ ...current.references,
153
+ pending: current.references.pending === releaseId ? null : current.references.pending,
154
+ approved: selectedId,
155
+ },
156
+ activation: activation("idle", null),
157
+ };
158
+ }
159
+ const prior = current.references.active;
160
+ return {
161
+ ...current,
162
+ references: {
163
+ ...current.references,
164
+ active: releaseId,
165
+ pending: null,
166
+ approved: releaseId,
167
+ rollback: prior && prior !== releaseId ? prior : current.references.rollback,
168
+ },
169
+ activation: activation("idle", null),
170
+ };
171
+ });
172
+ }
138
173
  async blockPending(reason, blockerGenerationIds) {
139
174
  return await this.update(current => ({ ...current, activation: { ...activation("blocked", reason), blockerGenerationIds: [...blockerGenerationIds] } }));
140
175
  }
@@ -1,6 +1,7 @@
1
1
  export { certifyMaterializedRelease, ensureSupervisor, recordParentCertifiedRelease, releaseEnvironment, releaseVerifiedIdleOwner, runBootstrap, startSupervisor, waitForProcessExit, waitForVerifiedEndpoint, } from "./bootstrap.js";
2
2
  export type { BootstrapOptions, SupervisorStartupAttempt } from "./bootstrap.js";
3
3
  export { selectCohortLaunch } from "./cohort-selection.js";
4
+ export { selectOrdinaryLaunchReleaseId } from "./ordinary-launch-selection.js";
4
5
  export type { CohortLaunchDecision, OwnershipProbe } from "./cohort-selection.js";
5
6
  export { CohortStateStore, RELEASE_COHORT_SCHEMA, emptyState, planProtectedReleases } from "./cohort-state.js";
6
7
  export type { ActiveReleaseTransactionReference, CohortState, EndpointOwnership, ExternalReleaseHold, ExternalReleaseHoldAuthority, OrphanReleaseDisposition, ProtectedReleaseInputs, ProtectedReleasePlan, ReleaseApproval, ReleaseCleanupDiagnostic, ReleaseCleanupDisposition, ReleaseCleanupStage, ReleaseCleanupState, ReleaseCleanupWorkerRun, ReleaseCleanupWorkerStatus, ReleaseCleanupWorkerSummary, ReleaseRecord, ReleaseReferences, ReleaseRetentionReconciliation, SupervisorEndpointMetadata, } from "./cohort-state.js";
@@ -1,5 +1,6 @@
1
1
  export { certifyMaterializedRelease, ensureSupervisor, recordParentCertifiedRelease, releaseEnvironment, releaseVerifiedIdleOwner, runBootstrap, startSupervisor, waitForProcessExit, waitForVerifiedEndpoint, } from "./bootstrap.js";
2
2
  export { selectCohortLaunch } from "./cohort-selection.js";
3
+ export { selectOrdinaryLaunchReleaseId } from "./ordinary-launch-selection.js";
3
4
  export { CohortStateStore, RELEASE_COHORT_SCHEMA, emptyState, planProtectedReleases } from "./cohort-state.js";
4
5
  export { DEPENDENCY_LAYER_MANIFEST, DEPENDENCY_LAYER_SCHEMA, RUNTIME_PAYLOAD_INVENTORY, dependencyLayerCertificationPath, dependencyReference, generateDependencyRuntimePayload, legacyDependencyLayerCertificationPath, materializeDependencyLayer, readCertifiedDependencyLayer, selectDependencyRuntimePayload, selectPublishedDependencyRuntimePayload, verifyDependencyLayer, } from "./dependency-layer.js";
5
6
  export { listRecordedEndpoints, liveReleaseIds, probeOwnership, readEndpointMetadata, removeEndpointArtifacts, sweepDeadEndpoints } from "./endpoints.js";
@@ -0,0 +1,14 @@
1
+ interface OrdinaryLaunchRelease {
2
+ readonly releaseId: string;
3
+ readonly packageVersion: string;
4
+ }
5
+ interface ApprovedOrdinaryLaunchRelease extends OrdinaryLaunchRelease {
6
+ readonly approval: string;
7
+ }
8
+ /**
9
+ * Ordinary launch has no rollback authority: it may advance to its installed package, but an
10
+ * older package installation must follow a newer approved active release instead of moving the
11
+ * shared selector backward. Explicit update/rollback uses CohortStateStore.activate directly.
12
+ */
13
+ export declare function selectOrdinaryLaunchReleaseId(candidate: OrdinaryLaunchRelease, active?: ApprovedOrdinaryLaunchRelease): string;
14
+ export {};
@@ -0,0 +1,15 @@
1
+ import { compare, valid as validSemver } from "semver";
2
+ /**
3
+ * Ordinary launch has no rollback authority: it may advance to its installed package, but an
4
+ * older package installation must follow a newer approved active release instead of moving the
5
+ * shared selector backward. Explicit update/rollback uses CohortStateStore.activate directly.
6
+ */
7
+ export function selectOrdinaryLaunchReleaseId(candidate, active) {
8
+ if (!active || active.approval !== "approved")
9
+ return candidate.releaseId;
10
+ const candidateVersion = validSemver(candidate.packageVersion);
11
+ const activeVersion = validSemver(active.packageVersion);
12
+ if (!candidateVersion || !activeVersion)
13
+ throw new Error("ordinary launch release versions must be valid semantic versions");
14
+ return compare(activeVersion, candidateVersion) >= 0 ? active.releaseId : candidate.releaseId;
15
+ }
@@ -271,7 +271,7 @@ export class SupervisorServer {
271
271
  let result;
272
272
  try {
273
273
  if (command.type === "create-launch-instance")
274
- this.#createLaunchInstance(command, socket, clientId);
274
+ await this.#createLaunchInstance(command, socket, clientId);
275
275
  if (command.type === "activate-launch-instance")
276
276
  this.#activateLaunchInstance(command, socket, clientId);
277
277
  if (command.type === "begin-launch-instance-stop")
@@ -289,17 +289,28 @@ export class SupervisorServer {
289
289
  }
290
290
  catch (error) {
291
291
  const message = error instanceof Error ? error.message : String(error);
292
- const code = /not found/i.test(message) ? "not-found" : /owner|ownership|authenticated/i.test(message) ? "ownership-error" : "driver-error";
292
+ const explicitCode = error instanceof Error && "code" in error ? error.code : undefined;
293
+ const code = explicitCode === "release-superseded"
294
+ ? "release-superseded"
295
+ : /not found/i.test(message) ? "not-found"
296
+ : /owner|ownership|authenticated/i.test(message) ? "ownership-error" : "driver-error";
293
297
  result = { requestId: command.requestId, ok: false, revision: this.#revision, error: { code, message } };
294
298
  }
295
299
  this.#results.set(resultKey, result);
296
300
  this.#send(socket, { type: "command-result", result });
297
301
  }
298
- #createLaunchInstance(command, socket, clientId) {
302
+ async #createLaunchInstance(command, socket, clientId) {
299
303
  assertNativeProcessIdentity(command.guardianIdentity);
300
304
  const existing = this.store.loadLaunchInstance(command.instanceId);
305
+ if (!existing && this.readActiveReleaseId) {
306
+ const active = await this.readActiveReleaseId().catch(() => null);
307
+ if (active !== null)
308
+ this.#superseded = active !== this.release.releaseId;
309
+ }
301
310
  if (this.#superseded && !existing) {
302
- throw new Error(PRODUCT_TEXT.diagnostic(`release ${this.release.releaseId} is no longer the release new sessions start on`));
311
+ throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`release ${this.release.releaseId} is no longer active for new sessions`)), {
312
+ code: "release-superseded",
313
+ });
303
314
  }
304
315
  if (existing) {
305
316
  if (existing.ownerClientId !== clientId
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-23T11:36:01.359Z",
8
+ "builtAt": "2026-09-23T16:42:04.513Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-23T11:35:47.633Z",
8
+ "builtAt": "2026-09-23T16:42:11.967Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-23T11:36:31.857Z",
8
+ "builtAt": "2026-09-23T16:43:16.752Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "b164cd202e968ec2a0a5d94d3ffbdbe10d94372ba44675086fb6a948e0100990",
11
+ "sha256": "051b57f8b1980f1cf2accf7d9f102941e5fcf74b4264f9375d924581e950b0d6",
12
12
  "size": 177664
13
13
  },
14
14
  "provenance": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.8-dev.560",
3
+ "version": "0.1.8-dev.563",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",