@zq-silk/yui 0.2.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- package/dist/worktree/roleWorkspace.js +0 -62
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for building and describing {@link MigrationReport}s.
|
|
3
|
+
*
|
|
4
|
+
* These never perform I/O and never interpret domain data — they only shape the
|
|
5
|
+
* structured evidence the engine emits.
|
|
6
|
+
*/
|
|
7
|
+
/** Reduce a planned step to the concise summary carried in reports. */
|
|
8
|
+
export function toStepSummary(planned) {
|
|
9
|
+
return {
|
|
10
|
+
axis: planned.axis,
|
|
11
|
+
...(planned.recordKind ? { recordKind: planned.recordKind } : {}),
|
|
12
|
+
fromVersion: planned.fromVersion,
|
|
13
|
+
toVersion: planned.toVersion,
|
|
14
|
+
transition: planned.transition,
|
|
15
|
+
declaredEffects: planned.step.declaredEffects
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The de-duplicated union of every planned step's declared effects, in
|
|
20
|
+
* first-seen deterministic order. This is exactly what the engine forwards to
|
|
21
|
+
* the injected `rebuildDerivedState` — the generic core keeps no domain list.
|
|
22
|
+
*/
|
|
23
|
+
export function collectEffects(steps) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const effects = [];
|
|
26
|
+
for (const planned of steps) {
|
|
27
|
+
for (const effect of planned.step.declaredEffects) {
|
|
28
|
+
if (!seen.has(effect)) {
|
|
29
|
+
seen.add(effect);
|
|
30
|
+
effects.push(effect);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return effects;
|
|
35
|
+
}
|
|
36
|
+
/** A short, human-readable one-line description of a report outcome. */
|
|
37
|
+
export function describeReport(report) {
|
|
38
|
+
switch (report.outcome) {
|
|
39
|
+
case "already-current":
|
|
40
|
+
return "Storage is already at the current version; nothing to migrate.";
|
|
41
|
+
case "blocked":
|
|
42
|
+
return `Migration blocked (${report.blocker.reason}): ${report.blocker.message} ${report.blocker.action}`;
|
|
43
|
+
case "active-runtime":
|
|
44
|
+
return `Migration refused: a live runtime is active${report.detail ? ` (${report.detail})` : ""}.`;
|
|
45
|
+
case "dry-run":
|
|
46
|
+
return `Dry run validated ${report.steps.length} step(s); fresh output discarded, source unchanged.`;
|
|
47
|
+
case "migrated":
|
|
48
|
+
return `Migrated through ${report.steps.length} step(s); source backed up at ${report.switch.backupPath ?? "(unspecified)"}.`;
|
|
49
|
+
case "failed":
|
|
50
|
+
return `Migration failed at ${report.stage}: ${report.error} Source is unchanged; delete any partial output and retry.`;
|
|
51
|
+
case "switch-ambiguous":
|
|
52
|
+
return `Migration switch is AMBIGUOUS: ${report.error} Recover from the backup at ${report.backupPath}.`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic, pure migration-core vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* This module is deliberately domain-agnostic: it knows about three independent
|
|
5
|
+
* monotonic version axes and how to chain adjacent steps, but it never encodes a
|
|
6
|
+
* list of concrete Yui records, derived-state fields, or reference invariants.
|
|
7
|
+
* Those live behind the injected {@link MigrationTarget} boundary so a new record
|
|
8
|
+
* family can be added without editing the generic engine.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Thrown by a target's `atomicSwitchWithBackup` when the switch is left in a
|
|
12
|
+
* partially-applied, ambiguous state (the original was moved aside but the
|
|
13
|
+
* promotion and its rollback both failed). It carries the exact recovery path so
|
|
14
|
+
* the orchestrator can report a truthful, actionable manual recovery rather than
|
|
15
|
+
* a false "the Home is unchanged".
|
|
16
|
+
*/
|
|
17
|
+
export class AmbiguousSwitchError extends Error {
|
|
18
|
+
/** The logical Home path that is now partially switched. */
|
|
19
|
+
homePath;
|
|
20
|
+
/** Where the original Home currently lives (the recovery source). */
|
|
21
|
+
backupPath;
|
|
22
|
+
/** The staged output that was not promoted. */
|
|
23
|
+
stagingPath;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
super(options.detail);
|
|
26
|
+
this.name = "AmbiguousSwitchError";
|
|
27
|
+
this.homePath = options.homePath;
|
|
28
|
+
this.backupPath = options.backupPath;
|
|
29
|
+
this.stagingPath = options.stagingPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { writeTextFileAtomically } from "./durableFile.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export const CURRENT_AGGREGATE_SCHEMA_VERSION = 1;
|
|
8
|
-
/** @deprecated Use CURRENT_STORAGE_LAYOUT_VERSION for new code. */
|
|
9
|
-
export const CURRENT_STORAGE_SCHEMA_VERSION = CURRENT_STORAGE_LAYOUT_VERSION;
|
|
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 };
|
|
10
7
|
export const STORAGE_SCHEMA_FILE = "schema.json";
|
|
11
|
-
/**
|
|
12
|
-
* The dispatcher is intentionally present from the first v5 release. There
|
|
13
|
-
* are no v4-to-v5 migrations: v5 is a fresh authoritative storage contract.
|
|
14
|
-
*/
|
|
15
|
-
export const STORAGE_MIGRATIONS = Object.freeze([]);
|
|
16
|
-
/** No historical aggregate is supported by this development release yet. */
|
|
17
|
-
export const AGGREGATE_MIGRATIONS = Object.freeze([]);
|
|
18
8
|
export class StorageSchemaError extends Error {
|
|
19
9
|
code;
|
|
20
10
|
constructor(code, message, options) {
|
|
@@ -35,65 +25,113 @@ export function inspectStorageSchema(rootDir) {
|
|
|
35
25
|
manifestPath
|
|
36
26
|
};
|
|
37
27
|
}
|
|
28
|
+
let manifest;
|
|
38
29
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
manifest = parseStorageManifest(raw);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
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)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
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
|
+
};
|
|
55
|
+
}
|
|
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
|
+
};
|
|
71
|
+
}
|
|
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
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
for (const [kind, currentEntry] of Object.entries(currentRecords)) {
|
|
88
|
+
const persisted = manifest.recordVersions[kind];
|
|
89
|
+
if (persisted === undefined) {
|
|
41
90
|
return {
|
|
42
91
|
status: "unsupported",
|
|
43
|
-
incompatibleComponent: "
|
|
44
|
-
direction:
|
|
45
|
-
currentVersion:
|
|
46
|
-
latestVersion:
|
|
92
|
+
incompatibleComponent: "record",
|
|
93
|
+
direction: "older",
|
|
94
|
+
currentVersion: 0,
|
|
95
|
+
latestVersion: currentEntry.version,
|
|
47
96
|
currentLayoutVersion: manifest.storageVersion,
|
|
48
97
|
latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
49
98
|
currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
|
|
50
99
|
latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
|
|
100
|
+
recordFamily: kind,
|
|
51
101
|
manifestPath
|
|
52
102
|
};
|
|
53
103
|
}
|
|
54
|
-
if (
|
|
104
|
+
if (persisted !== currentEntry.version) {
|
|
55
105
|
return {
|
|
56
106
|
status: "unsupported",
|
|
57
|
-
incompatibleComponent: "
|
|
58
|
-
direction:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
currentVersion: manifest.aggregateSchemaVersion,
|
|
62
|
-
latestVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
|
|
107
|
+
incompatibleComponent: "record",
|
|
108
|
+
direction: persisted < currentEntry.version ? "older" : "newer",
|
|
109
|
+
currentVersion: persisted,
|
|
110
|
+
latestVersion: currentEntry.version,
|
|
63
111
|
currentLayoutVersion: manifest.storageVersion,
|
|
64
112
|
latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
65
113
|
currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
|
|
66
114
|
latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
|
|
115
|
+
recordFamily: kind,
|
|
67
116
|
manifestPath
|
|
68
117
|
};
|
|
69
118
|
}
|
|
70
|
-
return {
|
|
71
|
-
status: "current",
|
|
72
|
-
currentVersion: manifest.storageVersion,
|
|
73
|
-
latestVersion: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
74
|
-
currentLayoutVersion: manifest.storageVersion,
|
|
75
|
-
latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
76
|
-
currentAggregateSchemaVersion: manifest.aggregateSchemaVersion,
|
|
77
|
-
latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
|
|
78
|
-
activeGeneration: manifest.activeGeneration,
|
|
79
|
-
manifestPath
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
return {
|
|
84
|
-
status: "invalid",
|
|
85
|
-
latestVersion: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
86
|
-
latestLayoutVersion: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
87
|
-
latestAggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
|
|
88
|
-
manifestPath,
|
|
89
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
90
|
-
};
|
|
91
119
|
}
|
|
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
|
+
};
|
|
92
130
|
}
|
|
93
131
|
export function ensureStorageSchema(rootDir, now = new Date()) {
|
|
94
132
|
const state = inspectStorageSchema(rootDir);
|
|
95
133
|
if (state.status === "uninitialized") {
|
|
96
|
-
|
|
134
|
+
writeCurrentStorageManifest(rootDir, now);
|
|
97
135
|
return;
|
|
98
136
|
}
|
|
99
137
|
requireInspectedSchema(state);
|
|
@@ -101,70 +139,31 @@ export function ensureStorageSchema(rootDir, now = new Date()) {
|
|
|
101
139
|
export function requireStorageSchema(rootDir) {
|
|
102
140
|
requireInspectedSchema(inspectStorageSchema(rootDir));
|
|
103
141
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
if (bySource.has(migration.fromVersion)) {
|
|
119
|
-
throw invalidMigration(`Duplicate migration from layout version ${migration.fromVersion}.`);
|
|
120
|
-
}
|
|
121
|
-
bySource.set(migration.fromVersion, migration);
|
|
122
|
-
}
|
|
123
|
-
const plan = [];
|
|
124
|
-
for (let current = fromVersion; current < targetVersion; current += 1) {
|
|
125
|
-
const migration = bySource.get(current);
|
|
126
|
-
if (migration === undefined || migration.toVersion !== current + 1) {
|
|
127
|
-
throw unsupportedVersion(current, targetVersion, "layout");
|
|
128
|
-
}
|
|
129
|
-
plan.push(migration);
|
|
130
|
-
}
|
|
131
|
-
for (const migration of plan)
|
|
132
|
-
migration.migrate(rootDir);
|
|
142
|
+
/**
|
|
143
|
+
* Compatible readers may bypass only a record-axis mismatch. Layout,
|
|
144
|
+
* aggregate, syntax, and initialization remain strict.
|
|
145
|
+
*/
|
|
146
|
+
export function requireCompatibleStorageSchema(rootDir) {
|
|
147
|
+
const state = inspectStorageSchema(rootDir);
|
|
148
|
+
if (state.status === "current")
|
|
149
|
+
return;
|
|
150
|
+
if (state.status === "unsupported"
|
|
151
|
+
&& state.incompatibleComponent === "record"
|
|
152
|
+
&& state.direction === "older")
|
|
153
|
+
return;
|
|
154
|
+
requireInspectedSchema(state);
|
|
133
155
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
/** Persist the current three-axis manifest through the existing atomic-file seam. */
|
|
157
|
+
export function writeCurrentStorageManifest(rootDir, now = new Date()) {
|
|
158
|
+
const recordVersions = {};
|
|
159
|
+
for (const [kind, entry] of Object.entries(currentRecordVersions())) {
|
|
160
|
+
recordVersions[kind] = entry.version;
|
|
139
161
|
}
|
|
140
|
-
const bySource = new Map();
|
|
141
|
-
for (const migration of migrations) {
|
|
142
|
-
assertLayoutVersion(migration.fromVersion, "Aggregate migration fromVersion");
|
|
143
|
-
assertLayoutVersion(migration.toVersion, "Aggregate migration toVersion");
|
|
144
|
-
if (migration.toVersion !== migration.fromVersion + 1) {
|
|
145
|
-
throw invalidMigration(`Aggregate migration ${migration.fromVersion}->${migration.toVersion} must advance exactly one schema version.`);
|
|
146
|
-
}
|
|
147
|
-
if (bySource.has(migration.fromVersion)) {
|
|
148
|
-
throw invalidMigration(`Duplicate migration from aggregate schema version ${migration.fromVersion}.`);
|
|
149
|
-
}
|
|
150
|
-
bySource.set(migration.fromVersion, migration);
|
|
151
|
-
}
|
|
152
|
-
const plan = [];
|
|
153
|
-
for (let current = fromVersion; current < targetVersion; current += 1) {
|
|
154
|
-
const migration = bySource.get(current);
|
|
155
|
-
if (migration === undefined || migration.toVersion !== current + 1) {
|
|
156
|
-
throw unsupportedVersion(current, targetVersion, "aggregate");
|
|
157
|
-
}
|
|
158
|
-
plan.push(migration);
|
|
159
|
-
}
|
|
160
|
-
return plan.reduce((current, migration) => migration.migrate(current), state);
|
|
161
|
-
}
|
|
162
|
-
function writeStorageManifest(rootDir, now) {
|
|
163
162
|
const manifest = {
|
|
164
163
|
schemaVersion: 1,
|
|
165
164
|
storageVersion: CURRENT_STORAGE_LAYOUT_VERSION,
|
|
166
165
|
aggregateSchemaVersion: CURRENT_AGGREGATE_SCHEMA_VERSION,
|
|
167
|
-
|
|
166
|
+
recordVersions,
|
|
168
167
|
updatedAt: now.toISOString()
|
|
169
168
|
};
|
|
170
169
|
writeTextFileAtomically(join(rootDir, STORAGE_SCHEMA_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
@@ -178,11 +177,18 @@ function requireInspectedSchema(state) {
|
|
|
178
177
|
case "invalid":
|
|
179
178
|
throw new StorageSchemaError("STORAGE_SCHEMA_INVALID", `Invalid storage schema manifest at ${state.manifestPath}: ${state.detail}`);
|
|
180
179
|
case "unsupported":
|
|
181
|
-
throw unsupportedVersion(state.currentVersion, state.latestVersion, state.incompatibleComponent);
|
|
180
|
+
throw unsupportedVersion(state.currentVersion, state.latestVersion, state.incompatibleComponent, state.recordFamily);
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
|
-
function unsupportedVersion(current, required, component) {
|
|
185
|
-
|
|
183
|
+
function unsupportedVersion(current, required, component, recordFamily) {
|
|
184
|
+
if (component === "record" && recordFamily === undefined) {
|
|
185
|
+
return new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", "This Home predates the durable record-version manifest and cannot be opened without explicit introduction migrations.");
|
|
186
|
+
}
|
|
187
|
+
const label = component === "layout"
|
|
188
|
+
? "Storage layout"
|
|
189
|
+
: component === "aggregate"
|
|
190
|
+
? "Aggregate schema"
|
|
191
|
+
: `Record family '${recordFamily}'`;
|
|
186
192
|
if (current < required) {
|
|
187
193
|
return new StorageSchemaError("STORAGE_SCHEMA_UNSUPPORTED", `${label} ${current} is older than required ${component} version ${required}; no migration is available in this Yui release.`);
|
|
188
194
|
}
|
|
@@ -190,7 +196,13 @@ function unsupportedVersion(current, required, component) {
|
|
|
190
196
|
}
|
|
191
197
|
function parseStorageManifest(raw) {
|
|
192
198
|
const value = parseJsonObject(raw, "Storage schema manifest");
|
|
193
|
-
|
|
199
|
+
return parseStorageSchemaManifest(value);
|
|
200
|
+
}
|
|
201
|
+
/** Strictly parse one already-decoded manifest object through the shared contract. */
|
|
202
|
+
export function parseStorageSchemaManifest(value) {
|
|
203
|
+
if (!isRecord(value))
|
|
204
|
+
throw new Error("Storage schema manifest must be an object");
|
|
205
|
+
assertKeys(value, ["schemaVersion", "storageVersion", "aggregateSchemaVersion", "updatedAt"], ["recordVersions"], "Storage schema manifest");
|
|
194
206
|
if (value.schemaVersion !== 1)
|
|
195
207
|
throw new Error("schemaVersion must be 1");
|
|
196
208
|
if (!Number.isInteger(value.storageVersion) || value.storageVersion < 1) {
|
|
@@ -200,21 +212,41 @@ function parseStorageManifest(raw) {
|
|
|
200
212
|
if (!Number.isInteger(aggregateSchemaVersion) || aggregateSchemaVersion < 1) {
|
|
201
213
|
throw new Error("aggregateSchemaVersion must be a positive integer");
|
|
202
214
|
}
|
|
203
|
-
const activeGeneration = value.activeGeneration ?? null;
|
|
204
|
-
if (activeGeneration !== null) {
|
|
205
|
-
throw new Error(`activeGeneration is not supported by storage layout ${String(value.storageVersion)}`);
|
|
206
|
-
}
|
|
207
215
|
if (typeof value.updatedAt !== "string" || !Number.isFinite(Date.parse(value.updatedAt))) {
|
|
208
216
|
throw new Error("updatedAt must be an ISO timestamp");
|
|
209
217
|
}
|
|
218
|
+
let recordVersions;
|
|
219
|
+
if (Object.hasOwn(value, "recordVersions")) {
|
|
220
|
+
if (!isRecord(value.recordVersions))
|
|
221
|
+
throw new Error("recordVersions must be an object");
|
|
222
|
+
const known = currentRecordVersions();
|
|
223
|
+
const kinds = Object.keys(value.recordVersions);
|
|
224
|
+
const unknown = kinds.filter((kind) => !Object.hasOwn(known, kind));
|
|
225
|
+
if (unknown.length > 0) {
|
|
226
|
+
throw new Error(`recordVersions has unknown family: ${unknown[0]}`);
|
|
227
|
+
}
|
|
228
|
+
const parsed = {};
|
|
229
|
+
for (const kind of kinds) {
|
|
230
|
+
const version = value.recordVersions[kind];
|
|
231
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
232
|
+
throw new Error(`recordVersions['${kind}'] must be a positive integer`);
|
|
233
|
+
}
|
|
234
|
+
parsed[kind] = version;
|
|
235
|
+
}
|
|
236
|
+
recordVersions = Object.freeze(parsed);
|
|
237
|
+
}
|
|
210
238
|
return {
|
|
211
239
|
schemaVersion: 1,
|
|
212
240
|
storageVersion: value.storageVersion,
|
|
213
241
|
aggregateSchemaVersion: aggregateSchemaVersion,
|
|
214
|
-
|
|
242
|
+
...(recordVersions === undefined ? {} : { recordVersions }),
|
|
215
243
|
updatedAt: value.updatedAt
|
|
216
244
|
};
|
|
217
245
|
}
|
|
246
|
+
/** Read and strictly parse the durable manifest through the same contract. */
|
|
247
|
+
export function readStorageSchemaManifest(rootDir) {
|
|
248
|
+
return parseStorageManifest(readFileSync(join(rootDir, STORAGE_SCHEMA_FILE), "utf8"));
|
|
249
|
+
}
|
|
218
250
|
function parseJsonObject(raw, label) {
|
|
219
251
|
let value;
|
|
220
252
|
try {
|
|
@@ -236,14 +268,6 @@ function assertKeys(value, requiredKeys, optionalKeys, label) {
|
|
|
236
268
|
if (missing.length > 0)
|
|
237
269
|
throw new Error(`${label} is missing field: ${missing[0]}`);
|
|
238
270
|
}
|
|
239
|
-
function assertLayoutVersion(version, label) {
|
|
240
|
-
if (!Number.isInteger(version) || version < 1) {
|
|
241
|
-
throw invalidMigration(`${label} must be a positive integer.`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
function invalidMigration(message) {
|
|
245
|
-
return new StorageSchemaError("STORAGE_MIGRATION_INVALID", message);
|
|
246
|
-
}
|
|
247
271
|
function readOptionalText(path) {
|
|
248
272
|
try {
|
|
249
273
|
return readFileSync(path, "utf8");
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scalar storage version constants.
|
|
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 for backward compatibility.
|
|
7
|
+
*/
|
|
8
|
+
/** Version of the on-disk layout (`schema.json`, root `state.json`, and locks). */
|
|
9
|
+
export const CURRENT_STORAGE_LAYOUT_VERSION = 6;
|
|
10
|
+
/** Version of the authoritative aggregate stored in `state.json`. */
|
|
11
|
+
export const CURRENT_AGGREGATE_SCHEMA_VERSION = 17;
|