@zq-silk/yui 0.15.11 → 0.16.0
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 +11 -6
- package/ARCHITECTURE.zh-CN.md +8 -4
- package/README.md +13 -5
- package/dist/agentRun/agentRun.js +3 -0
- package/dist/artifacts/artifactCommitLock.js +16 -55
- package/dist/artifacts/taskArtifactRepository.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +1 -1
- package/dist/cli/commandCatalog.js +92 -48
- package/dist/cli/completion.js +18 -18
- package/dist/cli/completionWizard.js +1 -1
- package/dist/cli/dynamicCompletion.js +1 -1
- package/dist/cli/interactionPolicy.js +11 -3
- package/dist/cli/invocationAuthority.js +64 -0
- package/dist/cli/managedDiagnostics.js +2 -2
- package/dist/cli/parseRepeatable.js +35 -0
- package/dist/cli/updatePorts.js +26 -5
- package/dist/cli.js +1303 -1235
- package/dist/commands/agentCommands.js +4 -4
- package/dist/commands/capabilityCommands.js +1 -1
- package/dist/commands/configCommands.js +6 -57
- package/dist/commands/configOverview.js +2 -2
- package/dist/commands/durableJobCommands.js +12 -6
- package/dist/commands/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +78 -29
- package/dist/commands/grantCommands.js +0 -3
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/operatorCommands.js +3 -3
- package/dist/commands/projectCommands.js +45 -25
- package/dist/commands/resourcesCommands.js +10 -40
- package/dist/commands/roleConfiguration.js +2 -6
- package/dist/commands/taskActivationCommands.js +4 -8
- package/dist/commands/taskAgentCapabilities.js +9 -0
- package/dist/commands/taskCommandSupport.js +155 -0
- package/dist/commands/taskCommandTypes.js +1 -0
- package/dist/commands/taskCommands.js +355 -732
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskExecutionCommands.js +1 -1
- package/dist/commands/taskFactCommands.js +325 -0
- package/dist/commands/taskInputCommands.js +12 -42
- package/dist/commands/taskIntegrationCommands.js +20 -58
- package/dist/commands/taskNextActionCommand.js +7 -6
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +12 -3
- package/dist/commands/taskPublicationVerifyCommand.js +24 -40
- package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
- package/dist/commands/taskUpstreamCommands.js +19 -8
- package/dist/commands/workflowCommands.js +6 -1
- package/dist/completion/completionInstaller.js +26 -26
- package/dist/completion/completionState.js +26 -26
- package/dist/completion/fileCompletionManager.js +6 -11
- package/dist/config/configCatalog.js +0 -2
- package/dist/config/timeZone.js +14 -0
- package/dist/config/yuiConfig.js +0 -29
- package/dist/context/runContextPack.js +5 -3
- package/dist/context/taskCatalog.js +185 -0
- package/dist/context/taskContext.js +152 -151
- package/dist/context/wakeRunReferences.js +11 -0
- package/dist/controller/agentCapabilities.js +58 -0
- package/dist/controller/agentRuntimeObserver.js +6 -11
- package/dist/controller/clientRuntime.js +22 -14
- package/dist/controller/controller.js +20 -24
- package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
- package/dist/controller/globalInputDelivery.js +13 -0
- package/dist/controller/jobClient.js +4 -4
- package/dist/controller/jobControl.js +60 -25
- package/dist/controller/jobSupervisor.js +4 -4
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/runtime.js +85 -43
- package/dist/controller/runtimeEventProcessor.js +0 -5
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/controller/taskAgentError.js +35 -0
- package/dist/coordination/workMailboxQueue.js +2 -24
- package/dist/core/controllerClient.js +2 -2
- package/dist/core/fileLockOwner.js +74 -0
- package/dist/decision/decision.js +17 -0
- package/dist/doctor/doctor.js +18 -9
- package/dist/event/taskEvent.js +12 -0
- package/dist/execution/workItemExecutionProjection.js +2 -2
- package/dist/executor/agentCapabilityConfig.js +43 -0
- package/dist/executor/agentConfigurationCatalog.js +49 -11
- package/dist/executor/agentExecutor.js +5 -8
- package/dist/executor/fileRoleLaunchPlanner.js +11 -53
- package/dist/executor/taskAgentCapabilities.js +64 -0
- package/dist/integration/deliveryObligation.js +1 -69
- package/dist/integration/gitIntegrationService.js +79 -90
- package/dist/integration/integrationAttempt.js +3 -14
- package/dist/integration/integrationSourceApplication.js +4 -12
- package/dist/integration/manifestTags.js +2 -2
- package/dist/job/durableJob.js +4 -25
- package/dist/job/jobAssignmentScope.js +22 -0
- package/dist/job/jobOperation.js +16 -0
- package/dist/job/jobRunner.js +2 -1
- package/dist/job/stepDirectory.js +15 -0
- package/dist/kernel/builtinCapabilities.js +15 -11
- package/dist/kernel/kernelPorts.js +1 -17
- package/dist/lifecycle/exactRunTerminalization.js +5 -10
- package/dist/message/globalProviderRetry.js +15 -0
- package/dist/message/inputControlResolution.js +3 -4
- package/dist/message/message.js +17 -20
- package/dist/message/messageContinuation.js +1 -1
- package/dist/milestone/milestone.js +11 -0
- package/dist/observability/executionAudit.js +19 -0
- package/dist/observability/orchestrationMetrics.js +24 -1
- package/dist/observability/runtimeIdentity.js +1 -17
- package/dist/output/agentConfigurationPresentation.js +2 -0
- package/dist/output/timePresentation.js +1 -14
- package/dist/plugins/pluginService.js +12 -4
- package/dist/release/releaseWorkflowPorts.js +45 -96
- package/dist/release/runtimeRelease.js +9 -15
- package/dist/repository/gitWorkspace.js +371 -105
- package/dist/repository/projectMaintenanceLock.js +98 -82
- package/dist/repository/taskBaseFreshness.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +82 -144
- package/dist/repository/taskWorkspacePreparer.js +113 -47
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- package/dist/resources/autoResourceGc.js +4 -77
- package/dist/resources/liveReferences.js +7 -54
- package/dist/resources/resourceDiscovery.js +5 -118
- package/dist/resources/resourceGc.js +233 -181
- package/dist/resources/resourceRegistrar.js +5 -4
- package/dist/resources/resourceRegistry.js +3 -37
- package/dist/resources/resourceRegistryStore.js +13 -36
- package/dist/resources/sqliteResourceRegistry.js +43 -26
- package/dist/review/deltaRecheck.js +1 -1
- package/dist/review/reviewAcceptance.js +1 -1
- package/dist/review/reviewDecision.js +1 -1
- package/dist/review/reviewRound.js +8 -7
- package/dist/review/taskFinalReviewContractResolution.js +1 -1
- package/dist/runtime/acpProtocol.js +4 -51
- package/dist/runtime/acpSession.js +10 -75
- package/dist/runtime/acpSessionConfiguration.js +1 -8
- package/dist/runtime/agentEndpoint.js +3 -2
- package/dist/runtime/agentError.js +5 -3
- package/dist/runtime/agentFailureContext.js +43 -0
- package/dist/runtime/agentHost.js +42 -25
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/codexInteractiveHost.js +2 -2
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/managedCaller.js +21 -1
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerErrors.js +38 -0
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/runtimeObservation.js +0 -5
- package/dist/runtime/runtimeSessionCandidate.js +2 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +10 -43
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +6 -8
- package/dist/scheduler/activeRoleRunDelivery.js +19 -7
- package/dist/scheduler/leaderWakeupProcessor.js +24 -6
- package/dist/scheduler/operatorEvent.js +12 -20
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +5 -8
- package/dist/scheduler/roleRunLiveness.js +4 -4
- package/dist/scheduler/roleRunStall.js +16 -23
- package/dist/scheduler/taskExecutionProjection.js +49 -36
- package/dist/scheduler/taskObservabilityProjection.js +6 -45
- package/dist/scheduler/taskWake.js +5 -10
- package/dist/scheduler/wakeReason.js +2 -0
- package/dist/scheduler/wakeupQueue.js +2 -4
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/contextRecords.js +106 -0
- package/dist/storage/currentTaskStore.js +1 -1
- package/dist/storage/homeLayout.js +13 -17
- package/dist/storage/migrations/agentFailureContext.js +22 -0
- package/dist/storage/migrations/currentInputContract.js +86 -0
- package/dist/storage/migrations/currentRuntimeContract.js +228 -0
- package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
- package/dist/storage/migrations/integrationContinuation.js +5 -4
- package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
- package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
- package/dist/storage/migrations/verificationPlanV1.js +162 -0
- package/dist/storage/migrations/verificationPolicy.js +74 -0
- package/dist/storage/migrations/workItemHistory.js +46 -0
- package/dist/storage/persistenceWorker.js +12 -4
- package/dist/storage/recordValidation.js +87 -0
- package/dist/storage/sqliteSchema.js +173 -1
- package/dist/storage/sqliteStore.js +172 -161
- package/dist/storage/storageSchema.js +2 -14
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +16 -7
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +5 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
- package/dist/task/archiveDiagnostics.js +1 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/completionReadiness.js +5 -24
- package/dist/task/draftPlan.js +0 -53
- package/dist/task/nextAction.js +7 -13
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- package/dist/task/remoteDeliveryService.js +89 -0
- package/dist/task/taskActivation.js +2 -25
- package/dist/task/taskActivationService.js +0 -2
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/task/taskSubmission.js +3 -14
- package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
- package/dist/telemetry/sqliteTelemetryStore.js +49 -49
- package/dist/telemetry/telemetryWiring.js +4 -3
- package/dist/tmux/tmuxManager.js +29 -20
- package/dist/verification/gateArtifact.js +15 -24
- package/dist/verification/gateArtifactStore.js +14 -7
- package/dist/verification/verificationGateService.js +29 -88
- package/dist/verification/verificationPlan.js +27 -72
- package/dist/web/assets/client/app.js +92 -17
- package/dist/web/assets/client/components.js +55 -13
- package/dist/web/assets/client/i18n.js +72 -4
- package/dist/web/assets/client/taskSurface.js +6 -5
- package/dist/web/assets/client/view.js +35 -7
- package/dist/web/assets/shell.js +6 -0
- package/dist/web/assets/styles/layout.js +7 -0
- package/dist/web/webServer.js +12 -3
- package/dist/web/webSnapshot.js +14 -85
- package/dist/web/webTaskSurface.js +17 -46
- package/dist/workItem/workItem.js +1 -12
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +110 -50
- package/docs/agent-result-consumption.md +4 -0
- package/docs/agent-result-consumption.zh-CN.md +3 -0
- package/docs/agent-runtime-drivers.md +7 -0
- package/docs/agent-runtime-drivers.zh-CN.md +5 -0
- package/docs/architecture/README.md +2 -0
- package/docs/architecture/README.zh-CN.md +3 -1
- package/docs/architecture/capabilities-and-resources.md +42 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
- package/docs/managed-turn-and-session-runtime.md +121 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/plugin-sdk.md +4 -2
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- package/docs/provider-runtime.md +3 -1
- package/docs/provider-runtime.zh-CN.md +4 -2
- package/docs/release-workflow.md +190 -7
- package/docs/release-workflow.zh-CN.md +139 -5
- package/docs/roles-and-configuration.md +29 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/sqlite-control-plane-design.md +16 -3
- package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
- package/docs/task-dag-semantics.md +3 -3
- package/docs/task-dag-semantics.zh-CN.md +2 -2
- package/docs/task-delivery.md +223 -17
- package/docs/task-delivery.zh-CN.md +171 -14
- package/docs/task-discovery.md +101 -0
- package/docs/task-discovery.zh-CN.md +85 -0
- package/docs/testing/verification-levels.md +161 -9
- package/docs/testing/verification-levels.zh-CN.md +116 -9
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -0
- package/skills/yui-leader/references/execution.md +3 -2
- package/skills/yui-leader/references/integration.md +9 -5
- package/skills/yui-leader/references/planning.md +9 -3
- package/skills/yui-operator/SKILL.md +24 -8
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +17 -2
- package/skills/yui-runtime/references/publication.md +26 -4
- package/skills/yui-runtime/references/recovery.md +71 -0
- package/dist/agent/agentRegistry.js +0 -10
- package/dist/agentRun/runIdentity.js +0 -22
- package/dist/commands/deliveryGuardPreflight.js +0 -30
- package/dist/commands/taskIntegrationQueueCommands.js +0 -226
- package/dist/commands/taskOverviewCommand.js +0 -376
- package/dist/completion/completionWizard.js +0 -125
- package/dist/context/dispatchContext.js +0 -110
- package/dist/context/wakeNotification.js +0 -144
- package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
- package/dist/integration/integrationQueueEntry.js +0 -191
- package/dist/integration/integrationQueueService.js +0 -810
- package/dist/release/fakeReleasePorts.js +0 -55
- package/dist/runtime/sessionOwnerRegistry.js +0 -137
- package/dist/task/deliveryGuard.js +0 -226
- /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Frozen 33→34 boundary. An old immediate request without origin was not
|
|
2
|
+
* auto-adoptable. Its owner must activate or cancel it with the old release;
|
|
3
|
+
* an upgrade cannot make that choice, even while execution is stopped. */
|
|
4
|
+
export function preflightCurrentInputContract(db) {
|
|
5
|
+
const blocked = db.prepare(`SELECT task_id FROM task_records
|
|
6
|
+
WHERE json_extract(payload,'$.status')='draft'
|
|
7
|
+
AND json_type(payload,'$.retiredAt') IS NULL
|
|
8
|
+
AND json_extract(payload,'$.activationRequest.startMode')='immediate'
|
|
9
|
+
AND json_extract(payload,'$.activationRequest.disposition')='pending'
|
|
10
|
+
AND json_type(payload,'$.activationRequest.origin') IS NULL LIMIT 1`)
|
|
11
|
+
.get();
|
|
12
|
+
if (blocked !== undefined) {
|
|
13
|
+
throw new Error(`Input contract cutover requires explicit activation or cancellation of the origin-less pending request: ${blocked.task_id}. Preserve the request and resolve it with the old release before upgrading.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Preserve original representations as audit Events, not runtime alternatives.
|
|
17
|
+
* All interpretation of wakePolicy/origin ends at this migration. */
|
|
18
|
+
export function migrateCurrentInputContract(db) {
|
|
19
|
+
preflightCurrentInputContract(db);
|
|
20
|
+
const highWater = db.prepare(`SELECT max(
|
|
21
|
+
coalesce((SELECT high_water FROM id_sequences WHERE task_id=? AND kind='event'),0),
|
|
22
|
+
coalesce((SELECT max(CAST(substr(event_id,7) AS INTEGER)) FROM events WHERE task_id=?),0)
|
|
23
|
+
) AS value`);
|
|
24
|
+
const saveEvent = db.prepare("INSERT INTO events(task_id,event_id,type,occurred_at,payload) VALUES(?,?,?,?,?)");
|
|
25
|
+
const saveCounter = db.prepare(`INSERT INTO id_sequences(task_id,kind,high_water) VALUES(?,'event',?)
|
|
26
|
+
ON CONFLICT(task_id,kind) DO UPDATE SET high_water=max(high_water,excluded.high_water)`);
|
|
27
|
+
const at = new Date().toISOString();
|
|
28
|
+
const archive = (taskId, type, record) => {
|
|
29
|
+
const seq = highWater.get(taskId, taskId).value + 1;
|
|
30
|
+
const event = { schemaVersion: 2, id: `event-${seq}`, taskId, type, createdAt: at,
|
|
31
|
+
payload: { record, disposition: "audit-only" } };
|
|
32
|
+
saveEvent.run(taskId, event.id, type, at, JSON.stringify(event));
|
|
33
|
+
saveCounter.run(taskId, seq);
|
|
34
|
+
};
|
|
35
|
+
const messages = db.prepare("SELECT task_id,message_id,payload FROM messages ORDER BY task_id,seq")
|
|
36
|
+
.all();
|
|
37
|
+
const saveMessage = db.prepare("UPDATE messages SET payload=? WHERE task_id=? AND message_id=?");
|
|
38
|
+
for (const row of messages) {
|
|
39
|
+
const message = JSON.parse(row.payload);
|
|
40
|
+
const user = message.kind === "user" || message.kind === "operator";
|
|
41
|
+
const hasPolicy = Object.hasOwn(message, "wakePolicy");
|
|
42
|
+
if (message.schemaVersion !== 3 || message.id !== row.message_id || message.taskId !== row.task_id
|
|
43
|
+
|| (hasPolicy && (!user || !["leader", "none"].includes(message.wakePolicy)))
|
|
44
|
+
|| (message.intent !== undefined && (!user || !["record", "discuss", "develop"].includes(message.intent)))
|
|
45
|
+
|| (hasPolicy && message.intent !== undefined
|
|
46
|
+
&& (message.wakePolicy === "none") !== (message.intent === "record"))) {
|
|
47
|
+
throw new Error(`Invalid historical Message input: ${row.task_id}/${row.message_id}. Preserve it for diagnosis.`);
|
|
48
|
+
}
|
|
49
|
+
if (!hasPolicy && (!user || message.intent !== undefined))
|
|
50
|
+
continue;
|
|
51
|
+
archive(row.task_id, "message.input-contract-retired", row.payload);
|
|
52
|
+
if (user && message.intent === undefined) {
|
|
53
|
+
message.intent = message.wakePolicy === "none" ? "record" : "discuss";
|
|
54
|
+
}
|
|
55
|
+
delete message.wakePolicy;
|
|
56
|
+
saveMessage.run(JSON.stringify(message), row.task_id, row.message_id);
|
|
57
|
+
}
|
|
58
|
+
const tasks = db.prepare("SELECT task_id,payload FROM task_records ORDER BY task_id")
|
|
59
|
+
.all();
|
|
60
|
+
const saveTask = db.prepare("UPDATE task_records SET payload=? WHERE task_id=?");
|
|
61
|
+
for (const row of tasks) {
|
|
62
|
+
const task = JSON.parse(row.payload);
|
|
63
|
+
const requests = [
|
|
64
|
+
...(task.activationRequest === undefined ? [] : [task.activationRequest]),
|
|
65
|
+
...(task.settledActivationRequests ?? [])
|
|
66
|
+
];
|
|
67
|
+
let changed = false;
|
|
68
|
+
for (const request of requests) {
|
|
69
|
+
if (request === null || typeof request !== "object" || request.schemaVersion !== 1
|
|
70
|
+
|| request.operation?.targetId !== row.task_id
|
|
71
|
+
|| !["immediate", "after-planning-turn"].includes(request.startMode)
|
|
72
|
+
|| !["pending", "failed", "cancelled", "adopted"].includes(request.disposition)
|
|
73
|
+
|| (Object.hasOwn(request, "origin") && !["explicit", "submit-develop"].includes(request.origin))) {
|
|
74
|
+
throw new Error(`Invalid historical activation request: ${row.task_id}. Preserve it for diagnosis.`);
|
|
75
|
+
}
|
|
76
|
+
if (Object.hasOwn(request, "origin")) {
|
|
77
|
+
changed = true;
|
|
78
|
+
delete request.origin;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (changed) {
|
|
82
|
+
archive(row.task_id, "task.activation-origin-retired", row.payload);
|
|
83
|
+
saveTask.run(JSON.stringify(task), row.task_id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { requireIdentity, requirePositiveInteger, requireText, requireTimestamp } from "../../domain/validation.js";
|
|
5
|
+
import { readHistoricalVerificationPlan } from "./verificationPlanV1.js";
|
|
6
|
+
/** Immutable audit only: no current executor or cache lookup reads this table.
|
|
7
|
+
* Raw payloads and binary logs survive retiring their executable contracts. */
|
|
8
|
+
export const CURRENT_RUNTIME_CONTRACT_SQL = `
|
|
9
|
+
CREATE TABLE storage_migration_archive (
|
|
10
|
+
migration_version INTEGER NOT NULL,
|
|
11
|
+
family TEXT NOT NULL,
|
|
12
|
+
record_key TEXT NOT NULL,
|
|
13
|
+
payload TEXT NOT NULL,
|
|
14
|
+
content BLOB,
|
|
15
|
+
PRIMARY KEY (migration_version, family, record_key)
|
|
16
|
+
);
|
|
17
|
+
`;
|
|
18
|
+
/** Read-only blockers, shared by preflight and the actual 34→35 transaction.
|
|
19
|
+
* An old admitted execution/physical owner must settle through its original
|
|
20
|
+
* contract. Neither an upgrade nor a directory name proves it is safe to drop. */
|
|
21
|
+
export function preflightCurrentRuntimeContract(db) {
|
|
22
|
+
const gate = db.prepare(`SELECT task_id,integration_id FROM integration_attempts
|
|
23
|
+
WHERE status NOT IN ('committed','superseded','failed')
|
|
24
|
+
AND json_type(payload,'$.gatePlanDigest') IS NOT NULL LIMIT 1`)
|
|
25
|
+
.get();
|
|
26
|
+
if (gate)
|
|
27
|
+
throw new Error(`Current verification cutover requires the admitted gate to settle: ${gate.task_id}/${gate.integration_id}. Continue or abort it with the old release.`);
|
|
28
|
+
const owner = db.prepare(`SELECT process_key FROM session_owners
|
|
29
|
+
WHERE json_extract(payload,'$.providerRoot.attribution')='launch-env' LIMIT 1`)
|
|
30
|
+
.get();
|
|
31
|
+
if (owner)
|
|
32
|
+
throw new Error(`Retired Session owner attribution requires explicit old-release reconciliation: ${owner.process_key}. Preserve its process evidence; no owner was rewritten.`);
|
|
33
|
+
if (db.name !== ":memory:" && db.name !== "") {
|
|
34
|
+
const directory = join(dirname(db.name), "runtime", "session-owners");
|
|
35
|
+
let files;
|
|
36
|
+
try {
|
|
37
|
+
files = readdirSync(directory).filter(name => name.endsWith(".json"));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code !== "ENOENT")
|
|
41
|
+
throw error;
|
|
42
|
+
files = [];
|
|
43
|
+
}
|
|
44
|
+
if (files.length)
|
|
45
|
+
throw new Error(`Retired file Session owners remain at ${directory}. Inspect and release their exact resources with the old release, then archive the files outside the active Home before upgrading.`);
|
|
46
|
+
}
|
|
47
|
+
for (const row of historicalIntegrations(db)) {
|
|
48
|
+
const value = JSON.parse(row.payload);
|
|
49
|
+
if (!["committed", "superseded", "failed"].includes(value.status)) {
|
|
50
|
+
throw new Error(`Historical Integration must be explicitly settled before retirement: ${row.task_id}/${row.integration_id}.`);
|
|
51
|
+
}
|
|
52
|
+
const workspace = db.prepare(`SELECT 1 FROM managed_workspaces WHERE task_id=?
|
|
53
|
+
AND json_extract(payload,'$.owner.integrationAttemptId')=? LIMIT 1`).get(row.task_id, row.integration_id);
|
|
54
|
+
const job = db.prepare(`SELECT 1 FROM durable_jobs WHERE task_id=?
|
|
55
|
+
AND json_extract(payload,'$.owner.integrationAttemptId')=?
|
|
56
|
+
AND (status IN ('queued','running') OR (status='unknown-needs-attention'
|
|
57
|
+
AND json_type(payload,'$.acknowledgedAt') IS NULL)) LIMIT 1`).get(row.task_id, row.integration_id);
|
|
58
|
+
const adoption = db.prepare(`SELECT 1 FROM events WHERE task_id=?
|
|
59
|
+
AND type='publication.candidate-adopted'
|
|
60
|
+
AND json_extract(payload,'$.payload.integrationId')=? LIMIT 1`).get(row.task_id, row.integration_id);
|
|
61
|
+
if (workspace || job || adoption) {
|
|
62
|
+
throw new Error(`Historical Integration still has execution or delivery references: ${row.task_id}/${row.integration_id}. Preserve the Home and resolve/export its original evidence before upgrading.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Frozen v34 contract retirement. Existing migration definitions are unchanged.
|
|
67
|
+
* Only active verification configuration changes; old proof is never relabelled
|
|
68
|
+
* as current success. Historical Integration bytes become ordinary Task Events. */
|
|
69
|
+
export function migrateCurrentRuntimeContract(db) {
|
|
70
|
+
preflightCurrentRuntimeContract(db);
|
|
71
|
+
const archive = db.prepare(`INSERT INTO storage_migration_archive
|
|
72
|
+
(migration_version,family,record_key,payload,content) VALUES(35,?,?,?,?)`);
|
|
73
|
+
for (const row of db.prepare("SELECT id,payload FROM projects").all()) {
|
|
74
|
+
const project = JSON.parse(row.payload);
|
|
75
|
+
let changed = false;
|
|
76
|
+
let plans = 0;
|
|
77
|
+
const knowledge = project.knowledge.map((entry) => {
|
|
78
|
+
if (entry.status !== "active")
|
|
79
|
+
return entry;
|
|
80
|
+
let value;
|
|
81
|
+
try {
|
|
82
|
+
value = JSON.parse(entry.body);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return entry;
|
|
86
|
+
}
|
|
87
|
+
if (!value || typeof value !== "object" || value.kind !== "verification-plan")
|
|
88
|
+
return entry;
|
|
89
|
+
if (++plans > 1 || value.schemaVersion !== 1 || Object.hasOwn(value, "mode")) {
|
|
90
|
+
throw new Error(`Invalid v34 verification configuration: ${row.id}. Preserve it for diagnosis.`);
|
|
91
|
+
}
|
|
92
|
+
readHistoricalVerificationPlan(value);
|
|
93
|
+
const { l1: _retired, ...current } = value;
|
|
94
|
+
changed = true;
|
|
95
|
+
return { ...entry, body: JSON.stringify({ ...current, schemaVersion: 2 }) };
|
|
96
|
+
});
|
|
97
|
+
if (changed) {
|
|
98
|
+
archive.run("project", row.id, row.payload, null);
|
|
99
|
+
db.prepare("UPDATE projects SET payload=? WHERE id=?").run(JSON.stringify({ ...project, knowledge }), row.id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const artifacts = db.prepare("SELECT key,project_id,payload FROM gate_artifacts WHERE level='L1'")
|
|
103
|
+
.all();
|
|
104
|
+
for (const row of artifacts) {
|
|
105
|
+
const value = JSON.parse(row.payload);
|
|
106
|
+
if (value.schemaVersion !== 1 || value.level !== "L1" || value.key !== row.key
|
|
107
|
+
|| value.projectId !== row.project_id || !/^[a-f0-9]{64}$/.test(row.key)
|
|
108
|
+
|| !["complete", "incomplete"].includes(value.status)
|
|
109
|
+
|| !["unknown", "succeeded", "failed"].includes(value.outcome)
|
|
110
|
+
|| (value.status === "incomplete") !== (value.outcome === "unknown")
|
|
111
|
+
|| !Array.isArray(value.steps)) {
|
|
112
|
+
throw new Error(`Invalid retired L1 evidence: ${row.key}. Preserve it for diagnosis.`);
|
|
113
|
+
}
|
|
114
|
+
validateRetiredGate(value);
|
|
115
|
+
archive.run("gate-artifact", row.key, row.payload, null);
|
|
116
|
+
const logs = db.prepare("SELECT step_name,log_content,log_digest,log_bytes FROM gate_artifact_logs WHERE artifact_key=?")
|
|
117
|
+
.all(row.key);
|
|
118
|
+
for (const { log_content, ...metadata } of logs) {
|
|
119
|
+
if (metadata.log_bytes !== log_content.length
|
|
120
|
+
|| metadata.log_digest !== createHash("sha256").update(log_content).digest("hex")) {
|
|
121
|
+
throw new Error(`Corrupted retired L1 log: ${row.key}/${metadata.step_name}. Preserve it for diagnosis.`);
|
|
122
|
+
}
|
|
123
|
+
archive.run("gate-artifact-log", JSON.stringify([row.key, metadata.step_name]), JSON.stringify(metadata), log_content);
|
|
124
|
+
}
|
|
125
|
+
db.prepare("DELETE FROM gate_artifact_logs WHERE artifact_key=?").run(row.key);
|
|
126
|
+
db.prepare("DELETE FROM gate_artifacts WHERE key=?").run(row.key);
|
|
127
|
+
}
|
|
128
|
+
const at = new Date().toISOString();
|
|
129
|
+
for (const row of historicalIntegrations(db)) {
|
|
130
|
+
const value = JSON.parse(row.payload);
|
|
131
|
+
if (value.schemaVersion !== 6 || value.taskId !== row.task_id || value.id !== row.integration_id
|
|
132
|
+
|| !/^integration-[1-9][0-9]*$/.test(value.id)
|
|
133
|
+
|| !Array.isArray(value.source.changeSetIds) || value.source.changeSetIds.length === 0
|
|
134
|
+
|| value.source.changeSetIds.some((id) => typeof id !== "string" || !/^change-set-[1-9][0-9]*$/.test(id))
|
|
135
|
+
|| new Set(value.source.changeSetIds).size !== value.source.changeSetIds.length
|
|
136
|
+
|| !Array.isArray(value.checkCommands) || value.checkCommands.some((command) => !text(command))
|
|
137
|
+
|| typeof value.rerunChecks !== "boolean" || !text(value.projectId) || !text(value.targetRef)
|
|
138
|
+
|| !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(value.beforeCommit)
|
|
139
|
+
|| !Number.isFinite(Date.parse(value.createdAt)) || !Number.isFinite(Date.parse(value.updatedAt))
|
|
140
|
+
|| !Number.isFinite(Date.parse(value.endedAt))
|
|
141
|
+
|| (value.status === "committed" && (value.afterCommit !== value.candidateCommit
|
|
142
|
+
|| !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(value.afterCommit) || !text(value.summary)))) {
|
|
143
|
+
throw new Error(`Invalid historical Integration: ${row.task_id}/${row.integration_id}. Preserve it for diagnosis.`);
|
|
144
|
+
}
|
|
145
|
+
const sequence = db.prepare(`SELECT max(
|
|
146
|
+
coalesce((SELECT high_water FROM id_sequences WHERE task_id=? AND kind='event'),0),
|
|
147
|
+
coalesce((SELECT max(CAST(substr(event_id,7) AS INTEGER)) FROM events WHERE task_id=?),0)
|
|
148
|
+
) AS value`).get(row.task_id, row.task_id).value + 1;
|
|
149
|
+
const event = { schemaVersion: 2, id: `event-${sequence}`, taskId: row.task_id,
|
|
150
|
+
type: "integration.source-retired", createdAt: at,
|
|
151
|
+
payload: { integrationId: row.integration_id, record: row.payload, disposition: "audit-only" } };
|
|
152
|
+
db.prepare("INSERT INTO events(task_id,event_id,type,occurred_at,payload) VALUES(?,?,?,?,?)")
|
|
153
|
+
.run(row.task_id, event.id, event.type, at, JSON.stringify(event));
|
|
154
|
+
db.prepare(`INSERT INTO id_sequences(task_id,kind,high_water) VALUES(?,'event',?)
|
|
155
|
+
ON CONFLICT(task_id,kind) DO UPDATE SET high_water=max(high_water,excluded.high_water)`).run(row.task_id, sequence);
|
|
156
|
+
// Preserve the retired identity's high-water mark so it cannot be reused.
|
|
157
|
+
const integrationSequence = Number(row.integration_id.slice("integration-".length));
|
|
158
|
+
if (!Number.isSafeInteger(integrationSequence) || integrationSequence < 1)
|
|
159
|
+
throw new Error("Invalid retired Integration identity.");
|
|
160
|
+
db.prepare(`INSERT INTO id_sequences(task_id,kind,high_water) VALUES(?,'integrationAttempt',?)
|
|
161
|
+
ON CONFLICT(task_id,kind) DO UPDATE SET high_water=max(high_water,excluded.high_water)`).run(row.task_id, integrationSequence);
|
|
162
|
+
db.prepare("DELETE FROM integration_attempts WHERE task_id=? AND integration_id=?").run(row.task_id, row.integration_id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function historicalIntegrations(db) {
|
|
166
|
+
return db.prepare(`SELECT task_id,integration_id,payload FROM integration_attempts
|
|
167
|
+
WHERE json_extract(payload,'$.source.kind')='historical-change-sets' ORDER BY task_id,integration_id`)
|
|
168
|
+
.all();
|
|
169
|
+
}
|
|
170
|
+
function text(value) {
|
|
171
|
+
return typeof value === "string" && value.trim().length > 0 && !value.includes("\0");
|
|
172
|
+
}
|
|
173
|
+
/** The retired v34 artifact shape, frozen here rather than accepted by current
|
|
174
|
+
* GateArtifact readers. It is archived verbatim, never made into an L2 proof. */
|
|
175
|
+
function validateRetiredGate(value) {
|
|
176
|
+
requireIdentity(value.projectId, "Retired GateArtifact projectId");
|
|
177
|
+
for (const field of ["key", "planDigest", "toolchainDigest"]) {
|
|
178
|
+
if (typeof value[field] !== "string" || !/^[a-f0-9]{64}$/.test(value[field])) {
|
|
179
|
+
throw new Error(`Invalid retired GateArtifact ${field}.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const commit = (sha) => {
|
|
183
|
+
if (typeof sha !== "string" || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(sha))
|
|
184
|
+
throw new Error("Invalid retired gate commit.");
|
|
185
|
+
};
|
|
186
|
+
commit(value.commit);
|
|
187
|
+
if (value.boundary !== undefined) {
|
|
188
|
+
requireText(value.boundary.targetRef, "Retired gate target");
|
|
189
|
+
commit(value.boundary.baseHead);
|
|
190
|
+
}
|
|
191
|
+
const identity = { projectId: value.projectId, level: value.level, commit: value.commit,
|
|
192
|
+
planDigest: value.planDigest, toolchainDigest: value.toolchainDigest,
|
|
193
|
+
...(value.boundary === undefined ? {} : { boundary: value.boundary }) };
|
|
194
|
+
if (createHash("sha256").update(canonical(identity)).digest("hex") !== value.key) {
|
|
195
|
+
throw new Error("Retired gate key does not match its original identity.");
|
|
196
|
+
}
|
|
197
|
+
const names = new Set();
|
|
198
|
+
for (const step of value.steps) {
|
|
199
|
+
requireIdentity(step.name, "Retired gate step name");
|
|
200
|
+
if (names.has(step.name))
|
|
201
|
+
throw new Error("Duplicate retired gate step.");
|
|
202
|
+
names.add(step.name);
|
|
203
|
+
requireText(step.command, "Retired gate command");
|
|
204
|
+
if (!["passed", "failed", "skipped"].includes(step.outcome))
|
|
205
|
+
throw new Error("Invalid retired gate step outcome.");
|
|
206
|
+
requirePositiveInteger(step.durationMs + 1, "Retired gate duration");
|
|
207
|
+
requireText(step.logPath, "Retired gate log path");
|
|
208
|
+
if (step.logPath.startsWith("/") || step.logPath.includes("..")
|
|
209
|
+
|| typeof step.logDigest !== "string" || !/^[a-f0-9]{64}$/.test(step.logDigest)
|
|
210
|
+
|| !Number.isSafeInteger(step.logBytes) || step.logBytes < 0)
|
|
211
|
+
throw new Error("Invalid retired gate log metadata.");
|
|
212
|
+
}
|
|
213
|
+
if (Object.hasOwn(value, "potentialReuseCount"))
|
|
214
|
+
throw new Error("Invalid v34 retired gate counter.");
|
|
215
|
+
requirePositiveInteger(value.reuseCount + 1, "Retired gate reuse count");
|
|
216
|
+
requireTimestamp(value.createdAt, "Retired gate createdAt");
|
|
217
|
+
requireTimestamp(value.lastUsedAt, "Retired gate lastUsedAt");
|
|
218
|
+
if (value.completedAt !== undefined)
|
|
219
|
+
requireTimestamp(value.completedAt, "Retired gate completedAt");
|
|
220
|
+
}
|
|
221
|
+
function canonical(value) {
|
|
222
|
+
if (Array.isArray(value))
|
|
223
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
224
|
+
if (value !== null && typeof value === "object")
|
|
225
|
+
return `{${Object.keys(value).sort()
|
|
226
|
+
.map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
|
227
|
+
return JSON.stringify(value);
|
|
228
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** Frozen pre-fix encoding used only to recognize valid historical Jobs.
|
|
3
|
+
* Never use corrected execution semantics to reinterpret a released migration,
|
|
4
|
+
* and never use these helpers to create executable work or reusable evidence.
|
|
5
|
+
*/
|
|
6
|
+
export function historicalVerificationPlanDigest(plan) {
|
|
7
|
+
return createHash("sha256").update(JSON.stringify(canonicalize({
|
|
8
|
+
id: plan.id, version: plan.version, toolchain: plan.toolchain,
|
|
9
|
+
bootstrap: plan.bootstrap, l1: plan.l1, l2: plan.l2,
|
|
10
|
+
...(plan.l3 === undefined ? {} : { l3: plan.l3 })
|
|
11
|
+
}))).digest("hex");
|
|
12
|
+
}
|
|
13
|
+
export function historicalGateJobSteps(plan) {
|
|
14
|
+
const encode = (step, name) => ({
|
|
15
|
+
name,
|
|
16
|
+
command: step.shell === true ? step.argv.join(" ") : step.argv.map(value => /^[A-Za-z0-9_./:=@+-]+$/u.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`).join(" "),
|
|
17
|
+
argv: step.argv,
|
|
18
|
+
...(step.cwd === undefined ? {} : { cwd: step.cwd }),
|
|
19
|
+
...(step.env === undefined ? {} : { env: step.env })
|
|
20
|
+
});
|
|
21
|
+
return [
|
|
22
|
+
...plan.bootstrap.map((step, index) => encode(step, `bootstrap-${index + 1}`)),
|
|
23
|
+
...plan.l2.steps.map((step, index) => encode(step, `gate-${index + 1}`))
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
function canonicalize(value) {
|
|
27
|
+
if (Array.isArray(value))
|
|
28
|
+
return value.map(canonicalize);
|
|
29
|
+
if (typeof value === "object" && value !== null) {
|
|
30
|
+
return Object.fromEntries(Object.entries(value)
|
|
31
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
32
|
+
.map(([key, entry]) => [key, canonicalize(entry)]));
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { durableJobIdempotencyKey } from "../../job/durableJob.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveHistoricalVerificationPlan } from "./verificationPlanV1.js";
|
|
5
|
+
import { historicalGateJobSteps, historicalVerificationPlanDigest } from "./historicalVerificationPlan.js";
|
|
5
6
|
/** Classify only known v19 Git conflicts. Manual strategy and CAS blockers
|
|
6
7
|
* keep their meaning. Never synthesize candidate/Job success or edit history. */
|
|
7
8
|
export function migrateIntegrationContinuation(db) {
|
|
@@ -74,10 +75,10 @@ function migrateBoundFastForward(db, attempt) {
|
|
|
74
75
|
name: `check-${index + 1}`, command, timeoutMs: 30 * 60_000
|
|
75
76
|
}));
|
|
76
77
|
if (attempt.gatePlanDigest !== undefined) {
|
|
77
|
-
const plan =
|
|
78
|
-
if (plan === undefined ||
|
|
78
|
+
const plan = resolveHistoricalVerificationPlan(project);
|
|
79
|
+
if (plan === undefined || historicalVerificationPlanDigest(plan) !== attempt.gatePlanDigest)
|
|
79
80
|
return;
|
|
80
|
-
steps =
|
|
81
|
+
steps = historicalGateJobSteps(plan).map(step => ({
|
|
81
82
|
...step, timeoutMs: 30 * 60_000
|
|
82
83
|
}));
|
|
83
84
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
/** Frozen v36 projection. Execution/Review/protocol data remains in audit only;
|
|
3
|
+
* a metadata query must not require those unrelated contracts to stay current. */
|
|
4
|
+
export function migrateNarrowAgentFailureContext(db) {
|
|
5
|
+
const rows = db.prepare("SELECT task_id,event_id,payload FROM events WHERE type='runtime.agent-error'").all();
|
|
6
|
+
const archive = db.prepare(`INSERT INTO storage_migration_archive
|
|
7
|
+
(migration_version,family,record_key,payload,content) VALUES(37,?,?,?,NULL)`);
|
|
8
|
+
const update = db.prepare("UPDATE events SET payload=? WHERE task_id=? AND event_id=?");
|
|
9
|
+
for (const row of rows) {
|
|
10
|
+
const event = JSON.parse(row.payload);
|
|
11
|
+
const raw = event?.payload?.capabilityContext;
|
|
12
|
+
if (typeof raw !== "string")
|
|
13
|
+
throw new Error(`Missing v36 failure context: ${row.task_id}/${row.event_id}.`);
|
|
14
|
+
const context = JSON.parse(raw);
|
|
15
|
+
const key = JSON.stringify([row.task_id, row.event_id]);
|
|
16
|
+
const emptyIdentities = {};
|
|
17
|
+
for (const field of ["runId", "nativeSessionId", "nativeTurnId"]) {
|
|
18
|
+
if (event.payload[field] !== "")
|
|
19
|
+
continue;
|
|
20
|
+
emptyIdentities[field] = "";
|
|
21
|
+
delete event.payload[field];
|
|
22
|
+
}
|
|
23
|
+
if (Object.keys(emptyIdentities).length) {
|
|
24
|
+
archive.run("agent-error-empty-identities", key, JSON.stringify(emptyIdentities));
|
|
25
|
+
}
|
|
26
|
+
if (context?.status === "unavailable" && text(context.reason)) {
|
|
27
|
+
if (Object.keys(emptyIdentities).length)
|
|
28
|
+
update.run(JSON.stringify(event), row.task_id, row.event_id);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const old = context?.effective;
|
|
32
|
+
if (context?.status !== "recorded" || !/^[a-f0-9]{64}$/.test(context.agentFingerprint)
|
|
33
|
+
|| old?.schemaVersion !== 4 || !text(old.agentId)
|
|
34
|
+
|| !["codex", "claude", "acp"].includes(old.adapterId)
|
|
35
|
+
|| !text(old.workspace?.root) || !isAbsolute(old.workspace.root)) {
|
|
36
|
+
throw new Error(`Invalid v36 failure context: ${row.task_id}/${row.event_id}.`);
|
|
37
|
+
}
|
|
38
|
+
const config = { adapterId: old.adapterId };
|
|
39
|
+
for (const key of ["model", "effort", ...(old.adapterId === "codex" ? ["profile"] : []),
|
|
40
|
+
...(old.adapterId === "claude" ? ["settingsFile"] : [])]) {
|
|
41
|
+
if (old[key] === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
if (!text(old[key]) || key === "settingsFile" && !isAbsolute(old[key])) {
|
|
44
|
+
throw new Error(`Invalid v36 failure ${key}: ${row.task_id}/${row.event_id}.`);
|
|
45
|
+
}
|
|
46
|
+
config[key] = old[key];
|
|
47
|
+
}
|
|
48
|
+
if (old.adapterId === "claude" && old.settingsSources !== undefined) {
|
|
49
|
+
if (!Array.isArray(old.settingsSources) || old.settingsSources.some((source) => !text(source))
|
|
50
|
+
|| new Set(old.settingsSources).size !== old.settingsSources.length) {
|
|
51
|
+
throw new Error(`Invalid v36 settings sources: ${row.task_id}/${row.event_id}.`);
|
|
52
|
+
}
|
|
53
|
+
config.settingsSources = old.settingsSources;
|
|
54
|
+
}
|
|
55
|
+
archive.run("agent-error-context", key, raw);
|
|
56
|
+
event.payload.capabilityContext = JSON.stringify({
|
|
57
|
+
status: "recorded", agentId: old.agentId, cwd: old.workspace.root, config,
|
|
58
|
+
agentFingerprint: context.agentFingerprint
|
|
59
|
+
});
|
|
60
|
+
update.run(JSON.stringify(event), row.task_id, row.event_id);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function text(value) {
|
|
64
|
+
return typeof value === "string" && value.trim().length > 0 && !value.includes("\0");
|
|
65
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function runLinkedWakes(db) {
|
|
2
|
+
return db.prepare(`SELECT task_id, wake_id, seq, turn_id, payload FROM task_wakes
|
|
3
|
+
WHERE turn_id IS NOT NULL OR json_type(payload, '$.runId') IS NOT NULL
|
|
4
|
+
ORDER BY task_id, seq`).all();
|
|
5
|
+
}
|
|
6
|
+
/** Read only stable SQL identities, including prefixes before payload key renames. */
|
|
7
|
+
export function preflightNotificationOnlyWakes(db) {
|
|
8
|
+
assertQuiescent(db, runLinkedWakes(db));
|
|
9
|
+
}
|
|
10
|
+
/** Frozen 29→30 transform. Old execution-linked wakes become audit events;
|
|
11
|
+
* accepted/pending notifications keep their exact identity and delivery state. */
|
|
12
|
+
export function migrateNotificationOnlyWakes(db) {
|
|
13
|
+
const rows = runLinkedWakes(db);
|
|
14
|
+
// Check every retiring reference before changing any data. The outer
|
|
15
|
+
// migration transaction also rolls back on any later failure.
|
|
16
|
+
for (const row of rows) {
|
|
17
|
+
const value = JSON.parse(row.payload);
|
|
18
|
+
if (typeof value.runId !== "string" || value.runId !== row.turn_id) {
|
|
19
|
+
throw new Error(`Run-linked wake identity is inconsistent: ${row.task_id}/${row.wake_id}.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
assertQuiescent(db, rows);
|
|
23
|
+
const counters = new Map();
|
|
24
|
+
const readCounter = db.prepare(`SELECT max(
|
|
25
|
+
coalesce((SELECT high_water FROM id_sequences WHERE task_id = ? AND kind = 'event'), 0),
|
|
26
|
+
coalesce((SELECT max(CAST(substr(event_id, 7) AS INTEGER)) FROM events WHERE task_id = ?), 0)
|
|
27
|
+
) AS value`);
|
|
28
|
+
const saveCounter = db.prepare(`INSERT INTO id_sequences(task_id, kind, high_water) VALUES (?, ?, ?)
|
|
29
|
+
ON CONFLICT(task_id, kind) DO UPDATE SET high_water = max(high_water, excluded.high_water)`);
|
|
30
|
+
const saveEvent = db.prepare("INSERT INTO events(task_id, event_id, type, occurred_at, payload) VALUES (?, ?, ?, ?, ?)");
|
|
31
|
+
const removeWake = db.prepare("DELETE FROM task_wakes WHERE task_id = ? AND wake_id = ?");
|
|
32
|
+
const at = new Date().toISOString();
|
|
33
|
+
for (const row of rows) {
|
|
34
|
+
const sequence = (counters.get(row.task_id)
|
|
35
|
+
?? readCounter.get(row.task_id, row.task_id).value) + 1;
|
|
36
|
+
counters.set(row.task_id, sequence);
|
|
37
|
+
const event = {
|
|
38
|
+
schemaVersion: 2, id: `event-${sequence}`, taskId: row.task_id,
|
|
39
|
+
type: "wake.run-link-retired", createdAt: at,
|
|
40
|
+
payload: { wakeId: row.wake_id, runId: row.turn_id,
|
|
41
|
+
record: row.payload, disposition: "retired-without-replay" }
|
|
42
|
+
};
|
|
43
|
+
saveEvent.run(row.task_id, event.id, event.type, at, JSON.stringify(event));
|
|
44
|
+
saveCounter.run(row.task_id, "event", sequence);
|
|
45
|
+
saveCounter.run(row.task_id, "taskWake", row.seq);
|
|
46
|
+
removeWake.run(row.task_id, row.wake_id);
|
|
47
|
+
}
|
|
48
|
+
db.exec(`
|
|
49
|
+
ALTER TABLE task_wakes DROP COLUMN turn_id;
|
|
50
|
+
UPDATE task_wakes SET payload = json_set(payload, '$.schemaVersion', 2);
|
|
51
|
+
UPDATE global_role_session_sets
|
|
52
|
+
SET payload = json_set(payload, '$.providerBinding', json('null'))
|
|
53
|
+
WHERE json_type(payload, '$.providerBinding') IS NULL;
|
|
54
|
+
`);
|
|
55
|
+
}
|
|
56
|
+
function assertQuiescent(db, rows) {
|
|
57
|
+
const activeRun = db.prepare("SELECT 1 FROM turns WHERE task_id = ? AND turn_id = ? AND status = 'active'");
|
|
58
|
+
const claimedWake = db.prepare(`SELECT 1 FROM mailboxes WHERE task_id = ?
|
|
59
|
+
AND json_extract(processing, '$.owner') = ? LIMIT 1`);
|
|
60
|
+
const pendingInput = db.prepare(`SELECT 1 FROM role_session_sets WHERE task_id = ? AND (
|
|
61
|
+
(json_extract(payload, '$.providerBinding.run.status') IN ('submitting', 'accepted', 'delivery-unknown')
|
|
62
|
+
AND (json_extract(payload, '$.providerBinding.run.runId') = ?
|
|
63
|
+
OR json_extract(payload, '$.providerBinding.run.input.wakeId') = ?))
|
|
64
|
+
OR (json_extract(payload, '$.providerBinding.retry.status') IN ('waiting', 'in-flight')
|
|
65
|
+
AND (json_extract(payload, '$.providerBinding.retry.input.runId') = ?
|
|
66
|
+
OR json_extract(payload, '$.providerBinding.retry.input.wakeId') = ?))) LIMIT 1`);
|
|
67
|
+
for (const row of rows) {
|
|
68
|
+
if (activeRun.get(row.task_id, row.turn_id)
|
|
69
|
+
|| claimedWake.get(row.task_id, `leader-notification:${row.wake_id}`)
|
|
70
|
+
|| pendingInput.get(row.task_id, row.turn_id, row.wake_id, row.turn_id, row.wake_id)) {
|
|
71
|
+
throw new Error(`Run-linked wake still owns unsettled input: ${row.task_id}/${row.wake_id}, Run ${row.turn_id}. Settle or stop that exact execution before upgrading.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|