@zq-silk/yui 0.14.2 → 0.15.1

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 (44) hide show
  1. package/ARCHITECTURE.md +27 -12
  2. package/README.md +85 -61
  3. package/dist/cli/commandCatalog.js +6 -6
  4. package/dist/cli/updateCommand.js +17 -9
  5. package/dist/cli/updateOrchestrator.js +81 -15
  6. package/dist/cli/updatePorts.js +72 -10
  7. package/dist/cli/upgradeCommand.js +104 -19
  8. package/dist/cli.js +2 -2
  9. package/dist/commands/agentCommands.js +13 -6
  10. package/dist/commands/controllerCommands.js +1 -1
  11. package/dist/commands/globalRoleCommands.js +11 -3
  12. package/dist/commands/roleConfiguration.js +7 -0
  13. package/dist/commands/roleRuntimeGuard.js +30 -0
  14. package/dist/commands/taskCommands.js +10 -3
  15. package/dist/controller/fileSchedulerStoreAdapter.js +4 -4
  16. package/dist/controller/runtime.js +11 -25
  17. package/dist/controller/runtimeLaunchCoordinator.js +9 -30
  18. package/dist/controller/sessionNotify.js +5 -0
  19. package/dist/core/controllerServer.js +5 -5
  20. package/dist/doctor/doctor.js +37 -14
  21. package/dist/executor/agentExecutor.js +8 -11
  22. package/dist/executor/effectiveLaunch.js +34 -17
  23. package/dist/executor/fileRoleLaunchPlanner.js +11 -8
  24. package/dist/observability/runtimeIdentity.js +48 -50
  25. package/dist/release/runtimeRelease.js +9 -1
  26. package/dist/runtime/agentHost.js +7 -0
  27. package/dist/runtime/codexInteractiveHost.js +191 -0
  28. package/dist/runtime/exactControlPlane.js +20 -29
  29. package/dist/runtime/structuredProviderHost.js +35 -0
  30. package/dist/runtime/tmuxAdapters.js +51 -9
  31. package/dist/scheduler/activeRoleTurnDelivery.js +4 -4
  32. package/dist/scheduler/leaderWakeupProcessor.js +3 -4
  33. package/dist/storage/currentTaskStore.js +6 -4
  34. package/dist/storage/sqliteSchema.js +134 -59
  35. package/dist/storage/sqliteStore.js +7 -5
  36. package/dist/storage/storageSchema.js +92 -223
  37. package/dist/storage/storageVersions.js +12 -16
  38. package/dist/storage/upgrade/upgradeOrchestrator.js +224 -62
  39. package/dist/tmux/tmuxManager.js +43 -28
  40. package/dist/version.js +3 -3
  41. package/docs/task-local-identity.md +9 -9
  42. package/i18n/README.zh-CN.md +33 -17
  43. package/package.json +1 -1
  44. package/dist/storage/upgrade/recordVersions.js +0 -82
@@ -1,10 +1,10 @@
1
- import { readFileSync } from "node:fs";
1
+ import { existsSync, readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { writeTextFileAtomically } from "./durableFile.js";
4
- import { CURRENT_AGGREGATE_SCHEMA_VERSION, CURRENT_STORAGE_LAYOUT_VERSION } from "./storageVersions.js";
5
- import { currentRecordVersions } from "./upgrade/recordVersions.js";
6
- export { CURRENT_STORAGE_LAYOUT_VERSION, CURRENT_AGGREGATE_SCHEMA_VERSION };
7
- export const STORAGE_SCHEMA_FILE = "schema.json";
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";
6
+ export { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "./storageVersions.js";
7
+ export const CURRENT_DATABASE_FILENAME = "yui.db";
8
8
  export class StorageSchemaError extends Error {
9
9
  code;
10
10
  constructor(code, message, options) {
@@ -13,250 +13,119 @@ export class StorageSchemaError extends Error {
13
13
  this.code = code;
14
14
  }
15
15
  }
16
+ /**
17
+ * Inspect the one authoritative SQLite migration head without changing it.
18
+ *
19
+ * `schema.json` and `state.json` are recognized only to prevent setup from
20
+ * overwriting an old Home whose SQLite authority is missing. They are not
21
+ * version authorities for current Homes.
22
+ */
16
23
  export function inspectStorageSchema(rootDir) {
17
- const manifestPath = join(rootDir, STORAGE_SCHEMA_FILE);
18
- const raw = readOptionalText(manifestPath);
19
- if (raw === null) {
24
+ const databasePath = join(rootDir, CURRENT_DATABASE_FILENAME);
25
+ const hasPreBaselineEvidence = existsSync(join(rootDir, "schema.json")) || existsSync(join(rootDir, "state.json"));
26
+ if (hasPreBaselineEvidence) {
20
27
  return {
21
- status: "uninitialized",
22
- latestVersion: CURRENT_STORAGE_LAYOUT_VERSION,
23
- latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
24
- latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
25
- manifestPath
28
+ status: "unsupported",
29
+ direction: "older",
30
+ currentVersion: 0,
31
+ latestVersion: CURRENT_STORAGE_VERSION,
32
+ minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
33
+ databasePath
26
34
  };
27
35
  }
28
- let manifest;
29
- try {
30
- manifest = parseStorageManifest(raw);
31
- }
32
- catch (error) {
36
+ if (!existsSync(databasePath)) {
37
+ if (existsSync(rootDir)) {
38
+ try {
39
+ if (readdirSync(rootDir).length > 0) {
40
+ return invalid(databasePath, new Error("The authoritative yui.db is missing from a non-empty Home."));
41
+ }
42
+ }
43
+ catch (error) {
44
+ return invalid(databasePath, error);
45
+ }
46
+ }
33
47
  return {
34
- status: "invalid",
35
- latestVersion: CURRENT_STORAGE_LAYOUT_VERSION,
36
- latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
37
- latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
38
- manifestPath,
39
- detail: error instanceof Error ? error.message : String(error)
48
+ status: "uninitialized",
49
+ latestVersion: CURRENT_STORAGE_VERSION,
50
+ minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
51
+ databasePath
40
52
  };
41
53
  }
42
- if (manifest.storageVersion !== CURRENT_STORAGE_LAYOUT_VERSION) {
43
- return {
44
- status: "unsupported",
45
- incompatibleComponent: "layout",
46
- direction: manifest.storageVersion < CURRENT_STORAGE_LAYOUT_VERSION ? "older" : "newer",
47
- currentVersion: manifest.storageVersion,
48
- latestVersion: CURRENT_STORAGE_LAYOUT_VERSION,
49
- currentLayoutVersion: manifest.storageVersion,
50
- latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
51
- currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
52
- latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
53
- manifestPath
54
- };
54
+ let database;
55
+ try {
56
+ database = new Database(databasePath, { readonly: true, fileMustExist: true });
55
57
  }
56
- if (manifest.aggregateSchemaVersion !== CURRENT_AGGREGATE_SCHEMA_VERSION) {
57
- return {
58
- status: "unsupported",
59
- incompatibleComponent: "aggregate",
60
- direction: manifest.aggregateSchemaVersion < CURRENT_AGGREGATE_SCHEMA_VERSION
61
- ? "older"
62
- : "newer",
63
- currentVersion: manifest.aggregateSchemaVersion,
64
- latestVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
65
- currentLayoutVersion: manifest.storageVersion,
66
- latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
67
- currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
68
- latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
69
- manifestPath
70
- };
58
+ catch (error) {
59
+ return invalid(databasePath, error);
71
60
  }
72
- const currentRecords = currentRecordVersions();
73
- if (manifest.recordVersions === undefined) {
74
- return {
75
- status: "unsupported",
76
- incompatibleComponent: "record",
77
- direction: "older",
78
- currentVersion: 0,
79
- latestVersion: 1,
80
- currentLayoutVersion: manifest.storageVersion,
81
- latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
82
- currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
83
- latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
84
- manifestPath
61
+ try {
62
+ const migration = inspectSqliteSchemaMigrations(database);
63
+ const fields = {
64
+ currentVersion: migration.currentVersion,
65
+ latestVersion: CURRENT_STORAGE_VERSION,
66
+ minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
67
+ databasePath
85
68
  };
86
- }
87
- for (const [kind, currentEntry] of Object.entries(currentRecords)) {
88
- const persisted = manifest.recordVersions[kind];
89
- if (persisted === undefined) {
90
- return {
91
- status: "unsupported",
92
- incompatibleComponent: "record",
93
- direction: "older",
94
- currentVersion: 0,
95
- latestVersion: currentEntry.version,
96
- currentLayoutVersion: manifest.storageVersion,
97
- latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
98
- currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
99
- latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
100
- recordFamily: kind,
101
- manifestPath
102
- };
69
+ if (migration.currentVersion === CURRENT_STORAGE_VERSION) {
70
+ return { status: "current", ...fields };
71
+ }
72
+ if (migration.currentVersion > CURRENT_STORAGE_VERSION) {
73
+ return { status: "unsupported", direction: "newer", ...fields };
103
74
  }
104
- if (persisted !== currentEntry.version) {
75
+ if (migration.currentVersion < MIN_SUPPORTED_STORAGE_VERSION) {
76
+ return { status: "unsupported", direction: "older", ...fields };
77
+ }
78
+ const plan = storageMigrationPlan(migration.currentVersion);
79
+ if (plan === null) {
105
80
  return {
106
- status: "unsupported",
107
- incompatibleComponent: "record",
108
- direction: persisted < currentEntry.version ? "older" : "newer",
109
- currentVersion: persisted,
110
- latestVersion: currentEntry.version,
111
- currentLayoutVersion: manifest.storageVersion,
112
- latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
113
- currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
114
- latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
115
- recordFamily: kind,
116
- manifestPath
81
+ status: "invalid",
82
+ latestVersion: CURRENT_STORAGE_VERSION,
83
+ minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
84
+ databasePath,
85
+ detail: `Storage migration registry has no complete path from `
86
+ + `${migration.currentVersion} to ${CURRENT_STORAGE_VERSION}.`
117
87
  };
118
88
  }
89
+ return {
90
+ status: "upgradeable",
91
+ pendingVersions: plan.map(({ toVersion }) => toVersion),
92
+ ...fields
93
+ };
119
94
  }
120
- return {
121
- status: "current",
122
- currentVersion: manifest.storageVersion,
123
- latestVersion: CURRENT_STORAGE_LAYOUT_VERSION,
124
- currentLayoutVersion: manifest.storageVersion,
125
- latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
126
- currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
127
- latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
128
- manifestPath
129
- };
130
- }
131
- export function ensureStorageSchema(rootDir, now = new Date()) {
132
- const state = inspectStorageSchema(rootDir);
133
- if (state.status === "uninitialized") {
134
- writeCurrentStorageManifest(rootDir, now);
135
- return;
95
+ catch (error) {
96
+ return invalid(databasePath, error);
136
97
  }
137
- requireInspectedSchema(state);
138
- }
139
- /** Persist the current three-axis manifest through the existing atomic-file seam. */
140
- export function writeCurrentStorageManifest(rootDir, now = new Date()) {
141
- const recordVersions = {};
142
- for (const [kind, entry] of Object.entries(currentRecordVersions())) {
143
- recordVersions[kind] = entry.version;
98
+ finally {
99
+ database.close();
144
100
  }
145
- const manifest = {
146
- schemaVersion: 1,
147
- storageVersion: CURRENT_STORAGE_LAYOUT_VERSION,
148
- aggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
149
- recordVersions,
150
- updatedAt: now.toISOString()
151
- };
152
- writeTextFileAtomically(join(rootDir, STORAGE_SCHEMA_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
153
101
  }
154
- function requireInspectedSchema(state) {
102
+ /** Require the current storage contract without normalizing historical data. */
103
+ export function requireCurrentStorageSchema(rootDir) {
104
+ const state = inspectStorageSchema(rootDir);
155
105
  switch (state.status) {
156
106
  case "current":
157
107
  return;
158
108
  case "uninitialized":
159
109
  throw new StorageSchemaError("STORAGE_UNINITIALIZED", "Yui storage is not initialized. Run `yui setup`.");
160
110
  case "invalid":
161
- throw new StorageSchemaError("STORAGE_SCHEMA_INVALID", `Invalid storage schema manifest at ${state.manifestPath}: ${state.detail}`);
111
+ throw new StorageSchemaError("STORAGE_SCHEMA_INVALID", `Invalid storage at ${state.databasePath}: ${state.detail}`);
112
+ case "upgradeable":
113
+ throw new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", `Storage version ${state.currentVersion} requires an explicit upgrade to `
114
+ + `${state.latestVersion}. Run \`yui upgrade\` or \`yui update\`.`);
162
115
  case "unsupported":
163
- throw unsupportedVersion(state.currentVersion, state.latestVersion, state.incompatibleComponent, state.recordFamily);
116
+ throw new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", state.direction === "newer"
117
+ ? `Storage version ${state.currentVersion} is newer than supported `
118
+ + `${state.latestVersion}; use a newer Yui release.`
119
+ : `Storage version ${state.currentVersion} is older than the minimum supported `
120
+ + `${state.minimumSupportedVersion}.`);
164
121
  }
165
122
  }
166
- function unsupportedVersion(current, required, component, recordFamily) {
167
- if (component === "record" && recordFamily === undefined) {
168
- return new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", "This Home predates the current record-version manifest. Preserve it for inspection and initialize a new Home.");
169
- }
170
- const label = component === "layout"
171
- ? "Storage layout"
172
- : component === "aggregate"
173
- ? "Aggregate schema"
174
- : `Record family '${recordFamily}'`;
175
- if (current < required) {
176
- return new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", `${label} ${current} is older than required ${component} version ${required}; use the matching Yui version or initialize a new Home.`);
177
- }
178
- return new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", `${label} ${current} is newer than supported ${component} version ${required}; use a newer Yui release.`);
179
- }
180
- function parseStorageManifest(raw) {
181
- const value = parseJsonObject(raw, "Storage schema manifest");
182
- return parseStorageSchemaManifest(value);
183
- }
184
- /** Strictly parse one already-decoded manifest object through the shared contract. */
185
- export function parseStorageSchemaManifest(value) {
186
- if (!isRecord(value))
187
- throw new Error("Storage schema manifest must be an object");
188
- assertKeys(value, ["schemaVersion", "storageVersion", "aggregateSchemaVersion", "updatedAt"], ["recordVersions"], "Storage schema manifest");
189
- if (value.schemaVersion !== 1)
190
- throw new Error("schemaVersion must be 1");
191
- if (!Number.isInteger(value.storageVersion) || value.storageVersion < 1) {
192
- throw new Error("storageVersion must be a positive integer");
193
- }
194
- const aggregateSchemaVersion = value.aggregateSchemaVersion;
195
- if (!Number.isInteger(aggregateSchemaVersion) || aggregateSchemaVersion < 1) {
196
- throw new Error("aggregateSchemaVersion must be a positive integer");
197
- }
198
- if (typeof value.updatedAt !== "string" || !Number.isFinite(Date.parse(value.updatedAt))) {
199
- throw new Error("updatedAt must be an ISO timestamp");
200
- }
201
- let recordVersions;
202
- if (Object.hasOwn(value, "recordVersions")) {
203
- if (!isRecord(value.recordVersions))
204
- throw new Error("recordVersions must be an object");
205
- const known = currentRecordVersions();
206
- const kinds = Object.keys(value.recordVersions);
207
- const unknown = kinds.filter((kind) => !Object.hasOwn(known, kind));
208
- if (unknown.length > 0) {
209
- throw new Error(`recordVersions has unknown family: ${unknown[0]}`);
210
- }
211
- const parsed = {};
212
- for (const kind of kinds) {
213
- const version = value.recordVersions[kind];
214
- if (!Number.isInteger(version) || version < 1) {
215
- throw new Error(`recordVersions['${kind}'] must be a positive integer`);
216
- }
217
- parsed[kind] = version;
218
- }
219
- recordVersions = Object.freeze(parsed);
220
- }
123
+ function invalid(databasePath, error) {
221
124
  return {
222
- schemaVersion: 1,
223
- storageVersion: value.storageVersion,
224
- aggregateSchemaVersion: aggregateSchemaVersion,
225
- ...(recordVersions === undefined ? {} : { recordVersions }),
226
- updatedAt: value.updatedAt
125
+ status: "invalid",
126
+ latestVersion: CURRENT_STORAGE_VERSION,
127
+ minimumSupportedVersion: MIN_SUPPORTED_STORAGE_VERSION,
128
+ databasePath,
129
+ detail: error instanceof Error ? error.message : String(error)
227
130
  };
228
131
  }
229
- function parseJsonObject(raw, label) {
230
- let value;
231
- try {
232
- value = JSON.parse(raw);
233
- }
234
- catch (error) {
235
- throw new Error(`${label} is not valid JSON`, { cause: error });
236
- }
237
- if (!isRecord(value))
238
- throw new Error(`${label} must be an object`);
239
- return value;
240
- }
241
- function assertKeys(value, requiredKeys, optionalKeys, label) {
242
- const expected = new Set([...requiredKeys, ...optionalKeys]);
243
- const unknown = Object.keys(value).filter((key) => !expected.has(key));
244
- const missing = requiredKeys.filter((key) => !Object.hasOwn(value, key));
245
- if (unknown.length > 0)
246
- throw new Error(`${label} has unknown field: ${unknown[0]}`);
247
- if (missing.length > 0)
248
- throw new Error(`${label} is missing field: ${missing[0]}`);
249
- }
250
- function readOptionalText(path) {
251
- try {
252
- return readFileSync(path, "utf8");
253
- }
254
- catch (error) {
255
- if (error instanceof Error && "code" in error && error.code === "ENOENT")
256
- return null;
257
- throw error;
258
- }
259
- }
260
- function isRecord(value) {
261
- return typeof value === "object" && value !== null && !Array.isArray(value);
262
- }
@@ -1,20 +1,16 @@
1
1
  /**
2
- * Scalar storage version constants.
2
+ * The one durable compatibility axis for a Yui Home.
3
3
  *
4
- * These live in their own module so that `storageSchema.ts`, `recordVersions.ts`,
5
- * and `taskStore.ts` can all import them without creating a circular dependency.
6
- * `storageSchema.ts` re-exports them as the public storage-contract boundary.
7
- */
8
- /**
9
- * Version of the on-disk layout (`schema.json` plus the SQLite database).
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.
10
8
  *
11
- * Layout 8 is the current SQLite WAL control-plane layout: the authoritative
12
- * store is `yui.db`. Layout 8 is the only physical layout this release reads
13
- * and writes. Historical aggregate contracts are rejected without mutation.
14
- */
15
- export const CURRENT_STORAGE_LAYOUT_VERSION = 8;
16
- /**
17
- * Aggregate 31 makes Agent results opaque, removes parsed Review/finding
18
- * authority, and intentionally has no upgrade path from earlier contracts.
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.
19
14
  */
20
- export const CURRENT_AGGREGATE_SCHEMA_VERSION = 31;
15
+ export const MIN_SUPPORTED_STORAGE_VERSION = 1;
16
+ export const CURRENT_STORAGE_VERSION = 1;