@zq-silk/yui 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/agent/managedRuntimeEnvironment.js +2 -1
- package/dist/cli/commandCatalog.js +251 -13
- package/dist/cli/updateOrchestrator.js +8 -0
- package/dist/cli/updatePorts.js +76 -22
- package/dist/cli.js +264 -20
- package/dist/commands/configCommands.js +83 -9
- package/dist/commands/controllerCommands.js +103 -0
- package/dist/commands/deliveryGuardPreflight.js +30 -0
- package/dist/commands/durableJobCommands.js +231 -0
- package/dist/commands/executionAuditCommands.js +193 -0
- package/dist/commands/grantCommands.js +374 -0
- package/dist/commands/projectCommands.js +119 -81
- package/dist/commands/releaseCommands.js +444 -0
- package/dist/commands/resourcesCommands.js +274 -0
- package/dist/commands/sessionCommands.js +104 -0
- package/dist/commands/taskActor.js +117 -0
- package/dist/commands/taskChangeSetCommands.js +60 -0
- package/dist/commands/taskCommands.js +618 -202
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +33 -6
- package/dist/commands/taskInputCommands.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +136 -33
- package/dist/commands/taskIntegrationQueueCommands.js +228 -0
- package/dist/commands/taskNextActionCommand.js +100 -0
- package/dist/commands/taskOverlapCommands.js +120 -0
- package/dist/commands/taskOverviewCommand.js +36 -8
- package/dist/commands/telemetryCommands.js +330 -0
- package/dist/commands/workflowCommands.js +415 -0
- package/dist/config/yuiConfig.js +62 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +402 -56
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
- package/dist/controller/handoverCandidate.js +168 -0
- package/dist/controller/jobClient.js +102 -0
- package/dist/controller/jobControl.js +613 -0
- package/dist/controller/jobSupervisor.js +498 -0
- package/dist/controller/providerHookRunFence.js +34 -5
- package/dist/controller/resourceCleanupLinux.js +18 -9
- package/dist/controller/resourceInventoryLinux.js +90 -39
- package/dist/controller/runtime.js +165 -15
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +297 -58
- package/dist/controller/sessionOwnerReconciliation.js +321 -0
- package/dist/core/controllerServer.js +416 -27
- package/dist/core/controllerTelemetry.js +167 -0
- package/dist/doctor/doctor.js +113 -16
- package/dist/domain/validation.js +9 -0
- package/dist/execution/executionGroup.js +40 -3
- package/dist/executor/agentExecutor.js +6 -3
- package/dist/executor/effectiveLaunch.js +52 -0
- package/dist/executor/executorRegistry.js +50 -0
- package/dist/executor/fileRoleLaunchPlanner.js +61 -6
- package/dist/grant/capabilityGrant.js +282 -0
- package/dist/integration/changeSet.js +16 -3
- package/dist/integration/changeSetManifest.js +46 -0
- package/dist/integration/gitIntegrationService.js +528 -147
- package/dist/integration/integrationAttempt.js +54 -5
- package/dist/integration/integrationQueueEntry.js +221 -0
- package/dist/integration/integrationQueueService.js +955 -0
- package/dist/integration/manifestTags.js +99 -0
- package/dist/integration/overlapDiagnostics.js +211 -0
- package/dist/job/durableJob.js +449 -0
- package/dist/job/jobRunner.js +350 -0
- package/dist/lifecycle/exactRunTerminalization.js +24 -2
- package/dist/lifecycle/providerErrorClass.js +126 -0
- package/dist/message/message.js +16 -3
- package/dist/observability/executionAudit.js +545 -0
- package/dist/observability/faultClassification.js +160 -0
- package/dist/observability/runtimeIdentity.js +367 -0
- package/dist/release/fakeReleasePorts.js +55 -0
- package/dist/release/releaseHandover.js +475 -0
- package/dist/release/releaseIdempotencyStore.js +165 -0
- package/dist/release/releaseWorkflow.js +459 -0
- package/dist/release/releaseWorkflowEngine.js +688 -0
- package/dist/release/releaseWorkflowPorts.js +1720 -0
- package/dist/release/runtimeRelease.js +495 -0
- package/dist/release/workflowFileLock.js +218 -0
- package/dist/repository/gitWorkspace.js +177 -1
- package/dist/repository/projectMaintenanceLock.js +315 -0
- package/dist/repository/taskWorkspaceCoordinator.js +87 -17
- package/dist/repository/taskWorkspacePreparer.js +1091 -517
- package/dist/resources/autoResourceGc.js +116 -0
- package/dist/resources/liveReferences.js +574 -0
- package/dist/resources/resourceDiscovery.js +477 -0
- package/dist/resources/resourceGc.js +645 -0
- package/dist/resources/resourceRegistrar.js +256 -0
- package/dist/resources/resourceRegistry.js +150 -0
- package/dist/resources/resourceRegistryStore.js +41 -0
- package/dist/resources/resourceTypes.js +42 -0
- package/dist/resources/sqliteResourceRegistry.js +111 -0
- package/dist/review/reviewConfig.js +10 -0
- package/dist/review/reviewFinding.js +240 -0
- package/dist/review/reviewFindingLedger.js +545 -0
- package/dist/review/reviewOutcomeClassifier.js +61 -0
- package/dist/review/reviewRound.js +56 -4
- package/dist/run/agentRun.js +80 -4
- package/dist/run/providerRetry.js +84 -0
- package/dist/run/providerRetryConfig.js +63 -0
- package/dist/run/yieldReceipt.js +65 -0
- package/dist/runtime/exactControlPlane.js +79 -2
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/sessionOwnerIdentity.js +269 -0
- package/dist/runtime/sessionOwnerRegistry.js +132 -0
- package/dist/runtime/sessionReconciliation.js +93 -0
- package/dist/runtime/sessionTerminationGuard.js +211 -0
- package/dist/runtime/taskRuntimeIsolation.js +13 -0
- package/dist/runtime/tmuxAdapters.js +34 -1
- package/dist/scheduler/actionability.js +155 -0
- package/dist/scheduler/activeRoleRunDelivery.js +14 -5
- package/dist/scheduler/activeTaskProgress.js +60 -0
- package/dist/scheduler/leaderWakeupProcessor.js +22 -11
- package/dist/scheduler/roleRunStall.js +135 -29
- package/dist/scheduler/taskExecutionProjection.js +11 -0
- package/dist/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +736 -1
- package/dist/storage/sqliteSchema.js +264 -3
- package/dist/storage/sqliteStore.js +487 -13
- package/dist/storage/storeRpc.js +21 -0
- package/dist/storage/taskStore.js +974 -21
- package/dist/storage/upgrade/homeClassification.js +120 -2
- package/dist/storage/upgrade/migrationReceipt.js +67 -0
- package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
- package/dist/storage/upgrade/recordVersions.js +10 -1
- package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
- package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +738 -0
- package/dist/task/repairWave.js +137 -0
- package/dist/task/taskRecordReference.js +6 -1
- package/dist/telemetry/sqliteTelemetryStore.js +387 -0
- package/dist/telemetry/telemetryCompaction.js +251 -0
- package/dist/telemetry/telemetryConfig.js +64 -0
- package/dist/telemetry/telemetryRouter.js +32 -0
- package/dist/telemetry/telemetryStore.js +19 -0
- package/dist/telemetry/telemetryWiring.js +33 -0
- package/dist/tmux/tmuxManager.js +20 -1
- package/dist/tmux/tmuxSocketEndpoint.js +20 -0
- package/dist/verification/gateArtifact.js +216 -0
- package/dist/verification/gateArtifactStore.js +87 -0
- package/dist/verification/verificationGateService.js +414 -0
- package/dist/verification/verificationPlan.js +308 -0
- package/dist/workspace/gitChangeSetCapture.js +12 -2
- package/dist/workspace/workItemChangeSetManager.js +60 -3
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -0
|
@@ -4,7 +4,7 @@ import { normalizeCheckResult } from "./checkResult.js";
|
|
|
4
4
|
export function createIntegrationAttempt(input, now) {
|
|
5
5
|
const timestamp = now.toISOString();
|
|
6
6
|
return validateIntegrationAttempt({
|
|
7
|
-
schemaVersion:
|
|
7
|
+
schemaVersion: 4,
|
|
8
8
|
id: input.id,
|
|
9
9
|
taskId: input.taskId,
|
|
10
10
|
projectId: input.projectId,
|
|
@@ -17,6 +17,26 @@ export function createIntegrationAttempt(input, now) {
|
|
|
17
17
|
updatedAt: timestamp
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Bind the check DurableJob to a running attempt. Set-once: the job is the
|
|
22
|
+
* durable source of truth for the checks, so a retry of the start path must
|
|
23
|
+
* find the recorded job instead of spawning a second one.
|
|
24
|
+
*/
|
|
25
|
+
export function recordIntegrationCheckJob(attempt, jobId, now) {
|
|
26
|
+
validateIntegrationAttempt(attempt);
|
|
27
|
+
if (attempt.status !== "running") {
|
|
28
|
+
throw new Error(`Integration check job can only bind a running attempt: ${attempt.status}.`);
|
|
29
|
+
}
|
|
30
|
+
if (attempt.jobId !== undefined) {
|
|
31
|
+
throw new Error(`Integration check job is already recorded: ${attempt.jobId}.`);
|
|
32
|
+
}
|
|
33
|
+
validateTaskRecordReference({ taskId: attempt.taskId, localId: jobId }, "durableJob");
|
|
34
|
+
return validateIntegrationAttempt({
|
|
35
|
+
...attempt,
|
|
36
|
+
jobId,
|
|
37
|
+
updatedAt: now.toISOString()
|
|
38
|
+
});
|
|
39
|
+
}
|
|
20
40
|
export function requireLeaderDecision(attempt, report, now) {
|
|
21
41
|
validateIntegrationAttempt(attempt);
|
|
22
42
|
const continuingAfterResolution = attempt.status === "blocked"
|
|
@@ -54,10 +74,11 @@ export function recordResolutionDecision(attempt, decision, now) {
|
|
|
54
74
|
...(decision.action === "reject" ? { endedAt: timestamp } : {})
|
|
55
75
|
});
|
|
56
76
|
}
|
|
77
|
+
const TERMINAL_STATUSES = ["committed", "superseded", "failed"];
|
|
57
78
|
export function updateIntegrationAttempt(attempt, patch, now) {
|
|
58
79
|
validateIntegrationAttempt(attempt);
|
|
59
80
|
const status = patch.status ?? attempt.status;
|
|
60
|
-
const terminal =
|
|
81
|
+
const terminal = TERMINAL_STATUSES.includes(status);
|
|
61
82
|
const updated = {
|
|
62
83
|
...attempt,
|
|
63
84
|
...patch,
|
|
@@ -71,9 +92,33 @@ export function updateIntegrationAttempt(attempt, patch, now) {
|
|
|
71
92
|
}
|
|
72
93
|
return validateIntegrationAttempt(updated);
|
|
73
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Mark a committed Integration as superseded (obsolete). A superseded
|
|
97
|
+
* Integration retains its evidence but is excluded from latest-committed
|
|
98
|
+
* selection, allowing the next valid committed Integration to become the
|
|
99
|
+
* Task's delivery baseline. Only a committed Integration may be superseded;
|
|
100
|
+
* a reason is required for the audit trail.
|
|
101
|
+
*/
|
|
102
|
+
export function supersedeIntegration(attempt, reason, now) {
|
|
103
|
+
validateIntegrationAttempt(attempt);
|
|
104
|
+
if (attempt.status !== "committed") {
|
|
105
|
+
throw new Error(`Integration cannot be superseded from ${attempt.status}: ${attempt.id}.`);
|
|
106
|
+
}
|
|
107
|
+
const timestamp = now.toISOString();
|
|
108
|
+
return validateIntegrationAttempt({
|
|
109
|
+
...attempt,
|
|
110
|
+
status: "superseded",
|
|
111
|
+
checks: [
|
|
112
|
+
...(attempt.checks ?? []),
|
|
113
|
+
{ name: "superseded", outcome: "failed", details: requireText(reason, "Supersede reason") }
|
|
114
|
+
],
|
|
115
|
+
updatedAt: timestamp,
|
|
116
|
+
endedAt: timestamp
|
|
117
|
+
});
|
|
118
|
+
}
|
|
74
119
|
export function validateIntegrationAttempt(attempt) {
|
|
75
|
-
if (attempt.schemaVersion !==
|
|
76
|
-
throw new Error("IntegrationAttempt must use schemaVersion
|
|
120
|
+
if (attempt.schemaVersion !== 4) {
|
|
121
|
+
throw new Error("IntegrationAttempt must use schemaVersion 4.");
|
|
77
122
|
}
|
|
78
123
|
validateTaskRecordReference({
|
|
79
124
|
taskId: attempt.taskId,
|
|
@@ -93,11 +138,15 @@ export function validateIntegrationAttempt(attempt) {
|
|
|
93
138
|
if (attempt.candidateCommit !== undefined) {
|
|
94
139
|
requireCommit(attempt.candidateCommit, "Integration candidate commit");
|
|
95
140
|
}
|
|
141
|
+
if (attempt.jobId !== undefined) {
|
|
142
|
+
validateTaskRecordReference({ taskId: attempt.taskId, localId: attempt.jobId }, "durableJob");
|
|
143
|
+
}
|
|
96
144
|
if (![
|
|
97
145
|
"running",
|
|
98
146
|
"blocked",
|
|
99
147
|
"validating",
|
|
100
148
|
"committed",
|
|
149
|
+
"superseded",
|
|
101
150
|
"failed"
|
|
102
151
|
].includes(attempt.status)) {
|
|
103
152
|
throw new Error(`Integration status is invalid: ${String(attempt.status)}.`);
|
|
@@ -121,7 +170,7 @@ export function validateIntegrationAttempt(attempt) {
|
|
|
121
170
|
attempt.checks?.forEach(normalizeCheckResult);
|
|
122
171
|
requireTimestamp(attempt.createdAt, "Integration Attempt createdAt");
|
|
123
172
|
requireTimestamp(attempt.updatedAt, "Integration Attempt updatedAt");
|
|
124
|
-
if (
|
|
173
|
+
if (TERMINAL_STATUSES.includes(attempt.status)) {
|
|
125
174
|
requireTimestamp(attempt.endedAt ?? "", "Integration Attempt endedAt");
|
|
126
175
|
}
|
|
127
176
|
return attempt;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { normalizedUniqueIdentities, normalizedUniqueText, requireIdentity, requireText, requireTimestamp } from "../domain/validation.js";
|
|
2
|
+
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
3
|
+
export function createIntegrationQueueEntry(input, now) {
|
|
4
|
+
const timestamp = now.toISOString();
|
|
5
|
+
return validateIntegrationQueueEntry({
|
|
6
|
+
schemaVersion: 1,
|
|
7
|
+
id: input.id,
|
|
8
|
+
taskId: input.taskId,
|
|
9
|
+
projectId: input.projectId,
|
|
10
|
+
changeSetId: input.changeSetId,
|
|
11
|
+
targetRef: input.targetRef,
|
|
12
|
+
status: "queued",
|
|
13
|
+
checkCommands: normalizedUniqueText(input.checkCommands ?? [], "Integration queue check command"),
|
|
14
|
+
evidenceRefs: normalizedUniqueText(input.evidenceRefs ?? [], "Integration queue evidence"),
|
|
15
|
+
createdAt: timestamp,
|
|
16
|
+
updatedAt: timestamp
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* An entry whose ChangeSet is already represented on the target (an ancestor
|
|
21
|
+
* or a same-tree commit) converges directly to committed with its proof. The
|
|
22
|
+
* entry still names the committed IntegrationAttempt that recorded the
|
|
23
|
+
* convergence, so every acceptance and provenance consumer reads one
|
|
24
|
+
* integration authority.
|
|
25
|
+
*/
|
|
26
|
+
export function createConvergedIntegrationQueueEntry(input, now) {
|
|
27
|
+
const timestamp = now.toISOString();
|
|
28
|
+
return validateIntegrationQueueEntry({
|
|
29
|
+
schemaVersion: 1,
|
|
30
|
+
id: input.id,
|
|
31
|
+
taskId: input.taskId,
|
|
32
|
+
projectId: input.projectId,
|
|
33
|
+
changeSetId: input.changeSetId,
|
|
34
|
+
targetRef: input.targetRef,
|
|
35
|
+
status: "committed",
|
|
36
|
+
checkCommands: [],
|
|
37
|
+
targetBefore: input.targetHead,
|
|
38
|
+
targetAfter: input.targetHead,
|
|
39
|
+
evidenceRefs: [input.proof],
|
|
40
|
+
...(input.integrationAttemptId === undefined
|
|
41
|
+
? {}
|
|
42
|
+
: { integrationAttemptId: input.integrationAttemptId }),
|
|
43
|
+
createdAt: timestamp,
|
|
44
|
+
updatedAt: timestamp,
|
|
45
|
+
endedAt: timestamp
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function markIntegrationQueueRunning(entry, targetBefore, now) {
|
|
49
|
+
validateIntegrationQueueEntry(entry);
|
|
50
|
+
if (!["queued", "conflicted", "validated"].includes(entry.status)) {
|
|
51
|
+
throw new Error(`Integration queue entry cannot run from ${entry.status}: ${entry.id}.`);
|
|
52
|
+
}
|
|
53
|
+
const { conflictSummary: _previousConflict, ...rest } = entry;
|
|
54
|
+
return validateIntegrationQueueEntry({
|
|
55
|
+
...rest,
|
|
56
|
+
status: "running",
|
|
57
|
+
targetBefore,
|
|
58
|
+
updatedAt: now.toISOString()
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function recordIntegrationQueueAttempt(entry, integrationAttemptId, now) {
|
|
62
|
+
validateIntegrationQueueEntry(entry);
|
|
63
|
+
if (entry.status !== "running") {
|
|
64
|
+
throw new Error(`Integration queue entry is not running: ${entry.id}/${entry.status}.`);
|
|
65
|
+
}
|
|
66
|
+
return validateIntegrationQueueEntry({
|
|
67
|
+
...entry,
|
|
68
|
+
integrationAttemptId,
|
|
69
|
+
updatedAt: now.toISOString()
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export function markIntegrationQueueBlocked(entry, conflictSummary, now) {
|
|
73
|
+
validateIntegrationQueueEntry(entry);
|
|
74
|
+
if (entry.status !== "running") {
|
|
75
|
+
throw new Error(`Integration queue entry is not running: ${entry.id}/${entry.status}.`);
|
|
76
|
+
}
|
|
77
|
+
return validateIntegrationQueueEntry({
|
|
78
|
+
...entry,
|
|
79
|
+
status: "conflicted",
|
|
80
|
+
conflictSummary: requireText(conflictSummary, "Integration queue conflict summary"),
|
|
81
|
+
updatedAt: now.toISOString()
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export function markIntegrationQueueCommitted(entry, targetAfter, now) {
|
|
85
|
+
validateIntegrationQueueEntry(entry);
|
|
86
|
+
if (entry.status !== "running" && entry.status !== "conflicted") {
|
|
87
|
+
throw new Error(`Integration queue entry cannot commit from ${entry.status}: ${entry.id}.`);
|
|
88
|
+
}
|
|
89
|
+
const timestamp = now.toISOString();
|
|
90
|
+
return validateIntegrationQueueEntry({
|
|
91
|
+
...entry,
|
|
92
|
+
status: "committed",
|
|
93
|
+
targetAfter,
|
|
94
|
+
updatedAt: timestamp,
|
|
95
|
+
endedAt: timestamp
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Records that this entry's reusable evidence was checked against an exact
|
|
100
|
+
* target head. The evidence only covers `evidenceTargetHead`; a target
|
|
101
|
+
* advance invalidates it unless the delta is provably empty. Path
|
|
102
|
+
* disjointness alone never justifies this transition: a gate may read any
|
|
103
|
+
* file, so a non-empty target increment cannot be proven irrelevant.
|
|
104
|
+
*/
|
|
105
|
+
export function markIntegrationQueueValidated(entry, now, evidenceTargetHead) {
|
|
106
|
+
validateIntegrationQueueEntry(entry);
|
|
107
|
+
if (entry.status !== "queued") {
|
|
108
|
+
throw new Error(`Integration queue entry cannot validate from ${entry.status}: ${entry.id}.`);
|
|
109
|
+
}
|
|
110
|
+
if (entry.evidenceRefs.length === 0) {
|
|
111
|
+
throw new Error(`Integration queue entry has no reusable evidence: ${entry.id}.`);
|
|
112
|
+
}
|
|
113
|
+
return validateIntegrationQueueEntry({
|
|
114
|
+
...entry,
|
|
115
|
+
status: "validated",
|
|
116
|
+
...(evidenceTargetHead === undefined ? {} : { evidenceTargetHead }),
|
|
117
|
+
updatedAt: now.toISOString()
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* A validated entry whose ChangeSet became affected by a later target advance
|
|
122
|
+
* loses its evidence coverage and runs its checks again; a conflicted entry is
|
|
123
|
+
* manually retried after its underlying issue was fixed.
|
|
124
|
+
*/
|
|
125
|
+
export function markIntegrationQueueRequeued(entry, now) {
|
|
126
|
+
validateIntegrationQueueEntry(entry);
|
|
127
|
+
if (entry.status !== "validated" && entry.status !== "conflicted") {
|
|
128
|
+
throw new Error(`Integration queue entry cannot requeue from ${entry.status}: ${entry.id}.`);
|
|
129
|
+
}
|
|
130
|
+
return validateIntegrationQueueEntry({
|
|
131
|
+
...entry,
|
|
132
|
+
status: "queued",
|
|
133
|
+
updatedAt: now.toISOString()
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
export function recordIntegrationQueueAffectedPaths(entry, affectedPaths, now) {
|
|
137
|
+
validateIntegrationQueueEntry(entry);
|
|
138
|
+
if (!["queued", "validated"].includes(entry.status)) {
|
|
139
|
+
throw new Error(`Integration queue entry cannot recompute overlap from ${entry.status}: ${entry.id}.`);
|
|
140
|
+
}
|
|
141
|
+
const previous = new Set(entry.affectedPaths ?? []);
|
|
142
|
+
const merged = [...new Set([...previous, ...affectedPaths])].sort();
|
|
143
|
+
return validateIntegrationQueueEntry({
|
|
144
|
+
...entry,
|
|
145
|
+
affectedPaths: merged,
|
|
146
|
+
updatedAt: now.toISOString()
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
export function markIntegrationQueueSuperseded(entry, reason, now) {
|
|
150
|
+
validateIntegrationQueueEntry(entry);
|
|
151
|
+
if (!["queued", "conflicted", "validated"].includes(entry.status)) {
|
|
152
|
+
throw new Error(`Integration queue entry cannot be superseded from ${entry.status}: ${entry.id}.`);
|
|
153
|
+
}
|
|
154
|
+
const timestamp = now.toISOString();
|
|
155
|
+
return validateIntegrationQueueEntry({
|
|
156
|
+
...entry,
|
|
157
|
+
status: "superseded",
|
|
158
|
+
supersedeReason: requireText(reason, "Integration queue supersede reason"),
|
|
159
|
+
updatedAt: timestamp,
|
|
160
|
+
endedAt: timestamp
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
export function validateIntegrationQueueEntry(entry) {
|
|
164
|
+
if (entry.schemaVersion !== 1) {
|
|
165
|
+
throw new Error("IntegrationQueueEntry must use schemaVersion 1.");
|
|
166
|
+
}
|
|
167
|
+
validateTaskRecordReference({ taskId: entry.taskId, localId: entry.id }, "integrationQueue");
|
|
168
|
+
validateTaskRecordReference({ taskId: entry.taskId, localId: entry.changeSetId }, "changeSet");
|
|
169
|
+
requireIdentity(entry.projectId, "Project id");
|
|
170
|
+
requireText(entry.targetRef, "Integration queue target ref");
|
|
171
|
+
normalizedUniqueText(entry.checkCommands, "Integration queue check command");
|
|
172
|
+
normalizedUniqueText(entry.evidenceRefs, "Integration queue evidence");
|
|
173
|
+
if (![
|
|
174
|
+
"queued",
|
|
175
|
+
"running",
|
|
176
|
+
"conflicted",
|
|
177
|
+
"validated",
|
|
178
|
+
"committed",
|
|
179
|
+
"superseded"
|
|
180
|
+
].includes(entry.status)) {
|
|
181
|
+
throw new Error(`Integration queue status is invalid: ${String(entry.status)}.`);
|
|
182
|
+
}
|
|
183
|
+
if (entry.targetBefore !== undefined)
|
|
184
|
+
requireCommit(entry.targetBefore, "Integration queue targetBefore");
|
|
185
|
+
if (entry.targetAfter !== undefined)
|
|
186
|
+
requireCommit(entry.targetAfter, "Integration queue targetAfter");
|
|
187
|
+
if (entry.evidenceTargetHead !== undefined) {
|
|
188
|
+
requireCommit(entry.evidenceTargetHead, "Integration queue evidenceTargetHead");
|
|
189
|
+
}
|
|
190
|
+
if (entry.integrationAttemptId !== undefined) {
|
|
191
|
+
normalizedUniqueIdentities([entry.integrationAttemptId], "Integration attempt id");
|
|
192
|
+
}
|
|
193
|
+
if (entry.conflictSummary !== undefined) {
|
|
194
|
+
requireText(entry.conflictSummary, "Integration queue conflict summary");
|
|
195
|
+
}
|
|
196
|
+
if (entry.affectedPaths !== undefined) {
|
|
197
|
+
normalizedUniqueText(entry.affectedPaths, "Integration queue affected path");
|
|
198
|
+
}
|
|
199
|
+
if (entry.supersedeReason !== undefined) {
|
|
200
|
+
requireText(entry.supersedeReason, "Integration queue supersede reason");
|
|
201
|
+
}
|
|
202
|
+
requireTimestamp(entry.createdAt, "Integration queue entry createdAt");
|
|
203
|
+
requireTimestamp(entry.updatedAt, "Integration queue entry updatedAt");
|
|
204
|
+
if (["committed", "superseded"].includes(entry.status)) {
|
|
205
|
+
requireTimestamp(entry.endedAt ?? "", "Integration queue entry endedAt");
|
|
206
|
+
}
|
|
207
|
+
if (entry.status === "committed") {
|
|
208
|
+
requireCommit(entry.targetAfter ?? "", "Integration queue targetAfter");
|
|
209
|
+
}
|
|
210
|
+
if (entry.status === "running") {
|
|
211
|
+
requireCommit(entry.targetBefore ?? "", "Integration queue targetBefore");
|
|
212
|
+
}
|
|
213
|
+
return entry;
|
|
214
|
+
}
|
|
215
|
+
function requireCommit(value, label) {
|
|
216
|
+
const normalized = requireText(value, label).toLowerCase();
|
|
217
|
+
if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(normalized)) {
|
|
218
|
+
throw new Error(`${label} is invalid.`);
|
|
219
|
+
}
|
|
220
|
+
return normalized;
|
|
221
|
+
}
|