@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
@@ -12,7 +12,7 @@
12
12
  * the write transaction; conflict ->
13
13
  * StorageConflictError (transactionWithRevisionCas).
14
14
  * - Atomic durable write ........ WAL + synchronous=FULL; COMMIT == fsync.
15
- * - Mailbox per-target ordering . WorkMailbox v2 sequence/cursors in one row.
15
+ * - Mailbox per-target ordering . WorkMailbox sequence/cursors in one row.
16
16
  * - Exactly-once terminal state . conditional updates + UNIQUE(request_id)
17
17
  * on the durable outbox.
18
18
  * - Crash recovery .............. WAL rollback of uncommitted transactions;
@@ -62,8 +62,9 @@ import { gateArtifactKey, validateGateArtifact } from "../verification/gateArtif
62
62
  import { validateWorkItem } from "../workItem/workItem.js";
63
63
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
64
64
  import { contextInputReferences, queryContextRecords } from "./contextRecords.js";
65
- import { inspectSqliteSchemaMigrations, migrateSqliteSchema, SqliteSchemaMigrationError, TELEMETRY_KEEP_PER_RUN, TELEMETRY_RUN_CAP } from "./sqliteSchema.js";
65
+ import { initializeSqliteSchema, validateSqliteSchema, SqliteSchemaError, TELEMETRY_KEEP_PER_RUN, TELEMETRY_RUN_CAP } from "./sqliteSchema.js";
66
66
  import { StorageSchemaError } from "./storageSchema.js";
67
+ import { STORAGE_FORMAT } from "./storageVersions.js";
67
68
  import { queryTaskCatalog } from "./taskCatalog.js";
68
69
  import { CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION, executionLaneActiveRunKey, executionLaneActiveRunKeyParts, isValidCapabilityGrantTransition, isValidReleaseWorkflowTransition, pendingWakeupProjection, StorageCancelledError, StorageConflictError, StorageRecordError, storedCapabilityGrant, storedPublicationReference, storedReleaseWorkflow, validateYuiConfig } from "./taskStore.js";
69
70
  /** Read the immutable Home identity without opening a writable Store connection. */
@@ -139,7 +140,8 @@ export class SqliteTaskStore {
139
140
  #dirty = false;
140
141
  constructor(rootDir, _options = {}) {
141
142
  this.#rootDir = rootDir;
142
- mkdirSync(rootDir, { recursive: true, mode: 0o700 });
143
+ if (!_options.readonly)
144
+ mkdirSync(rootDir, { recursive: true, mode: 0o700 });
143
145
  const filename = _options.databaseFilename ?? "yui.db";
144
146
  const databasePath = join(rootDir, filename);
145
147
  // Remember whether the database file existed before this connection opened
@@ -147,18 +149,17 @@ export class SqliteTaskStore {
147
149
  // may seed its singleton rows; a store that opens an existing file missing
148
150
  // those rows has found corruption, not a bootstrap opportunity.
149
151
  const databaseExisted = existsSync(databasePath);
150
- this.#db = new Database(databasePath);
152
+ this.#db = new Database(databasePath, _options.readonly ? { readonly: true, fileMustExist: true } : {});
151
153
  try {
154
+ const existingSchema = databaseExisted ? validateSqliteSchema(this.#db) : undefined;
152
155
  // §4.1 / §9: WAL, no fsync weakening, FKs on, busy timeout for CLI contention.
153
- this.#db.pragma("journal_mode = WAL");
156
+ if (!_options.readonly)
157
+ this.#db.pragma("journal_mode = WAL");
154
158
  this.#db.pragma("synchronous = FULL");
155
159
  this.#db.pragma("foreign_keys = ON");
156
160
  this.#db.pragma("busy_timeout = 5000");
157
161
  this.#db.pragma("wal_autocheckpoint = 1000");
158
- migrateSqliteSchema(this.#db, {
159
- mode: !databaseExisted ? "apply" : "validate"
160
- });
161
- const schema = inspectSqliteSchemaMigrations(this.#db);
162
+ const schema = existingSchema ?? initializeSqliteSchema(this.#db);
162
163
  this.#openedSchemaHead = {
163
164
  version: schema.currentVersion,
164
165
  checksum: schema.currentChecksum
@@ -309,12 +310,13 @@ export class SqliteTaskStore {
309
310
  }
310
311
  /** Verify the SQLite schema head at the single write boundary. */
311
312
  #prepareWrite() {
312
- const current = this.#db.prepare("SELECT version, checksum FROM schema_migrations ORDER BY version DESC LIMIT 1").get();
313
- if (current?.version !== this.#openedSchemaHead.version
313
+ const current = this.#db.prepare("SELECT format, major || '.' || minor AS version, checksum FROM storage_schema WHERE id=1").get();
314
+ if (current?.format !== STORAGE_FORMAT
315
+ || current.version !== this.#openedSchemaHead.version
314
316
  || current.checksum !== this.#openedSchemaHead.checksum) {
315
- throw new SqliteSchemaMigrationError(`open Store schema head ${this.#openedSchemaHead.version}/${this.#openedSchemaHead.checksum} `
317
+ throw new SqliteSchemaError(`open Store format ${STORAGE_FORMAT} found ${String(current?.format)}; schema head ${this.#openedSchemaHead.version}/${this.#openedSchemaHead.checksum} `
316
318
  + `changed to ${String(current?.version)}/${String(current?.checksum)}; `
317
- + "reopen the Store with the active Yui version before writing", "admission");
319
+ + "reopen the Store with the active Yui version before writing");
318
320
  }
319
321
  }
320
322
  #bumpRevision() {
@@ -1754,7 +1756,7 @@ export class SqliteTaskStore {
1754
1756
  // -- active turns ----------------------------------------------------------
1755
1757
  #saveActiveRun(taskId, pointer, runId) {
1756
1758
  this.#mutate(() => {
1757
- const payload = this.#json({ schemaVersion: 3, runId });
1759
+ const payload = this.#json({ schemaVersion: 1, runId });
1758
1760
  this.#db.prepare(`INSERT INTO active_turns (task_id, pointer, turn_id, payload, updated_at) VALUES (?, ?, ?, ?, ?)
1759
1761
  ON CONFLICT(task_id, pointer) DO UPDATE SET turn_id = excluded.turn_id, payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, pointer, runId, payload, this.#now());
1760
1762
  });
@@ -1,8 +1,8 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import Database from "better-sqlite3";
4
- import { inspectSqliteSchemaMigrations, storageMigrationPlan } from "./sqliteSchema.js";
5
- import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "./storageVersions.js";
4
+ import { inspectSqliteSchema, storageMinorUpgradePlan, SqliteSchemaError } from "./sqliteSchema.js";
5
+ import { compareStorageVersions, CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "./storageVersions.js";
6
6
  export { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "./storageVersions.js";
7
7
  export const CURRENT_DATABASE_FILENAME = "yui.db";
8
8
  export class StorageSchemaError extends Error {
@@ -47,7 +47,7 @@ export function inspectStorageSchema(rootDir) {
47
47
  return invalid(databasePath, error);
48
48
  }
49
49
  try {
50
- const migration = inspectSqliteSchemaMigrations(database);
50
+ const migration = inspectSqliteSchema(database);
51
51
  const fields = {
52
52
  currentVersion: migration.currentVersion,
53
53
  latestVersion: CURRENT_STORAGE_VERSION,
@@ -57,13 +57,13 @@ export function inspectStorageSchema(rootDir) {
57
57
  if (migration.currentVersion === CURRENT_STORAGE_VERSION) {
58
58
  return { status: "current", ...fields };
59
59
  }
60
- if (migration.currentVersion > CURRENT_STORAGE_VERSION) {
60
+ if (compareStorageVersions(migration.currentVersion, CURRENT_STORAGE_VERSION) > 0) {
61
61
  return { status: "unsupported", direction: "newer", ...fields };
62
62
  }
63
- if (migration.currentVersion < MIN_SUPPORTED_STORAGE_VERSION) {
63
+ if (compareStorageVersions(migration.currentVersion, MIN_SUPPORTED_STORAGE_VERSION) < 0) {
64
64
  return { status: "unsupported", direction: "older", ...fields };
65
65
  }
66
- const plan = storageMigrationPlan(migration.currentVersion);
66
+ const plan = storageMinorUpgradePlan(migration.currentVersion);
67
67
  if (plan === null) {
68
68
  return {
69
69
  status: "invalid",
@@ -114,6 +114,7 @@ function invalid(databasePath, error) {
114
114
  latestVersion: CURRENT_STORAGE_VERSION,
115
115
  minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
116
116
  databasePath,
117
- detail: error instanceof Error ? error.message : String(error)
117
+ detail: error instanceof Error ? error.message : String(error),
118
+ reason: error instanceof SqliteSchemaError && error.code === "STORAGE_FORMAT_UNSUPPORTED" ? "format" : "corruption"
118
119
  };
119
120
  }
@@ -1,16 +1,23 @@
1
- /**
2
- * The one durable compatibility axis for a Yui Home.
3
- *
4
- * Version 1 is the clean baseline introduced by Yui 0.15.0: SQLite's migration
5
- * ledger is authoritative and the historical layout/aggregate/record-version
6
- * manifest is not part of the active Home. Pre-0.15.0 Homes are outside this
7
- * compatibility line and remain untouched.
8
- *
9
- * Runtime code reads only {@link CURRENT_STORAGE_VERSION}. The explicit
10
- * upgrade boundary carries every forward migration from
11
- * {@link MIN_SUPPORTED_STORAGE_VERSION} to the current version so one target
12
- * CLI can upgrade any valid Home in that interval without installing
13
- * intermediate Yui releases.
14
- */
15
- export const MIN_SUPPORTED_STORAGE_VERSION = 1;
16
- export const CURRENT_STORAGE_VERSION = 37;
1
+ export const CURRENT_STORAGE_VERSION = "1.0";
2
+ export const MIN_SUPPORTED_STORAGE_VERSION = "1.0";
3
+ export const STORAGE_FORMAT = "yui-home";
4
+ export function isStorageVersion(value) {
5
+ if (typeof value !== "string" || !/^[1-9]\d*\.(0|[1-9]\d*)$/u.test(value))
6
+ return false;
7
+ return value.split(".").every(part => Number.isSafeInteger(Number(part)));
8
+ }
9
+ export function storageVersionParts(value) {
10
+ if (!isStorageVersion(value))
11
+ throw new Error(`Invalid storage version: ${String(value)}.`);
12
+ const [major, minor] = value.split(".").map(Number);
13
+ return { major: major, minor: minor };
14
+ }
15
+ export function compareStorageVersions(left, right) {
16
+ const a = storageVersionParts(left), b = storageVersionParts(right);
17
+ return Math.sign(a.major - b.major || a.minor - b.minor);
18
+ }
19
+ /** Cross-major conversion is never an ordinary update, even if explicitly pinned. */
20
+ export function isMinorStorageUpgrade(from, to) {
21
+ const a = storageVersionParts(from), b = storageVersionParts(to);
22
+ return a.major === b.major && b.minor > a.minor;
23
+ }
@@ -9,30 +9,10 @@ import { validateReleaseWorkflow } from "../release/releaseWorkflow.js";
9
9
  import { validateReviewConfig } from "../review/reviewConfig.js";
10
10
  import { validatePublicationReference } from "../task/publicationReference.js";
11
11
  import { validateTaskRecordReference } from "../task/taskRecordReference.js";
12
- export const CURRENT_CONFIG_SCHEMA_VERSION = 6;
13
- /** Current SQLite payload-family versions owned by this storage boundary. */
14
- export const CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION = 2;
15
- export const CURRENT_PROJECT_SCHEMA_VERSION = 6;
16
- export const CURRENT_AGENT_PROFILE_SCHEMA_VERSION = 3;
17
- export const CURRENT_GLOBAL_ROLE_SCHEMA_VERSION = 3;
18
- export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION = 5;
19
- export const CURRENT_TASK_SCHEMA_VERSION = 7;
20
- export const CURRENT_TASK_BRIEF_SCHEMA_VERSION = 2;
21
- export const CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION = 1;
22
- export const CURRENT_TASK_ROLE_SCHEMA_VERSION = 4;
23
- export const CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION = 2;
24
- export const CURRENT_WORK_ITEM_SCHEMA_VERSION = 15;
25
- export const CURRENT_REVIEW_ROUND_SCHEMA_VERSION = 8;
26
- export const CURRENT_CHANGE_SET_SCHEMA_VERSION = 4;
27
- export const CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION = 6;
28
- export const CURRENT_MESSAGE_SCHEMA_VERSION = 3;
29
- export const CURRENT_INPUT_REQUEST_SCHEMA_VERSION = 3;
30
- export const CURRENT_DECISION_SCHEMA_VERSION = 1;
31
- export const CURRENT_MILESTONE_SCHEMA_VERSION = 2;
32
- export const CURRENT_EVENT_SCHEMA_VERSION = 2;
33
- export const CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION = 2;
12
+ export const CURRENT_CONFIG_SCHEMA_VERSION = 1;
13
+ export const CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION = 1;
34
14
  export const CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION = 1;
35
- export const CURRENT_WORK_MAILBOX_SCHEMA_VERSION = 5;
15
+ export const CURRENT_WORK_MAILBOX_SCHEMA_VERSION = 1;
36
16
  export const CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION = 1;
37
17
  export const CURRENT_PENDING_WAKEUP_SCHEMA_VERSION = 1;
38
18
  export const COMPLETION_SHELLS = ["bash", "zsh", "fish"];
@@ -62,12 +42,6 @@ export function executionLaneActiveRunKeyParts(key) {
62
42
  return null;
63
43
  }
64
44
  }
65
- /**
66
- * Nested-record versions consumed by the current SQLite payload validators.
67
- */
68
- export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION = 12;
69
- export const CURRENT_RUN_SCHEMA_VERSION = 5;
70
- export const CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION = 1;
71
45
  export class StorageRecordError extends Error {
72
46
  constructor(message) { super(message); this.name = "StorageRecordError"; }
73
47
  }
@@ -5,17 +5,11 @@ import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation } from "../../
5
5
  import { RUNTIME_PROCESS_EXIT_TASK_EVENT, validateRuntimeProcessExitObservation } from "../../runtime/processExitObservation.js";
6
6
  import { SqliteTaskStore } from "../sqliteStore.js";
7
7
  import { storageBackupRoot } from "../homeLayout.js";
8
- import { preflightUnifyHomeLayout } from "../migrations/unifyHomeLayout.js";
9
- import { preflightCollapseWorktreeLayout } from "../migrations/collapseWorktreeLayout.js";
10
- import { preflightNotificationOnlyWakes } from "../migrations/notificationOnlyWakes.js";
11
- import { preflightVerificationPolicy } from "../migrations/verificationPolicy.js";
12
- import { preflightCurrentInputContract } from "../migrations/currentInputContract.js";
13
- import { preflightCurrentRuntimeContract } from "../migrations/currentRuntimeContract.js";
14
- import { migrateSqliteSchema, storageMigrationPlan } from "../sqliteSchema.js";
8
+ import { applySqliteMinorUpgrades, storageMinorUpgradePlan } from "../sqliteSchema.js";
15
9
  import { CURRENT_DATABASE_FILENAME, inspectStorageSchema } from "../storageSchema.js";
16
10
  import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "../storageVersions.js";
17
11
  /**
18
- * Upgrade one valid Home through the complete linear migration chain.
12
+ * Upgrade only recognized minor versions within the current Home major.
19
13
  *
20
14
  * Supported earlier versions are readable only here. Ordinary stores still
21
15
  * admit exactly {@link CURRENT_STORAGE_VERSION}; no old-shape normalizer or
@@ -29,6 +23,13 @@ export async function runStorageUpgrade(options) {
29
23
  return blocked({ ...corruptedClassification("Storage is not initialized."), uninitialized: true }, "uninitialized", "Yui storage is not initialized for this Home.", "Run `yui setup` with a new Home.");
30
24
  }
31
25
  if (state.status === "invalid") {
26
+ if (state.reason === "format") {
27
+ return blocked({
28
+ classification: { verdict: "UNSUPPORTED_FORMAT", status: "unsupported", detail: state.detail },
29
+ currentStorageVersion: CURRENT_STORAGE_VERSION,
30
+ minimumSupportedStorageVersion: MIN_SUPPORTED_STORAGE_VERSION
31
+ }, "unsupported", state.detail, "Keep this Home unchanged and use its matching release or the independent explicit conversion tool. Ordinary upgrades never cross storage formats.");
32
+ }
32
33
  return blocked(corruptedClassification(state.detail), "corruption", `Storage is invalid: ${state.detail}`, "Preserve this Home for diagnosis and restore it from a known-good backup.");
33
34
  }
34
35
  if (state.status === "unsupported") {
@@ -68,27 +69,12 @@ export async function runStorageUpgrade(options) {
68
69
  }
69
70
  };
70
71
  }
71
- const plan = storageMigrationPlan(state.currentVersion);
72
+ const plan = storageMinorUpgradePlan(state.currentVersion);
72
73
  if (plan === null) {
73
74
  return blocked(corruptedClassification(`No complete migration path exists from ${state.currentVersion} `
74
- + `to ${CURRENT_STORAGE_VERSION}.`), "corruption", "The storage migration registry is incomplete.", "Install a Yui release that carries the complete migration chain.");
75
+ + `to ${CURRENT_STORAGE_VERSION}.`), "corruption", "The storage migration registry is incomplete.", "Use a release with an explicit same-major minor upgrade path; cross-major conversion is separate.");
75
76
  }
76
77
  const classification = migratableClassification(state.currentVersion);
77
- // Genuine pre-migration safety check, shared by ALL migrating modes. The
78
- // unify-home data migration (18->19) physically relocates on-disk trees and
79
- // rewrites live launch pointers, so before any mode proceeds we run its READ-
80
- // ONLY preflight and refuse up front on the same conditions the migration would
81
- // throw on inside the apply transaction: a queued/running Job bound under a
82
- // relocating root, or a conflicting relocation target. The migration is applied
83
- // offline (after the operator has stopped this Home's writers), so the preflight
84
- // does not scan for live processes. Surfacing these here — before the Controller
85
- // is stopped or a backup is taken — is what makes `--dry-run` and the updater's
86
- // `--update-preflight` a genuine readiness signal rather than a mere list of
87
- // schema steps, while the in-transaction checks remain the authoritative guard
88
- // against anything that starts after this point.
89
- const migrationBlocked = preflightMigrationBlockers(options.home, plan, classification);
90
- if (migrationBlocked !== null)
91
- return migrationBlocked;
92
78
  if (options.mode === "update-preflight") {
93
79
  return {
94
80
  outcome: "update-preflight",
@@ -134,7 +120,7 @@ export async function runStorageUpgrade(options) {
134
120
  database.pragma("synchronous = FULL");
135
121
  database.pragma("foreign_keys = ON");
136
122
  database.pragma("busy_timeout = 5000");
137
- migrateSqliteSchema(database, { mode: "apply" });
123
+ applySqliteMinorUpgrades(database);
138
124
  migrationCommitted = true;
139
125
  }
140
126
  finally {
@@ -191,7 +177,7 @@ export async function runStorageUpgrade(options) {
191
177
  };
192
178
  }
193
179
  function validateCurrentStore(home) {
194
- const store = new SqliteTaskStore(home);
180
+ const store = new SqliteTaskStore(home, { readonly: true });
195
181
  try {
196
182
  store.validateCurrentRecords();
197
183
  for (const taskId of store.listTasks().map(({ id }) => id)) {
@@ -274,7 +260,7 @@ function unsupportedClassification(state) {
274
260
  const action = future
275
261
  ? "Use a newer Yui release."
276
262
  : "Preserve this Home for use with its matching historical Yui release, "
277
- + "or initialize a new Home with Yui 0.15.0 or later.";
263
+ + "or use the independent explicit converter for a different storage major.";
278
264
  return {
279
265
  classification: {
280
266
  verdict: "NEEDS_NEW_VERSION",
@@ -310,80 +296,6 @@ function blocked(classification, stage, message, action) {
310
296
  sceneUnchanged: true
311
297
  };
312
298
  }
313
- /**
314
- * Run each data migration's registered READ-ONLY preflight against the
315
- * current (pre-upgrade) database and, when any finds a blocker, return a
316
- * `blocked` result the caller surfaces before touching the Controller or the
317
- * Home. Returns `null` when the plan carries no registered readiness check,
318
- * or when every preflight is clear.
319
- *
320
- * Both the 23->24 unify and the 24->25 collapse migrations physically relocate
321
- * managed worktrees and share the same blocker shape (in-flight Job, conflicting
322
- * relocation target). When a Home is upgraded across both in one run, their
323
- * blockers are aggregated so the operator sees their readiness problems together.
324
- * Notification retirement separately rejects unsettled execution references.
325
- *
326
- * The database is opened read-only so the checks cannot mutate the authoritative
327
- * store, and the handle is always closed. An unexpected failure to evaluate a
328
- * preflight is itself a fail-closed blocker: we must not advance to an
329
- * irreversible migration on an unverifiable readiness signal.
330
- */
331
- function preflightMigrationBlockers(home, plan, classification) {
332
- const relocatesUnify = plan.some((step) => step.name === "unify-home-layout");
333
- const relocatesCollapse = plan.some((step) => step.name === "collapse-worktree-layout");
334
- const retiresRunWakes = plan.some((step) => step.name === "notification-only-wakes");
335
- const changesVerification = plan.some(step => step.name === "advisory-and-verification-policy");
336
- const changesInput = plan.some(step => step.name === "current-input-contract");
337
- const retiresRuntime = plan.some(step => step.name === "current-verification-and-owner-contract");
338
- if (!relocatesUnify && !relocatesCollapse && !retiresRunWakes && !changesVerification && !changesInput && !retiresRuntime)
339
- return null;
340
- let blockers;
341
- try {
342
- const database = new Database(join(home, CURRENT_DATABASE_FILENAME), {
343
- readonly: true,
344
- fileMustExist: true
345
- });
346
- try {
347
- blockers = [];
348
- if (relocatesUnify)
349
- blockers.push(...preflightUnifyHomeLayout(database).blockers);
350
- if (relocatesCollapse)
351
- blockers.push(...preflightCollapseWorktreeLayout(database).blockers);
352
- if (retiresRunWakes)
353
- preflightNotificationOnlyWakes(database);
354
- if (changesVerification)
355
- preflightVerificationPolicy(database);
356
- if (changesInput)
357
- preflightCurrentInputContract(database);
358
- if (retiresRuntime)
359
- preflightCurrentRuntimeContract(database);
360
- }
361
- finally {
362
- database.close();
363
- }
364
- }
365
- catch (error) {
366
- return {
367
- ...blocked(classification, "in-flight", `The storage migration readiness check could not be completed: ${messageOf(error)}`, "Resolve the named execution or resource boundary, preserving original input and results. "
368
- + "Confirm the relevant Runs and Jobs are settled, then rerun the upgrade.")
369
- };
370
- }
371
- if (blockers.length === 0)
372
- return null;
373
- return {
374
- outcome: "blocked",
375
- stage: "in-flight",
376
- message: "Refusing to migrate: the Home is not safe to relocate under the current layout yet. "
377
- + blockers.map((entry) => entry.detail).join("; ") + ".",
378
- action: "Resolve the reported condition(s) — let a queued/running Job finish or cancel it, and "
379
- + "clear any conflicting relocation target — then rerun the upgrade. The migration is applied "
380
- + "offline, so first confirm the Controller and any execution writers for this Home are "
381
- + "stopped. The authoritative Home is unchanged.",
382
- blockers: blockers.map((entry) => ({ reason: entry.reason, detail: entry.detail })),
383
- classification,
384
- sceneUnchanged: true
385
- };
386
- }
387
299
  function messageOf(error) {
388
300
  return error instanceof Error ? error.message : String(error);
389
301
  }
package/dist/task/task.js CHANGED
@@ -3,7 +3,7 @@ import { cancelTaskActivationRequest, recordTaskActivationEvidence, validateTask
3
3
  export function createTask(id, title, now, metadata = {}) {
4
4
  const timestamp = now.toISOString();
5
5
  return {
6
- schemaVersion: 7,
6
+ schemaVersion: 1,
7
7
  id: requireSafeIdentity(id, "Task id"),
8
8
  title: requireText(title, "Task title"),
9
9
  ...cloneMetadata(metadata),
@@ -16,7 +16,7 @@ export function createTask(id, title, now, metadata = {}) {
16
16
  /**
17
17
  * Persist the Task's durable workspace identity. The identity is immutable on
18
18
  * the Task: a second binding is rejected so restart/reconcile/attach can never
19
- * silently adopt a different (foreign or legacy) ref namespace.
19
+ * silently adopt a different workspace ref namespace.
20
20
  */
21
21
  export function bindTaskWorkspaceIdentity(task, identity, now) {
22
22
  validateTask(task);
@@ -409,8 +409,19 @@ export function startTaskExecution(task, now) {
409
409
  });
410
410
  }
411
411
  export function validateTask(task) {
412
- if (task.schemaVersion !== 7)
413
- throw new Error("Task must use schemaVersion 7.");
412
+ if (task.schemaVersion !== 1)
413
+ throw new Error("Task must use schemaVersion 1.");
414
+ const fields = [
415
+ "schemaVersion", "id", "title", "type", "description", "priority", "tags", "dueAt",
416
+ "projectBindings", "cwd", "workspaceIdentity", "status", "executionGate", "activationRequest",
417
+ "settledActivationRequests", "completedAt", "completedBy", "completionSummary", "completionArtifactRefs",
418
+ "retiredAt", "retiredBy", "retirementSummary", "retirementIsolation", "replacementTaskId",
419
+ "archivedAt", "archivedBy", "archiveReason", "archiveSummary", "createdAt", "updatedAt"
420
+ ];
421
+ for (const field of Object.keys(task)) {
422
+ if (!fields.includes(field))
423
+ throw new Error(`Task contains an unsupported field: ${field}.`);
424
+ }
414
425
  requireSafeIdentity(task.id, "Task id");
415
426
  requireText(task.title, "Task title");
416
427
  if (task.type !== undefined)
@@ -492,9 +503,6 @@ export function validateTask(task) {
492
503
  normalizeProjectBindings(task.projectBindings);
493
504
  if (task.cwd !== undefined)
494
505
  requireText(task.cwd, "Task workspace");
495
- if (Object.hasOwn(task, "legacyDeliveryPath")) {
496
- throw new Error("Task contains the removed delivery-path field.");
497
- }
498
506
  const completionFields = [task.completedAt, task.completedBy, task.completionSummary];
499
507
  const hasAnyCompletion = completionFields.some((value) => value !== undefined);
500
508
  const hasAllCompletion = completionFields.every((value) => value !== undefined);
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { isDeepStrictEqual } from "node:util";
3
- import { requireIdentity, requireTimestamp } from "../domain/validation.js";
3
+ import { requireIdentity, requireTimestamp, requireKnownFields } from "../domain/validation.js";
4
4
  import { recordOperationEvidence, validateOperationFacts } from "../kernel/operationFacts.js";
5
5
  export const TASK_ACTIVATION_IMPLEMENTATION = Object.freeze({
6
6
  id: "yui:task-activation",
@@ -229,6 +229,10 @@ export function validateTaskActivationRequest(request) {
229
229
  if (request.schemaVersion !== 1) {
230
230
  throw new Error("Activation request must use schemaVersion 1.");
231
231
  }
232
+ requireKnownFields(request, [
233
+ "schemaVersion", "operation", "startMode", "afterPlanningRun", "environmentPlan", "disposition",
234
+ "preparationId", "outcome", "requestedAt", "updatedAt"
235
+ ], "Activation request");
232
236
  validateOperationFacts(request.operation);
233
237
  if (request.operation.capability !== TASK_ACTIVATION_CAPABILITY) {
234
238
  throw new Error("Activation request capability is invalid.");
@@ -236,9 +240,6 @@ export function validateTaskActivationRequest(request) {
236
240
  if (!["immediate", "after-planning-turn"].includes(request.startMode)) {
237
241
  throw new Error(`Activation start mode is invalid: ${String(request.startMode)}.`);
238
242
  }
239
- if (Object.hasOwn(request, "origin")) {
240
- throw new Error("Activation contains retired origin metadata; use the storage upgrade boundary.");
241
- }
242
243
  if ((request.startMode === "after-planning-turn")
243
244
  !== (request.afterPlanningRun !== undefined)) {
244
245
  throw new Error("Activation deferral must name exactly the planning Turn it waits for.");
@@ -2,7 +2,7 @@ import Database from "better-sqlite3";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { CURRENT_DATABASE_FILENAME as COMMITTED_DATABASE_FILENAME } from "../storage/currentTaskStore.js";
5
- import { migrateSqliteSchema } from "../storage/sqliteSchema.js";
5
+ import { validateSqliteSchema } from "../storage/sqliteSchema.js";
6
6
  import { AsyncTaskStoreClient } from "../storage/storeRpc.js";
7
7
  import { DEFAULT_RUN_CAP, DEFAULT_TERMINAL_KEEP } from "./telemetryConfig.js";
8
8
  /**
@@ -251,13 +251,13 @@ export class SqliteTelemetryStore {
251
251
  throw new Error(`Telemetry database not found: ${this.#path}`);
252
252
  }
253
253
  const db = opening = new Database(this.#path);
254
+ validateSqliteSchema(db);
254
255
  db.pragma("journal_mode = WAL");
255
256
  db.pragma("synchronous = FULL");
256
257
  db.pragma("foreign_keys = ON");
257
258
  // Cold diagnostic reads/retention must not wait behind a semantic writer.
258
259
  db.pragma("busy_timeout = 0");
259
260
  db.pragma("wal_autocheckpoint = 1000");
260
- migrateSqliteSchema(db, { mode: "validate" });
261
261
  this.#db = db;
262
262
  return db;
263
263
  }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { isAbsolute } from "node:path";
3
- import { requireIdentity, requirePositiveInteger, requireText, requireTimestamp } from "../domain/validation.js";
3
+ import { requireIdentity, requireKnownFields, requirePositiveInteger, requireText, requireTimestamp } from "../domain/validation.js";
4
4
  /**
5
5
  * Issue 08: the reusable exact-SHA gate evidence record.
6
6
  *
@@ -80,6 +80,10 @@ export function recordGateArtifactReuse(artifact, now) {
80
80
  });
81
81
  }
82
82
  export function validateGateArtifact(artifact) {
83
+ requireKnownFields(artifact, [
84
+ "schemaVersion", "key", "projectId", "level", "commit", "planId", "planVersion", "planDigest", "toolchainDigest",
85
+ "boundary", "steps", "generator", "status", "outcome", "reuseCount", "createdAt", "completedAt", "lastUsedAt"
86
+ ], "GateArtifact");
83
87
  if (artifact.schemaVersion !== GATE_ARTIFACT_SCHEMA_VERSION) {
84
88
  throw new Error(`GateArtifact must use schemaVersion ${GATE_ARTIFACT_SCHEMA_VERSION}.`);
85
89
  }
@@ -139,8 +143,6 @@ export function validateGateArtifact(artifact) {
139
143
  throw new Error(`GateArtifact step ${step.name} logBytes is invalid.`);
140
144
  }
141
145
  }
142
- if (Object.hasOwn(artifact, "potentialReuseCount"))
143
- throw new Error("GateArtifact contains retired shadow metrics.");
144
146
  requirePositiveInteger(artifact.reuseCount + 1, "GateArtifact reuseCount");
145
147
  requireTimestamp(artifact.createdAt, "GateArtifact createdAt");
146
148
  requireTimestamp(artifact.lastUsedAt, "GateArtifact lastUsedAt");
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { isAbsolute, relative, resolve, sep } from "node:path";
3
- import { requireIdentity, requirePositiveInteger, requireText } from "../domain/validation.js";
3
+ import { requireIdentity, requireKnownFields, requirePositiveInteger, requireText } from "../domain/validation.js";
4
4
  /**
5
5
  * Issue 08: the per-Project VerificationPlan contract.
6
6
  *
@@ -11,7 +11,7 @@ import { requireIdentity, requirePositiveInteger, requireText } from "../domain/
11
11
  * explicit `--check <shell>` path, which is marked unstructured and is not
12
12
  * reusable across stages.
13
13
  */
14
- export const VERIFICATION_PLAN_SCHEMA_VERSION = 2;
14
+ export const VERIFICATION_PLAN_SCHEMA_VERSION = 1;
15
15
  export const VERIFICATION_PLAN_KIND = "verification-plan";
16
16
  /** The reserved knowledge marker that carries a Project's VerificationPlan. */
17
17
  export const VERIFICATION_PLAN_KNOWLEDGE_KIND = VERIFICATION_PLAN_KIND;
@@ -27,10 +27,9 @@ export function normalizeVerificationPlan(raw) {
27
27
  if (schemaVersion !== VERIFICATION_PLAN_SCHEMA_VERSION) {
28
28
  throw new Error(`VerificationPlan schemaVersion must be ${VERIFICATION_PLAN_SCHEMA_VERSION}.`);
29
29
  }
30
- if (Object.hasOwn(record, "mode"))
31
- throw new Error("VerificationPlan mode is retired; request an explicit rerun on the operation.");
32
- if (Object.hasOwn(record, "l1"))
33
- throw new Error("VerificationPlan l1 is retired; declare current checks in l2.");
30
+ requireKnownFields(record, [
31
+ "schemaVersion", "kind", "id", "version", "toolchain", "bootstrap", "l2", "l3", "excludedRealResourceChecks", "artifactTtlDays"
32
+ ], "VerificationPlan");
34
33
  const plan = {
35
34
  schemaVersion: VERIFICATION_PLAN_SCHEMA_VERSION,
36
35
  kind: VERIFICATION_PLAN_KIND,
@@ -145,7 +144,7 @@ export function verificationPlanDigest(plan) {
145
144
  const canonical = canonicalJson({
146
145
  // Old artifacts may have skipped shell commands or run in the wrong cwd.
147
146
  // Keep that history, but never reuse it as proof under corrected semantics.
148
- executionContract: "workspace-argv-or-shell/clean-candidate/l2-only/v5",
147
+ executionContract: "yui-baseline/clean-candidate/l2-only/v1",
149
148
  id: plan.id,
150
149
  version: plan.version,
151
150
  toolchain: plan.toolchain,
@@ -12,7 +12,7 @@ const TERMINAL_STATUSES = [
12
12
  export function createWorkItem(id, taskId, input, now) {
13
13
  const timestamp = now.toISOString();
14
14
  return validateWorkItem({
15
- schemaVersion: 15,
15
+ schemaVersion: 1,
16
16
  id: requireIdentity(id, "Work Item id"),
17
17
  taskId: requireIdentity(taskId, "Task id"),
18
18
  title: requireText(input.title, "Work item title"),
@@ -90,7 +90,7 @@ export function submitWorkItemCandidate(workItem, input, now) {
90
90
  const revision = workItem.revision + 1;
91
91
  const sequence = workItem.candidates.length + 1;
92
92
  const candidate = validateWorkItemCandidate({
93
- schemaVersion: 3,
93
+ schemaVersion: 1,
94
94
  id: `candidate-${sequence}`,
95
95
  taskId: workItem.taskId,
96
96
  workItemId: workItem.id,
@@ -321,8 +321,8 @@ export function validateWorkItem(workItem) {
321
321
  "acceptedCandidateId",
322
322
  "currentCandidateId"
323
323
  ], "WorkItem");
324
- if (workItem.schemaVersion !== 15)
325
- throw new Error("WorkItem must use schemaVersion 15.");
324
+ if (workItem.schemaVersion !== 1)
325
+ throw new Error("WorkItem must use schemaVersion 1.");
326
326
  validateTaskRecordReference({ taskId: workItem.taskId, localId: workItem.id }, "workItem");
327
327
  requireIdentity(workItem.taskId, "Task id");
328
328
  requireText(workItem.title, "Work item title");
@@ -470,8 +470,8 @@ export function validateWorkItemCandidate(candidate) {
470
470
  if (typeof candidate !== "object" || candidate === null) {
471
471
  throw new Error("Work Item candidate is required.");
472
472
  }
473
- if (candidate.schemaVersion !== 3) {
474
- throw new Error("Work Item candidate must use schemaVersion 3.");
473
+ if (candidate.schemaVersion !== 1) {
474
+ throw new Error("Work Item candidate must use schemaVersion 1.");
475
475
  }
476
476
  requireIdentity(candidate.taskId, "Work Item candidate Task id");
477
477
  validateTaskRecordReference({
@@ -36,10 +36,7 @@ export function cleanupFailure(reason, detail, expected, observed, status = "blo
36
36
  throw new CleanupInspectionError([{ resource: "git-workspace", reason, status,
37
37
  detail, expected, observed, sources: [], actions: [] }]);
38
38
  }
39
- /** Describe exact differences without disclosing paths outside this Task.
40
- * Legacy locations are displayed only for this Task's exact branch identity;
41
- * displaying a historical location is not accepting it as migration proof.
42
- */
39
+ /** Describe exact differences without disclosing paths outside this Task. */
43
40
  export function workspacePathValue(workspace, path) {
44
41
  const inside = (root) => {
45
42
  const value = relative(root, path);
@@ -52,11 +49,6 @@ export function workspacePathValue(workspace, path) {
52
49
  const taskPath = inside(join(home, "workspaces", "tasks", workspace.owner.taskId));
53
50
  if (taskPath !== undefined)
54
51
  return `<task>/${taskPath}`;
55
- const legacyPath = inside(join(home, "workspaces", "worktree"));
56
- const parts = legacyPath?.split("/");
57
- const taskSegment = workspace.entries.find(e => e.access === "write")?.branch.split("/")[1];
58
- if (parts?.length === 3 && parts[1] === taskSegment)
59
- return `<legacy-worktree>/${legacyPath}`;
60
52
  }
61
53
  const local = inside(workspace.root);
62
54
  return local === undefined ? "[outside authorized Task paths]" : `<owner>/${local}`;