@zq-silk/yui 0.16.1 → 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.
- package/ARCHITECTURE.md +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +9 -4
- package/dist/agent/agent.js +4 -9
- package/dist/agent/executionComponents.js +4 -4
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +13 -23
- package/dist/artifacts/managedGit.js +3 -56
- package/dist/brief/taskBrief.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -44
- package/dist/cli/updatePorts.js +76 -86
- package/dist/cli/upgradeCommand.js +4 -8
- package/dist/cli.js +39 -33
- package/dist/commands/controllerCommands.js +2 -2
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +91 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +2 -2
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/agentRuntimeObserver.js +4 -4
- package/dist/controller/clientRuntime.js +19 -44
- package/dist/controller/controller.js +24 -10
- package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
- package/dist/controller/globalInputDelivery.js +31 -8
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/jobSupervisor.js +3 -3
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +136 -119
- package/dist/controller/runtime.js +2 -28
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +28 -1
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/core/controllerIdentity.js +25 -0
- package/dist/core/controllerProcessIdentity.js +2 -2
- package/dist/core/controllerServer.js +4 -2
- package/dist/core/protocol.js +1 -1
- package/dist/doctor/doctor.js +2 -1
- package/dist/domain/validation.js +6 -0
- package/dist/event/taskEvent.js +3 -3
- package/dist/execution/workItemExecution.js +2 -2
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +11 -8
- package/dist/executor/effectiveLaunch.js +3 -3
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/grant/capabilityGrant.js +2 -2
- package/dist/input/inputRequest.js +4 -4
- package/dist/integration/changeSet.js +3 -3
- package/dist/integration/integrationAttempt.js +3 -3
- package/dist/integration/integrationSourceApplication.js +1 -1
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/job/durableJob.js +1 -1
- package/dist/job/jobRunner.js +2 -2
- package/dist/message/message.js +8 -6
- package/dist/message/messageContinuation.js +8 -9
- package/dist/milestone/milestone.js +3 -3
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/profile/agentProfile.js +3 -3
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/release/runtimeRelease.js +9 -7
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/project.js +3 -3
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +5 -7
- package/dist/review/reviewRound.js +5 -5
- package/dist/role/role.js +7 -8
- package/dist/runtime/acpProtocol.js +2 -3
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +25 -65
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +3 -3
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/codexInteractiveHost.js +1 -1
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/launchBroker.js +1 -1
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/providerContinuationReconciliationService.js +86 -38
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeIdentity.js +3 -3
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/runtimeCoherence.js +7 -3
- package/dist/runtime/runtimeObservation.js +3 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/runtime/taskRuntimeIsolation.js +3 -3
- package/dist/runtime/tmuxAdapters.js +3 -3
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/scheduler/taskWake.js +1 -1
- package/dist/storage/baselineSchema.js +606 -0
- package/dist/storage/homeLayout.js +5 -16
- package/dist/storage/recordValidation.js +16 -4
- package/dist/storage/sqliteSchema.js +106 -1741
- package/dist/storage/sqliteStore.js +16 -14
- package/dist/storage/storageSchema.js +8 -7
- package/dist/storage/storageVersions.js +23 -16
- package/dist/storage/taskStore.js +3 -29
- package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +17 -8
- package/dist/task/taskActivation.js +5 -4
- package/dist/telemetry/sqliteTelemetryStore.js +2 -2
- package/dist/verification/gateArtifact.js +5 -3
- package/dist/verification/verificationPlan.js +6 -7
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/dist/workItem/workItem.js +6 -6
- package/dist/workspace/cleanupInspection.js +1 -9
- package/dist/worktree/managedWorkspace.js +3 -3
- package/docs/managed-turn-and-session-runtime.md +50 -7
- package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +70 -214
- package/docs/release-workflow.zh-CN.md +53 -157
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/sqlite-control-plane-design.md +48 -289
- package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
- package/docs/storage-baseline.md +132 -0
- package/docs/storage-baseline.zh-CN.md +106 -0
- package/docs/task-delivery.md +110 -2
- package/docs/task-delivery.zh-CN.md +82 -2
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +42 -164
- package/docs/testing/verification-levels.zh-CN.md +29 -115
- package/i18n/README.zh-CN.md +7 -4
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
- package/dist/storage/migrations/agentFailureContext.js +0 -22
- package/dist/storage/migrations/agentRunContract.js +0 -159
- package/dist/storage/migrations/artifactsToGit.js +0 -338
- package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
- package/dist/storage/migrations/currentInputContract.js +0 -86
- package/dist/storage/migrations/currentRuntimeContract.js +0 -228
- package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
- package/dist/storage/migrations/integrationContinuation.js +0 -105
- package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
- package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
- package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
- package/dist/storage/migrations/submitIntent.js +0 -126
- package/dist/storage/migrations/unifyHomeLayout.js +0 -925
- package/dist/storage/migrations/verificationPlanV1.js +0 -162
- package/dist/storage/migrations/verificationPolicy.js +0 -74
- 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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
313
|
-
if (current?.
|
|
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
|
|
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"
|
|
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:
|
|
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 {
|
|
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 =
|
|
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 >
|
|
60
|
+
if (compareStorageVersions(migration.currentVersion, CURRENT_STORAGE_VERSION) > 0) {
|
|
61
61
|
return { status: "unsupported", direction: "newer", ...fields };
|
|
62
62
|
}
|
|
63
|
-
if (migration.currentVersion <
|
|
63
|
+
if (compareStorageVersions(migration.currentVersion, MIN_SUPPORTED_STORAGE_VERSION) < 0) {
|
|
64
64
|
return { status: "unsupported", direction: "older", ...fields };
|
|
65
65
|
}
|
|
66
|
-
const plan =
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
|
|
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 =
|
|
13
|
-
|
|
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 =
|
|
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
|
}
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
|
-
import { inspectAgentHostCompatibility, describeAgentHostUpgradeBlockers } from "../../runtime/agentHostCompatibility.js";
|
|
5
4
|
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation } from "../../runtime/runtimeObservation.js";
|
|
6
5
|
import { RUNTIME_PROCESS_EXIT_TASK_EVENT, validateRuntimeProcessExitObservation } from "../../runtime/processExitObservation.js";
|
|
7
6
|
import { SqliteTaskStore } from "../sqliteStore.js";
|
|
8
7
|
import { storageBackupRoot } from "../homeLayout.js";
|
|
9
|
-
import {
|
|
10
|
-
import { preflightCollapseWorktreeLayout } from "../migrations/collapseWorktreeLayout.js";
|
|
11
|
-
import { preflightNotificationOnlyWakes } from "../migrations/notificationOnlyWakes.js";
|
|
12
|
-
import { preflightVerificationPolicy } from "../migrations/verificationPolicy.js";
|
|
13
|
-
import { preflightCurrentInputContract } from "../migrations/currentInputContract.js";
|
|
14
|
-
import { preflightCurrentRuntimeContract } from "../migrations/currentRuntimeContract.js";
|
|
15
|
-
import { migrateSqliteSchema, storageMigrationPlan } from "../sqliteSchema.js";
|
|
8
|
+
import { applySqliteMinorUpgrades, storageMinorUpgradePlan } from "../sqliteSchema.js";
|
|
16
9
|
import { CURRENT_DATABASE_FILENAME, inspectStorageSchema } from "../storageSchema.js";
|
|
17
10
|
import { CURRENT_STORAGE_VERSION, MIN_SUPPORTED_STORAGE_VERSION } from "../storageVersions.js";
|
|
18
11
|
/**
|
|
19
|
-
* Upgrade
|
|
12
|
+
* Upgrade only recognized minor versions within the current Home major.
|
|
20
13
|
*
|
|
21
14
|
* Supported earlier versions are readable only here. Ordinary stores still
|
|
22
15
|
* admit exactly {@link CURRENT_STORAGE_VERSION}; no old-shape normalizer or
|
|
@@ -30,6 +23,13 @@ export async function runStorageUpgrade(options) {
|
|
|
30
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.");
|
|
31
24
|
}
|
|
32
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
|
+
}
|
|
33
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.");
|
|
34
34
|
}
|
|
35
35
|
if (state.status === "unsupported") {
|
|
@@ -40,15 +40,6 @@ export async function runStorageUpgrade(options) {
|
|
|
40
40
|
? classification.classification.blocker.action
|
|
41
41
|
: "Use a compatible Yui release.");
|
|
42
42
|
}
|
|
43
|
-
const incompatibleHosts = await inspectAgentHostCompatibility(options.home);
|
|
44
|
-
if (incompatibleHosts.length > 0) {
|
|
45
|
-
return {
|
|
46
|
-
...blocked(state.status === "current" ? currentClassification() : migratableClassification(state.currentVersion), "host-compatibility", `Existing Agent Hosts cannot safely use the target Controller/storage:\n${describeAgentHostUpgradeBlockers(incompatibleHosts)}`, "Keep the current Controller, Home and Host processes unchanged. Inspect the named Sessions and original pending "
|
|
47
|
-
+ "inputs/results; let current work settle, then use explicitly authorized Session replacement/cleanup at a safe "
|
|
48
|
-
+ "boundary before retrying the upgrade. This target cannot hot-patch legacy Host memory."),
|
|
49
|
-
hosts: incompatibleHosts
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
43
|
if (state.status === "current") {
|
|
53
44
|
try {
|
|
54
45
|
validateCurrentStore(options.home);
|
|
@@ -78,27 +69,12 @@ export async function runStorageUpgrade(options) {
|
|
|
78
69
|
}
|
|
79
70
|
};
|
|
80
71
|
}
|
|
81
|
-
const plan =
|
|
72
|
+
const plan = storageMinorUpgradePlan(state.currentVersion);
|
|
82
73
|
if (plan === null) {
|
|
83
74
|
return blocked(corruptedClassification(`No complete migration path exists from ${state.currentVersion} `
|
|
84
|
-
+ `to ${CURRENT_STORAGE_VERSION}.`), "corruption", "The storage migration registry is incomplete.", "
|
|
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.");
|
|
85
76
|
}
|
|
86
77
|
const classification = migratableClassification(state.currentVersion);
|
|
87
|
-
// Genuine pre-migration safety check, shared by ALL migrating modes. The
|
|
88
|
-
// unify-home data migration (18->19) physically relocates on-disk trees and
|
|
89
|
-
// rewrites live launch pointers, so before any mode proceeds we run its READ-
|
|
90
|
-
// ONLY preflight and refuse up front on the same conditions the migration would
|
|
91
|
-
// throw on inside the apply transaction: a queued/running Job bound under a
|
|
92
|
-
// relocating root, or a conflicting relocation target. The migration is applied
|
|
93
|
-
// offline (after the operator has stopped this Home's writers), so the preflight
|
|
94
|
-
// does not scan for live processes. Surfacing these here — before the Controller
|
|
95
|
-
// is stopped or a backup is taken — is what makes `--dry-run` and the updater's
|
|
96
|
-
// `--update-preflight` a genuine readiness signal rather than a mere list of
|
|
97
|
-
// schema steps, while the in-transaction checks remain the authoritative guard
|
|
98
|
-
// against anything that starts after this point.
|
|
99
|
-
const migrationBlocked = preflightMigrationBlockers(options.home, plan, classification);
|
|
100
|
-
if (migrationBlocked !== null)
|
|
101
|
-
return migrationBlocked;
|
|
102
78
|
if (options.mode === "update-preflight") {
|
|
103
79
|
return {
|
|
104
80
|
outcome: "update-preflight",
|
|
@@ -144,7 +120,7 @@ export async function runStorageUpgrade(options) {
|
|
|
144
120
|
database.pragma("synchronous = FULL");
|
|
145
121
|
database.pragma("foreign_keys = ON");
|
|
146
122
|
database.pragma("busy_timeout = 5000");
|
|
147
|
-
|
|
123
|
+
applySqliteMinorUpgrades(database);
|
|
148
124
|
migrationCommitted = true;
|
|
149
125
|
}
|
|
150
126
|
finally {
|
|
@@ -201,7 +177,7 @@ export async function runStorageUpgrade(options) {
|
|
|
201
177
|
};
|
|
202
178
|
}
|
|
203
179
|
function validateCurrentStore(home) {
|
|
204
|
-
const store = new SqliteTaskStore(home);
|
|
180
|
+
const store = new SqliteTaskStore(home, { readonly: true });
|
|
205
181
|
try {
|
|
206
182
|
store.validateCurrentRecords();
|
|
207
183
|
for (const taskId of store.listTasks().map(({ id }) => id)) {
|
|
@@ -284,7 +260,7 @@ function unsupportedClassification(state) {
|
|
|
284
260
|
const action = future
|
|
285
261
|
? "Use a newer Yui release."
|
|
286
262
|
: "Preserve this Home for use with its matching historical Yui release, "
|
|
287
|
-
+ "or
|
|
263
|
+
+ "or use the independent explicit converter for a different storage major.";
|
|
288
264
|
return {
|
|
289
265
|
classification: {
|
|
290
266
|
verdict: "NEEDS_NEW_VERSION",
|
|
@@ -320,80 +296,6 @@ function blocked(classification, stage, message, action) {
|
|
|
320
296
|
sceneUnchanged: true
|
|
321
297
|
};
|
|
322
298
|
}
|
|
323
|
-
/**
|
|
324
|
-
* Run each data migration's registered READ-ONLY preflight against the
|
|
325
|
-
* current (pre-upgrade) database and, when any finds a blocker, return a
|
|
326
|
-
* `blocked` result the caller surfaces before touching the Controller or the
|
|
327
|
-
* Home. Returns `null` when the plan carries no registered readiness check,
|
|
328
|
-
* or when every preflight is clear.
|
|
329
|
-
*
|
|
330
|
-
* Both the 23->24 unify and the 24->25 collapse migrations physically relocate
|
|
331
|
-
* managed worktrees and share the same blocker shape (in-flight Job, conflicting
|
|
332
|
-
* relocation target). When a Home is upgraded across both in one run, their
|
|
333
|
-
* blockers are aggregated so the operator sees their readiness problems together.
|
|
334
|
-
* Notification retirement separately rejects unsettled execution references.
|
|
335
|
-
*
|
|
336
|
-
* The database is opened read-only so the checks cannot mutate the authoritative
|
|
337
|
-
* store, and the handle is always closed. An unexpected failure to evaluate a
|
|
338
|
-
* preflight is itself a fail-closed blocker: we must not advance to an
|
|
339
|
-
* irreversible migration on an unverifiable readiness signal.
|
|
340
|
-
*/
|
|
341
|
-
function preflightMigrationBlockers(home, plan, classification) {
|
|
342
|
-
const relocatesUnify = plan.some((step) => step.name === "unify-home-layout");
|
|
343
|
-
const relocatesCollapse = plan.some((step) => step.name === "collapse-worktree-layout");
|
|
344
|
-
const retiresRunWakes = plan.some((step) => step.name === "notification-only-wakes");
|
|
345
|
-
const changesVerification = plan.some(step => step.name === "advisory-and-verification-policy");
|
|
346
|
-
const changesInput = plan.some(step => step.name === "current-input-contract");
|
|
347
|
-
const retiresRuntime = plan.some(step => step.name === "current-verification-and-owner-contract");
|
|
348
|
-
if (!relocatesUnify && !relocatesCollapse && !retiresRunWakes && !changesVerification && !changesInput && !retiresRuntime)
|
|
349
|
-
return null;
|
|
350
|
-
let blockers;
|
|
351
|
-
try {
|
|
352
|
-
const database = new Database(join(home, CURRENT_DATABASE_FILENAME), {
|
|
353
|
-
readonly: true,
|
|
354
|
-
fileMustExist: true
|
|
355
|
-
});
|
|
356
|
-
try {
|
|
357
|
-
blockers = [];
|
|
358
|
-
if (relocatesUnify)
|
|
359
|
-
blockers.push(...preflightUnifyHomeLayout(database).blockers);
|
|
360
|
-
if (relocatesCollapse)
|
|
361
|
-
blockers.push(...preflightCollapseWorktreeLayout(database).blockers);
|
|
362
|
-
if (retiresRunWakes)
|
|
363
|
-
preflightNotificationOnlyWakes(database);
|
|
364
|
-
if (changesVerification)
|
|
365
|
-
preflightVerificationPolicy(database);
|
|
366
|
-
if (changesInput)
|
|
367
|
-
preflightCurrentInputContract(database);
|
|
368
|
-
if (retiresRuntime)
|
|
369
|
-
preflightCurrentRuntimeContract(database);
|
|
370
|
-
}
|
|
371
|
-
finally {
|
|
372
|
-
database.close();
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
return {
|
|
377
|
-
...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. "
|
|
378
|
-
+ "Confirm the relevant Runs and Jobs are settled, then rerun the upgrade.")
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
if (blockers.length === 0)
|
|
382
|
-
return null;
|
|
383
|
-
return {
|
|
384
|
-
outcome: "blocked",
|
|
385
|
-
stage: "in-flight",
|
|
386
|
-
message: "Refusing to migrate: the Home is not safe to relocate under the current layout yet. "
|
|
387
|
-
+ blockers.map((entry) => entry.detail).join("; ") + ".",
|
|
388
|
-
action: "Resolve the reported condition(s) — let a queued/running Job finish or cancel it, and "
|
|
389
|
-
+ "clear any conflicting relocation target — then rerun the upgrade. The migration is applied "
|
|
390
|
-
+ "offline, so first confirm the Controller and any execution writers for this Home are "
|
|
391
|
-
+ "stopped. The authoritative Home is unchanged.",
|
|
392
|
-
blockers: blockers.map((entry) => ({ reason: entry.reason, detail: entry.detail })),
|
|
393
|
-
classification,
|
|
394
|
-
sceneUnchanged: true
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
299
|
function messageOf(error) {
|
|
398
300
|
return error instanceof Error ? error.message : String(error);
|
|
399
301
|
}
|
package/dist/task/nextAction.js
CHANGED
|
@@ -91,24 +91,7 @@ export function projectNextAction(facts) {
|
|
|
91
91
|
ref: refs[1]
|
|
92
92
|
}
|
|
93
93
|
],
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
const draftWork = selectOpenWorkItem(facts.workItems);
|
|
98
|
-
if (draftWork.kind === "blocked") {
|
|
99
|
-
const refs = [
|
|
100
|
-
ref("work-item", draftWork.itemId),
|
|
101
|
-
ref("work-item", draftWork.blockedBy)
|
|
102
|
-
];
|
|
103
|
-
return buildAction(facts, {
|
|
104
|
-
kind: "repair-protocol-inconsistency",
|
|
105
|
-
reason: `Draft Work Item ${draftWork.itemId} depends on ${draftWork.blockedBy}, which is missing, retired, or not completed. Edit the Draft before activation.`,
|
|
106
|
-
refs,
|
|
107
|
-
conflicts: refs,
|
|
108
|
-
preconditions: [
|
|
109
|
-
{ fact: `Draft dependency ${draftWork.blockedBy} is valid`, satisfied: false, ref: refs[1] }
|
|
110
|
-
],
|
|
111
|
-
recommendedCommand: `yui task work edit ${task.id}/${draftWork.itemId} --clear-dependencies`
|
|
94
|
+
...dependencyJudgment(task.id, dependencyIssue.workItemId)
|
|
112
95
|
});
|
|
113
96
|
}
|
|
114
97
|
return buildAction(facts, {
|
|
@@ -323,18 +306,24 @@ export function projectNextAction(facts) {
|
|
|
323
306
|
}
|
|
324
307
|
const openWork = selectOpenWorkItem(facts.workItems);
|
|
325
308
|
if (openWork?.kind === "blocked") {
|
|
309
|
+
const issue = draftWorkItemDependencyIssue(facts.workItems);
|
|
310
|
+
const itemId = issue?.workItemId ?? openWork.itemId;
|
|
311
|
+
const dependencyId = issue?.dependencyId ?? openWork.blockedBy;
|
|
326
312
|
const refs = [
|
|
327
|
-
ref("work-item",
|
|
328
|
-
ref("work-item",
|
|
313
|
+
ref("work-item", itemId),
|
|
314
|
+
ref("work-item", dependencyId)
|
|
329
315
|
];
|
|
330
316
|
return buildAction(facts, {
|
|
331
317
|
kind: "repair-protocol-inconsistency",
|
|
332
|
-
reason:
|
|
318
|
+
reason: issue?.kind === "cycle"
|
|
319
|
+
? `Work Item dependency cycle includes ${itemId}/${dependencyId}.`
|
|
320
|
+
: `Work Item ${itemId} depends on ${dependencyId}, which is ${issue === undefined ? "not completed" : "missing or retired"}.`,
|
|
333
321
|
refs,
|
|
334
322
|
conflicts: refs,
|
|
335
323
|
preconditions: [
|
|
336
|
-
{ fact: `Dependency ${
|
|
337
|
-
]
|
|
324
|
+
{ fact: `Dependency ${dependencyId} is valid and completed`, satisfied: false, ref: refs[1] }
|
|
325
|
+
],
|
|
326
|
+
...dependencyJudgment(task.id, itemId)
|
|
338
327
|
});
|
|
339
328
|
}
|
|
340
329
|
if (openWork?.kind === "ready") {
|
|
@@ -769,6 +758,27 @@ function reviewRoundConflict(round, activeRuns) {
|
|
|
769
758
|
}
|
|
770
759
|
return null;
|
|
771
760
|
}
|
|
761
|
+
function dependencyJudgment(taskId, workItemId) {
|
|
762
|
+
return {
|
|
763
|
+
recommendedCommand: `yui task work show ${taskId}/${workItemId}`,
|
|
764
|
+
alternatives: [
|
|
765
|
+
{
|
|
766
|
+
kind: "inspect-dependencies",
|
|
767
|
+
reason: "Read the related requirements and dependency records before choosing a repair.",
|
|
768
|
+
recommendedCommand: `yui task work list ${taskId}`
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
kind: "revise-dependencies",
|
|
772
|
+
reason: "If the intended dependency graph is wrong, edit the affected WorkItem with --after for the complete intended set, preserving unrelated valid dependencies."
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
kind: "retire-obsolete-work",
|
|
776
|
+
reason: "Only if the requirement is no longer needed, deliberately retire it and reconcile its dependents."
|
|
777
|
+
}
|
|
778
|
+
],
|
|
779
|
+
judgmentRequired: "The records identify a blocked dependency, not which business requirement or edge is wrong. Determine the intended graph from Task requirements; do not infer permission to delete dependencies."
|
|
780
|
+
};
|
|
781
|
+
}
|
|
772
782
|
function selectOpenWorkItem(workItems) {
|
|
773
783
|
const byId = new Map(workItems.map((item) => [item.id, item]));
|
|
774
784
|
const openItems = workItems.filter((item) => OPEN_WORK_ITEM_STATUSES.has(item.status));
|
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:
|
|
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
|
|
19
|
+
* silently adopt a different workspace ref namespace.
|
|
20
20
|
*/
|
|
21
21
|
export function bindTaskWorkspaceIdentity(task, identity, now) {
|
|
22
22
|
validateTask(task);
|
|
@@ -304,7 +304,8 @@ export function reopenTask(task, now) {
|
|
|
304
304
|
}
|
|
305
305
|
: {}),
|
|
306
306
|
status: "active",
|
|
307
|
-
|
|
307
|
+
// Reopening intent does not lift an independent execution stop. Only the
|
|
308
|
+
// explicit start operation may do that after its authority/cleanup checks.
|
|
308
309
|
updatedAt: now.toISOString()
|
|
309
310
|
});
|
|
310
311
|
}
|
|
@@ -408,8 +409,19 @@ export function startTaskExecution(task, now) {
|
|
|
408
409
|
});
|
|
409
410
|
}
|
|
410
411
|
export function validateTask(task) {
|
|
411
|
-
if (task.schemaVersion !==
|
|
412
|
-
throw new Error("Task must use schemaVersion
|
|
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
|
+
}
|
|
413
425
|
requireSafeIdentity(task.id, "Task id");
|
|
414
426
|
requireText(task.title, "Task title");
|
|
415
427
|
if (task.type !== undefined)
|
|
@@ -491,9 +503,6 @@ export function validateTask(task) {
|
|
|
491
503
|
normalizeProjectBindings(task.projectBindings);
|
|
492
504
|
if (task.cwd !== undefined)
|
|
493
505
|
requireText(task.cwd, "Task workspace");
|
|
494
|
-
if (Object.hasOwn(task, "legacyDeliveryPath")) {
|
|
495
|
-
throw new Error("Task contains the removed delivery-path field.");
|
|
496
|
-
}
|
|
497
506
|
const completionFields = [task.completedAt, task.completedBy, task.completionSummary];
|
|
498
507
|
const hasAnyCompletion = completionFields.some((value) => value !== undefined);
|
|
499
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 {
|
|
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");
|