@zq-silk/yui 0.16.2 → 1.0.0-alpha

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 (111) hide show
  1. package/README.md +6 -4
  2. package/dist/agent/agent.js +4 -9
  3. package/dist/agent/executionComponents.js +4 -4
  4. package/dist/agentRun/agentRun.js +8 -8
  5. package/dist/artifacts/managedGit.js +3 -56
  6. package/dist/brief/taskBrief.js +3 -3
  7. package/dist/cli/updateOrchestrator.js +2 -13
  8. package/dist/cli/updatePorts.js +36 -69
  9. package/dist/cli/upgradeCommand.js +4 -8
  10. package/dist/cli.js +4 -7
  11. package/dist/commands/controllerCommands.js +1 -1
  12. package/dist/commands/taskCommands.js +8 -1
  13. package/dist/commands/taskRoleRuntimeStatus.js +1 -1
  14. package/dist/context/runInputContract.js +1 -1
  15. package/dist/controller/agentRuntimeObserver.js +4 -4
  16. package/dist/controller/clientRuntime.js +19 -44
  17. package/dist/controller/controller.js +16 -4
  18. package/dist/controller/fileSchedulerStoreAdapter.js +6 -6
  19. package/dist/controller/globalInputDelivery.js +3 -1
  20. package/dist/controller/jobSupervisor.js +3 -3
  21. package/dist/controller/providerRetryDelivery.js +128 -118
  22. package/dist/controller/runtime.js +1 -1
  23. package/dist/controller/structuredProviderObservation.js +1 -1
  24. package/dist/coordination/workMailbox.js +4 -4
  25. package/dist/core/controllerIdentity.js +25 -0
  26. package/dist/core/controllerProcessIdentity.js +2 -2
  27. package/dist/core/controllerServer.js +4 -2
  28. package/dist/core/protocol.js +1 -1
  29. package/dist/domain/validation.js +6 -0
  30. package/dist/event/taskEvent.js +3 -3
  31. package/dist/execution/workItemExecution.js +2 -2
  32. package/dist/executor/agentExecutor.js +6 -6
  33. package/dist/executor/effectiveLaunch.js +3 -3
  34. package/dist/grant/capabilityGrant.js +2 -2
  35. package/dist/input/inputRequest.js +4 -4
  36. package/dist/integration/changeSet.js +3 -3
  37. package/dist/integration/integrationAttempt.js +3 -3
  38. package/dist/integration/integrationSourceApplication.js +1 -1
  39. package/dist/job/durableJob.js +1 -1
  40. package/dist/job/jobRunner.js +2 -2
  41. package/dist/message/message.js +8 -6
  42. package/dist/milestone/milestone.js +3 -3
  43. package/dist/profile/agentProfile.js +3 -3
  44. package/dist/release/runtimeRelease.js +9 -7
  45. package/dist/repository/project.js +3 -3
  46. package/dist/resources/liveReferences.js +1 -1
  47. package/dist/resources/sqliteResourceRegistry.js +2 -2
  48. package/dist/review/reviewRound.js +5 -5
  49. package/dist/role/role.js +7 -8
  50. package/dist/runtime/acpProtocol.js +2 -3
  51. package/dist/runtime/agentDriverObservation.js +1 -1
  52. package/dist/runtime/agentHost.js +3 -3
  53. package/dist/runtime/agentHostProtocol.js +2 -2
  54. package/dist/runtime/codexInteractiveHost.js +1 -1
  55. package/dist/runtime/launchBroker.js +1 -1
  56. package/dist/runtime/processExitObservation.js +1 -1
  57. package/dist/runtime/providerContinuationReconciliationService.js +84 -75
  58. package/dist/runtime/providerRuntimeIdentity.js +3 -3
  59. package/dist/runtime/runtimeCoherence.js +7 -3
  60. package/dist/runtime/runtimeObservation.js +3 -3
  61. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  62. package/dist/runtime/taskRuntimeIsolation.js +3 -3
  63. package/dist/runtime/tmuxAdapters.js +3 -3
  64. package/dist/scheduler/taskWake.js +1 -1
  65. package/dist/storage/baselineSchema.js +606 -0
  66. package/dist/storage/homeLayout.js +5 -16
  67. package/dist/storage/recordValidation.js +16 -4
  68. package/dist/storage/sqliteSchema.js +106 -1741
  69. package/dist/storage/sqliteStore.js +16 -14
  70. package/dist/storage/storageSchema.js +8 -7
  71. package/dist/storage/storageVersions.js +23 -16
  72. package/dist/storage/taskStore.js +3 -29
  73. package/dist/storage/upgrade/upgradeOrchestrator.js +14 -102
  74. package/dist/task/task.js +15 -7
  75. package/dist/task/taskActivation.js +5 -4
  76. package/dist/telemetry/sqliteTelemetryStore.js +2 -2
  77. package/dist/verification/gateArtifact.js +5 -3
  78. package/dist/verification/verificationPlan.js +6 -7
  79. package/dist/workItem/workItem.js +6 -6
  80. package/dist/workspace/cleanupInspection.js +1 -9
  81. package/dist/worktree/managedWorkspace.js +3 -3
  82. package/docs/managed-turn-and-session-runtime.md +26 -14
  83. package/docs/managed-turn-and-session-runtime.zh-CN.md +18 -10
  84. package/docs/release-workflow.md +52 -300
  85. package/docs/release-workflow.zh-CN.md +41 -234
  86. package/docs/sqlite-control-plane-design.md +48 -289
  87. package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
  88. package/docs/storage-baseline.md +132 -0
  89. package/docs/storage-baseline.zh-CN.md +106 -0
  90. package/docs/task-delivery.md +3 -4
  91. package/docs/task-delivery.zh-CN.md +3 -3
  92. package/docs/testing/verification-levels.md +40 -180
  93. package/docs/testing/verification-levels.zh-CN.md +27 -131
  94. package/i18n/README.zh-CN.md +5 -4
  95. package/package.json +1 -1
  96. package/dist/storage/migrations/agentFailureContext.js +0 -22
  97. package/dist/storage/migrations/agentRunContract.js +0 -159
  98. package/dist/storage/migrations/artifactsToGit.js +0 -338
  99. package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
  100. package/dist/storage/migrations/currentInputContract.js +0 -86
  101. package/dist/storage/migrations/currentRuntimeContract.js +0 -228
  102. package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
  103. package/dist/storage/migrations/integrationContinuation.js +0 -105
  104. package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
  105. package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
  106. package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
  107. package/dist/storage/migrations/submitIntent.js +0 -126
  108. package/dist/storage/migrations/unifyHomeLayout.js +0 -925
  109. package/dist/storage/migrations/verificationPlanV1.js +0 -162
  110. package/dist/storage/migrations/verificationPolicy.js +0 -74
  111. package/dist/storage/migrations/workItemHistory.js +0 -46
package/README.md CHANGED
@@ -34,8 +34,9 @@ not from terminal windows you juggle or details you have to remember.
34
34
  - **Isolated by default** — repository work happens in managed Git worktrees;
35
35
  the stable checkout stays read-only.
36
36
 
37
- > **Status:** pre-1.0 (0.15.x). CLI surfaces and configuration may still change
38
- > between releases; each upgrade migrates valid existing Homes.
37
+ > **Status:** 1.0.0-alpha, with a clean storage 1.0 baseline. Default updates
38
+ > support only declared minor upgrades within one storage major. Existing v37
39
+ > Homes require the separate [one-time conversion](docs/storage-baseline.md).
39
40
 
40
41
  [Quick start](#quick-start) · [Working through conversation](#working-through-conversation) · [Architecture](#architecture) · [Design principles](#design-principles)
41
42
 
@@ -383,8 +384,9 @@ npm users do not need to compile it.
383
384
 
384
385
  To exercise your checkout, run `make install-local`, then use the absolute
385
386
  `<checkout>/output/dev/bin/yui` launcher. It defaults to an isolated Home under
386
- that checkout; run its `setup` before stateful use. Do not use the global `yui`
387
- or `make link` to validate local changes. Live-model, paid or shared-resource
387
+ that checkout; run its `setup` before stateful use. Development tooling does not
388
+ manage global installations; do not use the global `yui` to validate local changes.
389
+ Live-model, paid or shared-resource
388
390
  tests require an explicit request for those resources.
389
391
 
390
392
  ## Community and support
@@ -9,7 +9,7 @@ export function createConfiguredAgent(id, adapterId, command, baseArgs, environm
9
9
  validateAgentBaseArguments(adapterId, baseArgs);
10
10
  const timestamp = now.toISOString();
11
11
  return {
12
- schemaVersion: 3,
12
+ schemaVersion: 1,
13
13
  id: normalizedId,
14
14
  // An unnamed component resolves from the plan, which for ACP means the
15
15
  // unidentified entry rather than a guess at which product is installed.
@@ -45,18 +45,13 @@ export function resolveAgentEnvironment(agent, processEnvironment = process.env)
45
45
  }));
46
46
  }
47
47
  export function validateConfiguredAgent(agent) {
48
- if (agent.schemaVersion !== 3)
48
+ if (agent.schemaVersion !== 1)
49
49
  throw new Error("Agent schema version is invalid.");
50
50
  requireSafeIdentity(agent.id, "Agent id");
51
51
  if (!isAgentAdapterId(agent.adapterId))
52
52
  throw new Error(`Agent adapter is unsupported: ${agent.adapterId}.`);
53
- // A schema 3 record always carries a component: storage 10 backfilled every
54
- // stored Agent, and the constructor resolves one for every new Agent. So an
55
- // absent value here is a corrupt record, not an old one, and resolving it to
56
- // the plan default would invent a product identity for data that never lost
57
- // one. That default belongs to the constructor, where an operator naming
58
- // only a plan is a real and supported request — this validator reads records
59
- // that were already written, where the same silence means something else.
53
+ // Creation resolves an omitted component before persistence. Reading a
54
+ // stored record must not invent a missing product identity.
60
55
  if (agent.component === undefined) {
61
56
  throw new Error(`Agent is missing its execution component: ${agent.id}.`);
62
57
  }
@@ -138,10 +138,10 @@ export function displayExecutionComponent(adapterId, componentId) {
138
138
  /**
139
139
  * Resolve the component for a connection plan, given what the caller stated.
140
140
  *
141
- * An absent component is not an error: it means the caller named only the plan,
142
- * which is how every binding made before this axis existed reads. It resolves
143
- * to the plan's default, which for ACP is the unidentified entry — the value is
144
- * never inferred from a command, an argument or an environment variable.
141
+ * Creation may name only a connection plan. Resolve its default before
142
+ * persistence; current stored Agents and bindings require an explicit component.
143
+ * ACP defaults to the unidentified entry, never a product inferred from command,
144
+ * arguments or environment.
145
145
  */
146
146
  export function resolveAgentExecutionComponent(adapterId, componentId) {
147
147
  if (componentId === undefined)
@@ -36,7 +36,7 @@ export function createRun(id, taskId, roleName, mode, input, now, context) {
36
36
  if (snapshot.taskId !== taskId)
37
37
  throw new Error("AgentRun Context Snapshot belongs to another Task.");
38
38
  return {
39
- schemaVersion: 5,
39
+ schemaVersion: 1,
40
40
  id: requireSafeIdentity(id, "AgentRun id"),
41
41
  taskId: requireSafeIdentity(taskId, "Task id"),
42
42
  roleName: requireSafeIdentity(roleName, "Role name"),
@@ -132,8 +132,8 @@ export function validateRun(run) {
132
132
  "createdAt",
133
133
  "updatedAt"
134
134
  ], "AgentRun");
135
- if (run.schemaVersion !== 5)
136
- throw new Error("AgentRun must use schemaVersion 5.");
135
+ if (run.schemaVersion !== 1)
136
+ throw new Error("AgentRun must use schemaVersion 1.");
137
137
  validateTaskRecordReference({ taskId: run.taskId, localId: run.id }, "run");
138
138
  requireSafeIdentity(run.roleName, "Role name");
139
139
  if (run.mode !== "new" && run.mode !== "resume") {
@@ -283,8 +283,8 @@ export function validateRun(run) {
283
283
  throw new Error("Execution AgentRun cannot carry Review effective provenance.");
284
284
  }
285
285
  for (const record of run.inputs) {
286
- // Structural validation keeps historical records and observed subsequent
287
- // inputs readable. Creation/submission require the initial frozen Snapshot.
286
+ // Validate readable evidence, not readiness to execute. A missing Snapshot
287
+ // must still allow inspection, failure settlement and explicit retirement.
288
288
  createRunInputEnvelope(runEnvelopeContext(run), record.input);
289
289
  }
290
290
  if (!["active", "completed", "failed"].includes(run.status)) {
@@ -335,7 +335,7 @@ function finishRun(run, status, output, now, failureReason, provider, systemEvid
335
335
  ...run,
336
336
  status,
337
337
  result: {
338
- schemaVersion: 2,
338
+ schemaVersion: 1,
339
339
  ...(output === undefined ? {} : { output: requireResultText(output, "AgentRun result output") }),
340
340
  ...(diagnostic === undefined
341
341
  ? {}
@@ -352,8 +352,8 @@ function finishRun(run, status, output, now, failureReason, provider, systemEvid
352
352
  return validateRun(terminal);
353
353
  }
354
354
  function validateRunResult(result) {
355
- if (result === undefined || result.schemaVersion !== 2) {
356
- throw new Error("A terminal AgentRun requires AgentRunResult schemaVersion 2.");
355
+ if (result === undefined || result.schemaVersion !== 1) {
356
+ throw new Error("A terminal AgentRun requires AgentRunResult schemaVersion 1.");
357
357
  }
358
358
  rejectUnknownFields(result, [
359
359
  "schemaVersion",
@@ -1,4 +1,4 @@
1
- import { execFile, execFileSync } from "node:child_process";
1
+ import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  const executeFile = promisify(execFile);
4
4
  /**
@@ -259,8 +259,8 @@ function isExternalProgramConfigKey(key) {
259
259
  * list of repo-local keys that can execute an external program. Empty means the
260
260
  * repository's own config is within the managed boundary.
261
261
  *
262
- * PURE: no I/O. Callers (sync migration and async runtime) read the config with
263
- * their own managed runner and pass the bytes here, so the security policy lives
262
+ * PURE: no I/O. Callers read the config with the managed runner and pass the
263
+ * bytes here, so the security policy lives
264
264
  * in exactly one testable place. Reading `--local` deliberately excludes the
265
265
  * command-line `-c` hardening flags (which are safe and not persisted) and the
266
266
  * null-device'd global/system config; it sees only what is written in the repo.
@@ -277,56 +277,3 @@ export function externalProgramConfigViolations(localConfigListZ) {
277
277
  }
278
278
  return [...offending].sort();
279
279
  }
280
- /**
281
- * SYNCHRONOUS managed invocation, used ONLY where an async runner is
282
- * structurally impossible: a storage `migrateData(db)` step runs inside
283
- * `db.transaction(...)`, which better-sqlite3 requires to be synchronous, yet
284
- * the 18->19 migration must build per-Task artifact repositories on disk. This
285
- * shares the SAME hardening as {@link spawnManagedGit} — identical argument
286
- * refusal, identical scrubbed environment, identical prepended flags — so the
287
- * synchronous path never weakens the trust boundary. It is not exported for
288
- * ordinary runtime use; the async runner remains the only production write path.
289
- */
290
- function spawnManagedGitSync(repoPath, args, options) {
291
- assertSafeArguments(args);
292
- const env = managedGitEnvironment();
293
- if (options?.commitDates !== undefined) {
294
- // Deterministic history: fixed author/committer dates make a rebuild of the
295
- // same source data reproduce the same commit ids. This only sets metadata.
296
- env.GIT_AUTHOR_DATE = options.commitDates.author;
297
- env.GIT_COMMITTER_DATE = options.commitDates.committer;
298
- }
299
- try {
300
- const stdout = execFileSync("git", [...HARDENING_FLAGS, "-C", repoPath, ...args], {
301
- encoding: "buffer",
302
- env,
303
- maxBuffer: options?.maxBuffer ?? 16 * 1024 * 1024,
304
- timeout: options?.timeoutMs ?? 30_000,
305
- windowsHide: true
306
- });
307
- return { stdout: stdout, stderr: Buffer.alloc(0) };
308
- }
309
- catch (error) {
310
- throw new ManagedGitError([...args], readErrorStream(error), error);
311
- }
312
- }
313
- /** Synchronous counterpart to {@link managedGit}; returns trimmed UTF-8 stdout. */
314
- export function managedGitSync(repoPath, args, options) {
315
- return spawnManagedGitSync(repoPath, args, options).stdout.toString("utf8");
316
- }
317
- /** Synchronous counterpart to {@link managedGitBuffer}; returns raw stdout bytes. */
318
- export function managedGitSyncBuffer(repoPath, args, options) {
319
- return spawnManagedGitSync(repoPath, args, options).stdout;
320
- }
321
- /** Synchronous counterpart to {@link managedGitSucceeds}; never throws on non-zero exit. */
322
- export function managedGitSyncSucceeds(repoPath, args, options) {
323
- try {
324
- spawnManagedGitSync(repoPath, args, options);
325
- return true;
326
- }
327
- catch (error) {
328
- if (error instanceof ManagedGitError)
329
- return false;
330
- throw error;
331
- }
332
- }
@@ -1,6 +1,6 @@
1
1
  export function createTaskBrief(input, now) {
2
2
  return {
3
- schemaVersion: 2,
3
+ schemaVersion: 1,
4
4
  objective: requireText(input.objective, "Task objective"),
5
5
  boundaries: normalizeBoundaries(input.boundaries),
6
6
  technicalApproach: optionalText(input.technicalApproach, "Task technical approach"),
@@ -22,8 +22,8 @@ export function updateTaskBrief(brief, patch, updatedBy, now) {
22
22
  }, now);
23
23
  }
24
24
  export function validateTaskBrief(brief) {
25
- if (brief.schemaVersion !== 2) {
26
- throw new Error("Task Brief requires schemaVersion 2.");
25
+ if (brief.schemaVersion !== 1) {
26
+ throw new Error("Task Brief requires schemaVersion 1.");
27
27
  }
28
28
  if (!Array.isArray(brief.boundaries) || typeof brief.technicalApproach !== "string"
29
29
  || typeof brief.updatedAt !== "string" || !Number.isFinite(Date.parse(brief.updatedAt))) {
@@ -7,6 +7,7 @@
7
7
  * verified before the Controller is restarted.
8
8
  */
9
9
  import { UpdateControllerReconciliationError } from "../controller/updateReconciliation.js";
10
+ import { isControllerIdentity } from "../core/controllerIdentity.js";
10
11
  export function runUpdate(ports, options) {
11
12
  let staged;
12
13
  try {
@@ -61,8 +62,6 @@ function runStagedUpdate(ports, staged, home) {
61
62
  action: preflight.action,
62
63
  recoverable: true,
63
64
  version: staged.version,
64
- ...(preflight.blockers === undefined ? {} : { blockers: preflight.blockers }),
65
- ...(preflight.retryCommand === undefined ? {} : { retryCommand: preflight.retryCommand }),
66
65
  ...(preflight.sceneUnchanged === true ? { sceneUnchanged: true } : {})
67
66
  };
68
67
  }
@@ -159,8 +158,7 @@ function runCoordinatedUpdate(ports, staged, home) {
159
158
  return restoreControllerOrReport(ports, home, captured.lifecycle, {
160
159
  outcome: "aborted", phase: "preflight",
161
160
  message: fencedPreflight.message, action: fencedPreflight.action,
162
- recoverable: true, version: staged.version,
163
- ...(fencedPreflight.blockers === undefined ? {} : { blockers: fencedPreflight.blockers })
161
+ recoverable: true, version: staged.version
164
162
  });
165
163
  }
166
164
  return activateAndVerify(ports, staged, home, captured.lifecycle, fencedPreflight);
@@ -391,15 +389,6 @@ function isStorageMigrationResult(value) {
391
389
  function isPositivePid(value) {
392
390
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
393
391
  }
394
- function isControllerIdentity(value) {
395
- return isRecord(value)
396
- && typeof value.executablePath === "string"
397
- && value.executablePath.length > 0
398
- && Array.isArray(value.args)
399
- && value.args.every((arg) => typeof arg === "string")
400
- && typeof value.version === "string"
401
- && value.version.length > 0;
402
- }
403
392
  function isRecord(value) {
404
393
  return typeof value === "object" && value !== null && !Array.isArray(value);
405
394
  }
@@ -25,11 +25,13 @@
25
25
  * mismatch fails closed.
26
26
  */
27
27
  import { spawnSync } from "node:child_process";
28
- import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
28
+ import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
29
29
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
30
30
  import { fileURLToPath } from "node:url";
31
31
  import { runtimeError } from "../errors/cliError.js";
32
32
  import { isConcreteVersion } from "../domain/validation.js";
33
+ import { parseControllerIdentity } from "../core/controllerIdentity.js";
34
+ import { isMinorStorageUpgrade, isStorageVersion, storageVersionParts } from "../storage/storageVersions.js";
33
35
  import { STORAGE_DOCTOR_CHECK_NAMES } from "../doctor/doctor.js";
34
36
  import { acquireHandoverLock } from "../release/runtimeRelease.js";
35
37
  import { updateStagingRoot } from "../storage/homeLayout.js";
@@ -507,18 +509,16 @@ function restoreControllerIdentity(home, identity, environment, spawn) {
507
509
  "const { spawn } = require('node:child_process');",
508
510
  "const values = process.argv.slice(1);",
509
511
  "const handoverOwnerPid = Number(values.pop());",
510
- "const version = values.pop();",
511
- "const args = JSON.parse(values.pop());",
512
- "const executable = values.pop();",
512
+ "const identity = JSON.parse(values.pop());",
513
513
  "const home = values.pop();",
514
514
  "const runtimeModule = values.pop();",
515
515
  "(async () => {",
516
516
  " const { ensureFileTaskControllerIdentity } = await import(runtimeModule);",
517
- " await ensureFileTaskControllerIdentity(home, { executablePath: executable, args, version }, {",
517
+ " await ensureFileTaskControllerIdentity(home, identity, {",
518
518
  " environment: process.env,",
519
519
  " handoverOwnerPid,",
520
520
  " spawnController: (_home, launchEnv) => {",
521
- " const child = spawn(executable, args, { detached: true, stdio: 'ignore', env: launchEnv });",
521
+ " const child = spawn(identity.executablePath, identity.args, { detached: true, stdio: 'ignore', env: launchEnv });",
522
522
  " child.unref();",
523
523
  " }",
524
524
  " });",
@@ -529,9 +529,7 @@ function restoreControllerIdentity(home, identity, environment, spawn) {
529
529
  helper,
530
530
  UPDATE_CLIENT_RUNTIME_PATH,
531
531
  home,
532
- identity.executablePath,
533
- JSON.stringify(identity.args),
534
- identity.version,
532
+ JSON.stringify(parseControllerIdentity(identity)),
535
533
  String(process.pid)
536
534
  ], { cwd: process.cwd(), env: launchEnvironment, shell: false, stdio: "pipe" });
537
535
  assertSpawnOk(result, "restore the previously running Controller identity");
@@ -656,21 +654,6 @@ function structuredErrorMessage(result) {
656
654
  }
657
655
  return undefined;
658
656
  }
659
- function parseControllerIdentity(value) {
660
- if (typeof value.executablePath !== "string"
661
- || value.executablePath.length === 0
662
- || !Array.isArray(value.args)
663
- || value.args.some((arg) => typeof arg !== "string")
664
- || typeof value.version !== "string"
665
- || value.version.length === 0) {
666
- throw new Error("Authenticated Controller identity is malformed; treating ownership as unknown-active.");
667
- }
668
- return {
669
- executablePath: value.executablePath,
670
- args: value.args,
671
- version: value.version
672
- };
673
- }
674
657
  function controllerErrorCodeFromResult(result) {
675
658
  for (const buffer of [result.stdout, result.stderr]) {
676
659
  try {
@@ -775,23 +758,22 @@ function assertActivatedControllerIdentity(identity, activatedBinary, activatedV
775
758
  * entrypoint derivation as the production startup identity check (P1-1, rr23).
776
759
  */
777
760
  export function activatedControllerEntrypoint(activatedBinary) {
778
- let resolvedBinary;
779
- try {
780
- resolvedBinary = realpathSync(activatedBinary);
781
- }
782
- catch {
783
- // `verify` already checked existsSync. Keep the fallback deterministic for
784
- // test seams and fail closed later if the identity does not match it.
785
- resolvedBinary = resolve(activatedBinary);
786
- }
761
+ const resolvedBinary = realpathSync(activatedBinary);
787
762
  const direct = join(dirname(resolvedBinary), "controller", "controllerMain.js");
788
- if (existsSync(direct))
789
- return direct;
790
763
  // npm may expose a non-symlink launcher under <prefix>/bin. Resolve the
791
764
  // package's canonical global layout when it is present.
792
765
  const prefix = resolve(dirname(resolvedBinary), "..");
793
766
  const packageEntrypoint = join(prefix, "lib", "node_modules", PACKAGE_NAME, "dist", "controller", "controllerMain.js");
794
- return existsSync(packageEntrypoint) ? packageEntrypoint : direct;
767
+ for (const candidate of [direct, packageEntrypoint]) {
768
+ if (!existsSync(candidate))
769
+ continue;
770
+ const entrypoint = realpathSync(candidate);
771
+ if (!statSync(entrypoint).isFile()) {
772
+ throw runtimeError(`Controller entrypoint is not a regular file: ${entrypoint}.`);
773
+ }
774
+ return entrypoint;
775
+ }
776
+ throw runtimeError(`Controller entrypoint is missing for the activated binary: ${resolvedBinary}.`);
795
777
  }
796
778
  function interpretPreflight(result) {
797
779
  // Require a valid `{ ok:true, data }` success envelope before trusting any
@@ -841,7 +823,6 @@ function interpretPreflight(result) {
841
823
  action: "Use a staged binary that supports the update-preflight contract; do not force the update."
842
824
  };
843
825
  }
844
- const blockers = parseUpdateBlockers(data.blockers);
845
826
  return {
846
827
  status: "blocked",
847
828
  stage: typeof data.stage === "string" ? data.stage : "preflight",
@@ -849,8 +830,6 @@ function interpretPreflight(result) {
849
830
  action: typeof data.action === "string"
850
831
  ? data.action
851
832
  : "Resolve the reported condition and retry.",
852
- ...(blockers === undefined ? {} : { blockers }),
853
- ...(typeof data.retryCommand === "string" ? { retryCommand: data.retryCommand } : {}),
854
833
  ...(data.sceneUnchanged === true ? { sceneUnchanged: true } : {})
855
834
  };
856
835
  }
@@ -864,9 +843,13 @@ function parseUpdatePreflightResult(data) {
864
843
  const homeClassification = data.classification;
865
844
  if (!isRecord(homeClassification) || !isRecord(homeClassification.classification))
866
845
  return null;
846
+ const from = homeClassification.storageVersion;
847
+ const to = homeClassification.currentStorageVersion;
848
+ if (!isStorageVersion(from) || !isStorageVersion(to))
849
+ return null;
867
850
  const classification = homeClassification.classification;
868
851
  if (data.status === "already-current") {
869
- if (data.stepCount !== 0
852
+ if (data.stepCount !== 0 || from !== to
870
853
  || classification.verdict !== "USABLE"
871
854
  || classification.status !== "current")
872
855
  return null;
@@ -875,8 +858,17 @@ function parseUpdatePreflightResult(data) {
875
858
  if (data.stepCount <= 0
876
859
  || classification.verdict !== "MIGRATABLE"
877
860
  || classification.status !== "migration-ready"
861
+ || !isMinorStorageUpgrade(from, to)
878
862
  || !data.steps.every(isStorageMigrationStep))
879
863
  return null;
864
+ let previous = from;
865
+ for (const step of data.steps) {
866
+ if (step.fromVersion !== previous)
867
+ return null;
868
+ previous = step.toVersion;
869
+ }
870
+ if (previous !== to)
871
+ return null;
880
872
  return { status: "migration-ready", stepCount: data.stepCount };
881
873
  }
882
874
  function interpretStorageMigration(result) {
@@ -912,38 +904,13 @@ function interpretStorageMigration(result) {
912
904
  }
913
905
  function isStorageMigrationStep(value) {
914
906
  return isRecord(value)
915
- && Number.isSafeInteger(value.fromVersion)
916
- && Number.isSafeInteger(value.toVersion)
917
- && value.toVersion === value.fromVersion + 1
907
+ && isStorageVersion(value.fromVersion)
908
+ && isStorageVersion(value.toVersion)
909
+ && isMinorStorageUpgrade(value.fromVersion, value.toVersion)
910
+ && storageVersionParts(value.toVersion).minor === storageVersionParts(value.fromVersion).minor + 1
918
911
  && typeof value.name === "string"
919
912
  && value.name.length > 0;
920
913
  }
921
- function parseUpdateBlockers(value) {
922
- if (value === undefined)
923
- return undefined;
924
- if (!Array.isArray(value))
925
- return undefined;
926
- const parsed = [];
927
- for (const item of value) {
928
- if (!isRecord(item) || typeof item.reason !== "string" || item.reason.length === 0) {
929
- return undefined;
930
- }
931
- const optional = ["taskId", "roleName", "runId", "nativeSessionId"];
932
- if (optional.some((key) => item[key] !== undefined && typeof item[key] !== "string")) {
933
- return undefined;
934
- }
935
- parsed.push({
936
- ...(typeof item.taskId === "string" ? { taskId: item.taskId } : {}),
937
- ...(typeof item.roleName === "string" ? { roleName: item.roleName } : {}),
938
- ...(typeof item.runId === "string" ? { runId: item.runId } : {}),
939
- ...(typeof item.nativeSessionId === "string"
940
- ? { nativeSessionId: item.nativeSessionId }
941
- : {}),
942
- reason: item.reason
943
- });
944
- }
945
- return parsed;
946
- }
947
914
  /**
948
915
  * Extract the validated `data` object from a spawn result's `{ ok: true, data }`
949
916
  * JSON envelope, or `null` when the result is not a trustworthy success envelope.
@@ -1,6 +1,7 @@
1
1
  /** `yui upgrade` plans or applies the supported linear storage migration chain. */
2
2
  import { runStorageUpgrade } from "../storage/upgrade/upgradeOrchestrator.js";
3
3
  import { ensureFileTaskController, ensureFileTaskControllerIdentity, stopFileTaskController } from "../controller/clientRuntime.js";
4
+ import { parseControllerIdentity } from "../core/controllerIdentity.js";
4
5
  import { callController, ControllerClientError } from "../core/controllerClient.js";
5
6
  import { spawn } from "node:child_process";
6
7
  import { runtimeError, usageError } from "../errors/cliError.js";
@@ -99,16 +100,11 @@ async function captureUpgradeController(home) {
99
100
  return undefined;
100
101
  throw error;
101
102
  }
102
- const identity = value;
103
- if (!Number.isSafeInteger(identity.pid) || identity.pid < 1
104
- || typeof identity.executablePath !== "string" || !identity.executablePath
105
- || !Array.isArray(identity.args) || identity.args.some(arg => typeof arg !== "string")
106
- || typeof identity.version !== "string" || !identity.version) {
103
+ const pid = value?.pid;
104
+ if (!Number.isSafeInteger(pid) || pid < 1) {
107
105
  throw runtimeError("Cannot capture the exact Controller for a reversible upgrade preflight.");
108
106
  }
109
- return { pid: identity.pid, identity: {
110
- executablePath: identity.executablePath, args: identity.args, version: identity.version
111
- } };
107
+ return { pid: pid, identity: parseControllerIdentity(value) };
112
108
  }
113
109
  async function runUpdateOwnedUpgrade(home, environment) {
114
110
  const ownerText = environment.YUI_UPDATE_HANDOVER_OWNER_PID;
package/dist/cli.js CHANGED
@@ -100,7 +100,7 @@ import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/ma
100
100
  import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
101
101
  import { runSetupCommand, validateSetupInvocation } from "./setup/setupCommand.js";
102
102
  import { openCurrentTaskStore, validateCurrentTaskStore } from "./storage/currentTaskStore.js";
103
- import { SqliteSchemaMigrationError } from "./storage/sqliteSchema.js";
103
+ import { SqliteSchemaError } from "./storage/sqliteSchema.js";
104
104
  import { inspectStorageSchema } from "./storage/storageSchema.js";
105
105
  import { resolveYuiHome } from "./storage/taskStore.js";
106
106
  import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
@@ -141,7 +141,7 @@ void main().catch((error) => {
141
141
  });
142
142
  function runtimeFailureMessage(error) {
143
143
  const message = error instanceof Error ? error.message : String(error);
144
- if (!(error instanceof SqliteSchemaMigrationError))
144
+ if (!(error instanceof SqliteSchemaError))
145
145
  return message;
146
146
  try {
147
147
  return `${message}\n${describeCliHomeInvocation({
@@ -542,12 +542,9 @@ export async function main() {
542
542
  validateCurrentTaskStore(home);
543
543
  const controllerMethod = method;
544
544
  const updateHandoverOwner = process.env.YUI_UPDATE_HANDOVER_OWNER_PID;
545
- // A pre-fix updater cannot pass the owner environment variable to the
546
- // activated restart child, but it remains that child's direct parent while
547
- // holding the exact handover lock. Inherit only that OS-backed relationship;
548
- // every unrelated live lock still compares foreign and fails closed.
545
+ // Maintenance children must explicitly name their exact lock owner.
549
546
  const updateHandoverOwnerPid = updateHandoverOwner === undefined
550
- ? (Number.isSafeInteger(process.ppid) && process.ppid > 0 ? process.ppid : undefined)
547
+ ? undefined
551
548
  : Number(updateHandoverOwner);
552
549
  if (updateHandoverOwnerPid !== undefined
553
550
  && (!Number.isSafeInteger(updateHandoverOwnerPid) || updateHandoverOwnerPid < 1)) {
@@ -49,7 +49,7 @@ export function renderControllerResourceStatus(snapshot, verbose, width = defaul
49
49
  { header: "Reason", minWidth: 12, maxWidth: 30 }
50
50
  ], visibleDomains.map((domain) => [
51
51
  domain.yuiHome,
52
- domain.domainKind ?? "legacy",
52
+ domain.domainKind ?? "unmarked",
53
53
  domain.liveness === undefined
54
54
  ? "—"
55
55
  : `${domain.liveness}/${domain.disposition ?? "review"}`,
@@ -4916,7 +4916,14 @@ function retryRunOperation(args, store, options) {
4916
4916
  const now = clock(options);
4917
4917
  const previous = store.transaction((tx) => {
4918
4918
  const run = requireRun(tx, args[0], options);
4919
- readRunContextSnapshot(tx, run);
4919
+ // Review, synthesis and bounded automatic recovery reuse exact evidence.
4920
+ // Explicit ordinary retries below
4921
+ // create a new Run and Snapshot from current authorized facts; missing old
4922
+ // evidence must not prevent that explicit recovery.
4923
+ if (run.purpose === "review" || run.sourceExecutionGroupId !== undefined
4924
+ || options.providerRetryChainId !== undefined) {
4925
+ readRunContextSnapshot(tx, run);
4926
+ }
4920
4927
  return run;
4921
4928
  });
4922
4929
  if (previous.purpose === "review") {
@@ -446,7 +446,7 @@ function projectTaskRoleRuntime(run, session, tmux, events, _mailbox, store, tas
446
446
  const fence = { ...basicFence, ...(nativeTurnId === undefined ? {} : { nativeTurnId }) };
447
447
  let projection = projectRuntimeTaskEvents(fence, createdAt, events);
448
448
  projection = projectRuntimeObservation(projection, createRuntimeObservation({
449
- schemaVersion: 4,
449
+ schemaVersion: 1,
450
450
  eventId: `runtime-host-${receiptId}`,
451
451
  semanticKey: `runtime-host-${receiptId}`,
452
452
  kind: "host.observed",
@@ -36,7 +36,7 @@ export function validateRunInput(value) {
36
36
  /** Execution must never substitute current Task facts for missing frozen evidence. */
37
37
  export function requireRunContextSnapshotRef(input) {
38
38
  if (input.contextSnapshotRef === undefined) {
39
- throw new Error("AgentRun Context Snapshot is required for execution; the record remains audit-only.");
39
+ throw new Error("AgentRun Context Snapshot is required for this execution. Inspect the Run and retire or explicitly retry it; original evidence is never reconstructed.");
40
40
  }
41
41
  return validateContextSnapshotRef(input.contextSnapshotRef);
42
42
  }
@@ -102,7 +102,7 @@ export class AgentRuntimeObserver {
102
102
  ? tokenObservationIdentity("baseline", fence, source.sourceId, baselineKey)
103
103
  : usageObservationIdentity(fence, source.sourceId, latestOccurrence);
104
104
  this.inbox.enqueueObservation(createRuntimeObservation({
105
- schemaVersion: 4,
105
+ schemaVersion: 1,
106
106
  eventId: identity.eventId,
107
107
  semanticKey: identity.semanticKey,
108
108
  kind: "activity.observed",
@@ -137,7 +137,7 @@ export class AgentRuntimeObserver {
137
137
  const health = JSON.stringify([sample.status, sample.detail ?? null]);
138
138
  if (state.health !== health) {
139
139
  this.inbox.enqueueObservation(createRuntimeObservation({
140
- schemaVersion: 4,
140
+ schemaVersion: 1,
141
141
  eventId: observationId("health", fence, source.sourceId, health),
142
142
  semanticKey: observationId("health", fence, source.sourceId, health),
143
143
  kind: "observer.health",
@@ -183,7 +183,7 @@ export class AgentRuntimeObserver {
183
183
  usages.forEach((occurrence, usageIndex) => {
184
184
  const identity = usageObservationIdentity(fence, source.sourceId, occurrence);
185
185
  this.inbox.enqueueObservation(createRuntimeObservation({
186
- schemaVersion: 4,
186
+ schemaVersion: 1,
187
187
  eventId: identity.eventId,
188
188
  semanticKey: identity.semanticKey,
189
189
  kind: "activity.observed",
@@ -213,7 +213,7 @@ export class AgentRuntimeObserver {
213
213
  });
214
214
  if (activityChanged && state.cursor !== undefined) {
215
215
  this.inbox.enqueueObservation(createRuntimeObservation({
216
- schemaVersion: 4,
216
+ schemaVersion: 1,
217
217
  eventId: observationId("activity", fence, source.sourceId, sample.activityId),
218
218
  semanticKey: observationId("activity", fence, source.sourceId, sample.activityId),
219
219
  kind: "activity.observed",