@abelxiaoxing/cadence 1.8.4 → 2.0.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/README.md +324 -117
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/agents/design-explorer.md +14 -18
- package/agents/diagnosis-worker.md +14 -14
- package/agents/implementation-worker.md +16 -17
- package/config/workflow-task.example.json +7 -0
- package/docs/remote-access.md +60 -0
- package/docs/reuse-and-distribution.md +44 -0
- package/docs/service-management.md +108 -0
- package/licenses/bundled-web.txt +6245 -0
- package/licenses/pi-web-ui-MIT.txt +21 -0
- package/licenses/pi-web-ui-reuse.md +22 -0
- package/package.json +94 -21
- package/src/index.ts +21 -1802
- package/src/native-build.ts +6 -0
- package/src/native-client.ts +103 -0
- package/src/native-export.ts +58 -0
- package/src/native-history.ts +359 -0
- package/src/operator-cli.mjs +453 -1937
- package/src/plan-proposal.ts +87 -0
- package/src/sdk-worker.mjs +549 -0
- package/src/service/contracts/active.ts +82 -0
- package/src/service/contracts/budget.ts +148 -0
- package/src/service/contracts/build.ts +67 -0
- package/src/service/contracts/capture.ts +48 -0
- package/src/service/contracts/core.ts +33 -0
- package/src/service/contracts/current.ts +261 -0
- package/src/service/contracts/domain.ts +68 -0
- package/src/service/contracts/history.ts +2245 -0
- package/src/service/contracts/models.ts +139 -0
- package/src/service/coordination.ts +862 -0
- package/src/service/private-files.ts +204 -0
- package/src/service-cli.mjs +5592 -0
- package/src/service-client.ts +747 -0
- package/src/service-contract.ts +245 -0
- package/src/service-host.mjs +22382 -0
- package/src/service-http-client.ts +425 -0
- package/src/service-wire.ts +372 -0
- package/src/task-shell.ts +1045 -0
- package/src/task-store.ts +479 -0
- package/src/task-types.ts +184 -0
- package/src/verification-diagnostics.ts +29 -72
- package/src/worker-capture.mjs +1197 -0
- package/src/worker-public.mjs +1753 -0
- package/src/worker-redaction.mjs +608 -0
- package/src/worker-tools.mjs +202 -0
- package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/web/dist/assets/cadence.css +2 -0
- package/web/dist/assets/cadence.js +7 -0
- package/web/dist/assets/markdown.js +296 -0
- package/web/dist/assets/react.js +9 -0
- package/web/dist/assets/rolldown-runtime.js +1 -0
- package/web/dist/assets/timeline-client.js +41 -0
- package/web/dist/assets/vis-timeline.css +1274 -0
- package/web/dist/assets/vis-timeline.js +32 -0
- package/web/dist/index.html +18 -0
- package/web/dist/manifest.json +505 -0
- package/config/plan-draft.example.json +0 -125
- package/config/plan-draft.multiple-tasks.example.json +0 -375
- package/config/plan-draft.quick.example.json +0 -78
- package/prompts/abel-design.md +0 -382
- package/prompts/abel-diagnose.md +0 -71
- package/prompts/abel-implement.md +0 -284
- package/prompts/abel-init.md +0 -52
- package/src/activation.ts +0 -44
- package/src/activity-contracts.ts +0 -82
- package/src/agent-registry.ts +0 -53
- package/src/apply-transaction.ts +0 -1437
- package/src/artifact-store.ts +0 -213
- package/src/build-windows-job.mjs +0 -95
- package/src/candidate-artifact.ts +0 -143
- package/src/candidate-context.ts +0 -289
- package/src/candidate-patch.ts +0 -271
- package/src/candidate-workspace.ts +0 -80
- package/src/canonical.ts +0 -16
- package/src/change-contract-schema.ts +0 -191
- package/src/change-contract.ts +0 -324
- package/src/change-verification.ts +0 -1617
- package/src/contracts.ts +0 -2466
- package/src/control-contracts.ts +0 -382
- package/src/delivery-compiler.ts +0 -1805
- package/src/delivery-revision.ts +0 -42
- package/src/design-control.ts +0 -1299
- package/src/design-diagnostics.ts +0 -181
- package/src/design-journal.ts +0 -1773
- package/src/directory-sync.ts +0 -36
- package/src/durable-contracts.ts +0 -227
- package/src/durable-workflow.ts +0 -822
- package/src/evidence-draft.ts +0 -122
- package/src/execution-profile.ts +0 -64
- package/src/execution-retention.ts +0 -77
- package/src/file-snapshot.ts +0 -163
- package/src/implement-continuation.ts +0 -559
- package/src/implement-graph.ts +0 -690
- package/src/implement-plan.ts +0 -72
- package/src/install-config.d.mts +0 -6
- package/src/isolation-backend.ts +0 -353
- package/src/model-source.ts +0 -22
- package/src/openspec-cli.ts +0 -494
- package/src/operator-cli-entry.mjs +0 -24
- package/src/operator-tools.ts +0 -309
- package/src/package-candidate-process.ts +0 -206
- package/src/package-candidate.ts +0 -24
- package/src/package-delivery.ts +0 -486
- package/src/package-state.ts +0 -320
- package/src/package-verification.ts +0 -635
- package/src/package-workflow.ts +0 -305
- package/src/packet-runtime.ts +0 -459
- package/src/phase-execution.ts +0 -2163
- package/src/pi-adapter.ts +0 -19
- package/src/plan-draft-summary.ts +0 -527
- package/src/plan-draft.ts +0 -502
- package/src/remove-path.ts +0 -32
- package/src/run-state.ts +0 -420
- package/src/run-store.ts +0 -1179
- package/src/safe-path.ts +0 -77
- package/src/scoped-grep-worker.mjs +0 -23
- package/src/scoped-tools.ts +0 -775
- package/src/single-task-draft.ts +0 -142
- package/src/sqlite-schema.ts +0 -171
- package/src/state-root.ts +0 -215
- package/src/storage-schema.ts +0 -360
- package/src/subagent-activity.ts +0 -1417
- package/src/subagent-process.ts +0 -476
- package/src/task-ledger.ts +0 -1338
- package/src/transport-budget.ts +0 -50
- package/src/verification-capability.ts +0 -1191
- package/src/verification-environment-io.ts +0 -389
- package/src/verification-environment.ts +0 -162
- package/src/verification-identity.ts +0 -77
- package/src/verification-scan.ts +0 -64
- package/src/windows-job-backend.ts +0 -419
- package/src/windows-job.c +0 -149
- package/src/workflow-engine.ts +0 -28
- package/src/workflow-policy.ts +0 -1670
- package/src/workflow-recovery-policy.ts +0 -97
- package/src/workflow-scheduling.ts +0 -105
- package/src/workflow-state-machine.ts +0 -4341
- package/src/workflow-status.ts +0 -576
- package/src/workspace-io-entry.mjs +0 -76
- package/src/workspace-io-worker.mjs +0 -1312
- package/src/workspace-io.ts +0 -89
- package/src/workspace-store.ts +0 -634
package/src/workflow-policy.ts
DELETED
|
@@ -1,1670 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import type { WorkflowActivityUpdate } from "./activity-contracts.ts";
|
|
5
|
-
import {
|
|
6
|
-
type CandidateContextBoundary,
|
|
7
|
-
type CandidateContextRef,
|
|
8
|
-
classifyCandidateContextRequest,
|
|
9
|
-
normalizeCandidateContextRefs,
|
|
10
|
-
} from "./candidate-context.ts";
|
|
11
|
-
import { compareCanonicalStrings } from "./canonical.ts";
|
|
12
|
-
import {
|
|
13
|
-
type ApprovalBoundaryCode,
|
|
14
|
-
isValidRelativePath,
|
|
15
|
-
type StructuredVerificationContract,
|
|
16
|
-
verificationBoundInputPaths,
|
|
17
|
-
} from "./contracts.ts";
|
|
18
|
-
import type { ControlStage } from "./control-contracts.ts";
|
|
19
|
-
import type {
|
|
20
|
-
GateApprovalProof,
|
|
21
|
-
ImplementPlan,
|
|
22
|
-
PlanTaskDraft,
|
|
23
|
-
} from "./delivery-compiler.ts";
|
|
24
|
-
import { canonicalJson } from "./run-state.ts";
|
|
25
|
-
import type { OperationLease } from "./run-store.ts";
|
|
26
|
-
import type { ResolvedStateRoot } from "./state-root.ts";
|
|
27
|
-
import type { WorkspaceEntry } from "./workspace-store.ts";
|
|
28
|
-
|
|
29
|
-
export const SHA256 = /^[a-f0-9]{64}$/u;
|
|
30
|
-
export const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
|
|
31
|
-
export const CHANGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/u;
|
|
32
|
-
export const PACKAGE_NAME =
|
|
33
|
-
/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/iu;
|
|
34
|
-
export const LOCKFILES = new Set([
|
|
35
|
-
"bun.lock",
|
|
36
|
-
"bun.lockb",
|
|
37
|
-
"package-lock.json",
|
|
38
|
-
"pnpm-lock.yaml",
|
|
39
|
-
"yarn.lock",
|
|
40
|
-
]);
|
|
41
|
-
export const IMPLEMENTATION_ROUTE_LIMITS = Object.freeze({
|
|
42
|
-
preferredContextWindow: 128_000,
|
|
43
|
-
preferredOutputTokens: 64_000,
|
|
44
|
-
minimumContextWindow: 16_000,
|
|
45
|
-
minimumOutputTokens: 8_000,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export function implementationRouteRequirements(
|
|
49
|
-
input: {
|
|
50
|
-
task?: PlanTaskDraft;
|
|
51
|
-
artifactCorrection?: RedArtifactCorrection;
|
|
52
|
-
repair?: { failureIdentities: string[] };
|
|
53
|
-
} = {},
|
|
54
|
-
) {
|
|
55
|
-
if (!input.task) {
|
|
56
|
-
return {
|
|
57
|
-
minContextWindow: IMPLEMENTATION_ROUTE_LIMITS.minimumContextWindow,
|
|
58
|
-
minOutputTokens: IMPLEMENTATION_ROUTE_LIMITS.minimumOutputTokens,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
const serializedBytes = Buffer.byteLength(canonicalJson(input.task), "utf8");
|
|
62
|
-
const pathCount = Object.values(input.task.phases).reduce(
|
|
63
|
-
(total, phase) =>
|
|
64
|
-
total + phase.read.length + phase.write.length + phase.delete.length,
|
|
65
|
-
0,
|
|
66
|
-
);
|
|
67
|
-
const complexity =
|
|
68
|
-
serializedBytes +
|
|
69
|
-
pathCount * 512 +
|
|
70
|
-
(input.artifactCorrection ? 16_384 : 0) +
|
|
71
|
-
(input.repair ? input.repair.failureIdentities.length * 256 + 16_384 : 0);
|
|
72
|
-
return {
|
|
73
|
-
minContextWindow: IMPLEMENTATION_ROUTE_LIMITS.minimumContextWindow,
|
|
74
|
-
minOutputTokens: IMPLEMENTATION_ROUTE_LIMITS.minimumOutputTokens,
|
|
75
|
-
preferredContextWindow: Math.min(
|
|
76
|
-
IMPLEMENTATION_ROUTE_LIMITS.preferredContextWindow,
|
|
77
|
-
Math.max(
|
|
78
|
-
IMPLEMENTATION_ROUTE_LIMITS.minimumContextWindow,
|
|
79
|
-
16_384 + complexity * 2,
|
|
80
|
-
),
|
|
81
|
-
),
|
|
82
|
-
preferredOutputTokens: Math.min(
|
|
83
|
-
IMPLEMENTATION_ROUTE_LIMITS.preferredOutputTokens,
|
|
84
|
-
Math.max(
|
|
85
|
-
IMPLEMENTATION_ROUTE_LIMITS.minimumOutputTokens,
|
|
86
|
-
8_192 + Math.ceil(complexity / 2),
|
|
87
|
-
),
|
|
88
|
-
),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export const SEMANTIC_ROUTE_FAILURE_CODES = new Set([
|
|
93
|
-
"child-no-structural-submit",
|
|
94
|
-
"invalid-structural-result",
|
|
95
|
-
"structural-identity-mismatch",
|
|
96
|
-
"invalid-diff",
|
|
97
|
-
"candidate-diff-invalid",
|
|
98
|
-
]);
|
|
99
|
-
|
|
100
|
-
export function semanticRouteFailure(
|
|
101
|
-
value: DurableCandidateProposal,
|
|
102
|
-
): string | undefined {
|
|
103
|
-
return (value.kind === "retryable" || value.kind === "paused") &&
|
|
104
|
-
SEMANTIC_ROUTE_FAILURE_CODES.has(value.code)
|
|
105
|
-
? value.code
|
|
106
|
-
: undefined;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function isCancellationException(
|
|
110
|
-
error: unknown,
|
|
111
|
-
signal: AbortSignal,
|
|
112
|
-
): boolean {
|
|
113
|
-
if (!signal.aborted) return false;
|
|
114
|
-
if (error === signal.reason) return true;
|
|
115
|
-
return (
|
|
116
|
-
error instanceof Error &&
|
|
117
|
-
(error.name === "AbortError" ||
|
|
118
|
-
error.message === "cancelled" ||
|
|
119
|
-
error.message === "operation-cancelled")
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// These adapters are read-only. Cancellation fences their late settlement;
|
|
124
|
-
// mutating operations must instead be awaited through their owned lifecycle.
|
|
125
|
-
export async function cancellableRead<T>(
|
|
126
|
-
read: () => Promise<T>,
|
|
127
|
-
signal?: AbortSignal,
|
|
128
|
-
): Promise<T> {
|
|
129
|
-
signal?.throwIfAborted();
|
|
130
|
-
if (!signal) return read();
|
|
131
|
-
return new Promise<T>((resolve, reject) => {
|
|
132
|
-
const onAbort = () => reject(signal.reason);
|
|
133
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
134
|
-
const reading = Promise.resolve().then(() => {
|
|
135
|
-
signal.throwIfAborted();
|
|
136
|
-
return read();
|
|
137
|
-
});
|
|
138
|
-
reading
|
|
139
|
-
.then(resolve, reject)
|
|
140
|
-
.finally(() => signal.removeEventListener("abort", onAbort));
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function emitWorkflowActivity(
|
|
145
|
-
observer: ((event: WorkflowActivityUpdate) => void) | undefined,
|
|
146
|
-
event: WorkflowActivityUpdate,
|
|
147
|
-
): void {
|
|
148
|
-
try {
|
|
149
|
-
observer?.(structuredClone(event));
|
|
150
|
-
} catch {
|
|
151
|
-
// Presentation is observational and cannot change workflow behavior.
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export interface WorkflowWorkspaceFacts {
|
|
156
|
-
baselineRevisionId?: string;
|
|
157
|
-
currentWorkspaceRevisionId?: string;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export interface WorkflowRouteFacts {
|
|
161
|
-
routeId?: string;
|
|
162
|
-
routeFingerprint?: string;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export interface WorkflowVerificationStatus {
|
|
166
|
-
scope: "baseline" | DurableVerificationScope;
|
|
167
|
-
attribution: "pre-existing" | "introduced" | "unresolved" | "environment";
|
|
168
|
-
taskId?: string;
|
|
169
|
-
failureIdentities?: string[];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export interface WorkflowContextRequest {
|
|
173
|
-
code:
|
|
174
|
-
| "approved-context-needed"
|
|
175
|
-
| "task-split-needed"
|
|
176
|
-
| "boundary-review-needed";
|
|
177
|
-
refs: CandidateContextRef[];
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export interface RedArtifactCorrection {
|
|
181
|
-
code: "red-artifact-constraint";
|
|
182
|
-
attempt: number;
|
|
183
|
-
maxAttempts: number;
|
|
184
|
-
contextRequest?: WorkflowContextRequest;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export type {
|
|
188
|
-
RecoveryActionDecision,
|
|
189
|
-
RecoveryActionRequest,
|
|
190
|
-
WorkflowRecoveryFact,
|
|
191
|
-
WorkflowRecoveryFeedback,
|
|
192
|
-
} from "./workflow-recovery-policy.ts";
|
|
193
|
-
export { decideRecoveryAction } from "./workflow-recovery-policy.ts";
|
|
194
|
-
|
|
195
|
-
import type {
|
|
196
|
-
RecoveryActionDecision,
|
|
197
|
-
RecoveryActionRequest,
|
|
198
|
-
WorkflowRecoveryFact,
|
|
199
|
-
WorkflowRecoveryFeedback,
|
|
200
|
-
} from "./workflow-recovery-policy.ts";
|
|
201
|
-
|
|
202
|
-
export const APPROVAL_AUTHORITY_CATEGORIES = [
|
|
203
|
-
"observable-behavior",
|
|
204
|
-
"architecture-policy",
|
|
205
|
-
"dependency",
|
|
206
|
-
"path-boundary",
|
|
207
|
-
"conflict-resource",
|
|
208
|
-
"verification-contract",
|
|
209
|
-
"agents-contract",
|
|
210
|
-
"irreversible-scope",
|
|
211
|
-
] as const;
|
|
212
|
-
|
|
213
|
-
export type ApprovalAuthorityCategory =
|
|
214
|
-
(typeof APPROVAL_AUTHORITY_CATEGORIES)[number];
|
|
215
|
-
|
|
216
|
-
export interface ApprovalRequirement {
|
|
217
|
-
category: ApprovalAuthorityCategory;
|
|
218
|
-
requiredGates: Array<"gate-a" | "gate-b">;
|
|
219
|
-
refs: string[];
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export const INTERNAL_APPROVAL_CODES = [
|
|
223
|
-
"boundary-review-needed",
|
|
224
|
-
"repair-boundary-expansion",
|
|
225
|
-
"task-write-set-empty",
|
|
226
|
-
] as const;
|
|
227
|
-
|
|
228
|
-
export type InternalApprovalCode = (typeof INTERNAL_APPROVAL_CODES)[number];
|
|
229
|
-
export type WorkflowApprovalCode = ApprovalBoundaryCode | InternalApprovalCode;
|
|
230
|
-
|
|
231
|
-
// These pauses need a new executable plan, not a new user decision. Environment,
|
|
232
|
-
// integrity and cancellation codes deliberately do not grant this continuation.
|
|
233
|
-
export function permitsPlanAmendment(code: string): boolean {
|
|
234
|
-
return ["needs-task-split", "task-split-needed"].includes(code);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export type ParentRecoveryStrategy =
|
|
238
|
-
| "inspect-exhausted-correction"
|
|
239
|
-
| "inspect-route-availability"
|
|
240
|
-
| "restore-runtime-environment"
|
|
241
|
-
| "restore-verification-capability";
|
|
242
|
-
|
|
243
|
-
const ROUTE_RECOVERY_CODES = new Set(["child-timeout", "transport-failure"]);
|
|
244
|
-
|
|
245
|
-
const ENVIRONMENT_RECOVERY_CODES = new Set([
|
|
246
|
-
"bubblewrap-launch-failed",
|
|
247
|
-
"bubblewrap-or-dependency-unavailable",
|
|
248
|
-
"bun-executable-unavailable",
|
|
249
|
-
"checkpoint-unavailable",
|
|
250
|
-
"checkout-cleanup-failed",
|
|
251
|
-
"checkout-failed",
|
|
252
|
-
"child-provider-authentication-failed",
|
|
253
|
-
"clone-failed",
|
|
254
|
-
"git-apply-check-unavailable",
|
|
255
|
-
"git-apply-unavailable",
|
|
256
|
-
"git-index-unavailable",
|
|
257
|
-
"isolation-backend-launch-failed",
|
|
258
|
-
"isolation-backend-unavailable",
|
|
259
|
-
"local-executable-missing",
|
|
260
|
-
"root-unavailable",
|
|
261
|
-
"runner-missing",
|
|
262
|
-
"sandbox-runtime-unavailable",
|
|
263
|
-
"script-missing",
|
|
264
|
-
"verification-environment-changed",
|
|
265
|
-
"verification-environment-requires-trusted-mode",
|
|
266
|
-
"verification-environment-unavailable",
|
|
267
|
-
"verification-runner-launch-failed",
|
|
268
|
-
"workspace-dependency-missing",
|
|
269
|
-
]);
|
|
270
|
-
|
|
271
|
-
const CORRECTION_RECOVERY_CODES = new Set([
|
|
272
|
-
"child-timeout",
|
|
273
|
-
"transport-failure",
|
|
274
|
-
"artifact-attempts-exhausted",
|
|
275
|
-
"baseline-deletion-drift",
|
|
276
|
-
"baseline-directory-drift",
|
|
277
|
-
"baseline-file-drift",
|
|
278
|
-
"candidate-check-failed",
|
|
279
|
-
"candidate-diff-invalid",
|
|
280
|
-
"child-no-structural-submit",
|
|
281
|
-
"git-apply-check-failed",
|
|
282
|
-
"invalid-diff",
|
|
283
|
-
"invalid-diff-bytes",
|
|
284
|
-
"invalid-structural-result",
|
|
285
|
-
"needs-task-split",
|
|
286
|
-
"parent-review-rejected",
|
|
287
|
-
"red-artifact-constraint",
|
|
288
|
-
"red-not-witnessed",
|
|
289
|
-
"repair-attempts-exhausted",
|
|
290
|
-
"stale-snapshot",
|
|
291
|
-
"structural-identity-mismatch",
|
|
292
|
-
"task-split-needed",
|
|
293
|
-
"verification-rejected",
|
|
294
|
-
"verification-report-invalid",
|
|
295
|
-
"verification-report-missing",
|
|
296
|
-
"write-set-mismatch",
|
|
297
|
-
"workspace-revision-stale",
|
|
298
|
-
]);
|
|
299
|
-
|
|
300
|
-
const RECOVERY_AMENDMENT_CODES = new Set([
|
|
301
|
-
"artifact-attempts-exhausted",
|
|
302
|
-
"candidate-check-failed",
|
|
303
|
-
"candidate-diff-invalid",
|
|
304
|
-
"child-no-structural-submit",
|
|
305
|
-
"invalid-diff",
|
|
306
|
-
"invalid-diff-bytes",
|
|
307
|
-
"invalid-structural-result",
|
|
308
|
-
"needs-task-split",
|
|
309
|
-
"red-artifact-constraint",
|
|
310
|
-
"red-not-witnessed",
|
|
311
|
-
"repair-attempts-exhausted",
|
|
312
|
-
"structural-identity-mismatch",
|
|
313
|
-
"task-split-needed",
|
|
314
|
-
"verification-rejected",
|
|
315
|
-
"write-set-mismatch",
|
|
316
|
-
]);
|
|
317
|
-
|
|
318
|
-
function hasCurrentExhaustedRecovery(
|
|
319
|
-
code: string,
|
|
320
|
-
diagnostic: SafeAttemptDiagnostic | undefined,
|
|
321
|
-
): diagnostic is SafeAttemptDiagnostic & { recovery: WorkflowRecoveryFact } {
|
|
322
|
-
const recovery = diagnostic?.recovery;
|
|
323
|
-
return (
|
|
324
|
-
recovery !== undefined &&
|
|
325
|
-
recovery.failures >= recovery.feedback.maxAttempts &&
|
|
326
|
-
CORRECTION_RECOVERY_CODES.has(recovery.feedback.code) &&
|
|
327
|
-
(code === recovery.feedback.code ||
|
|
328
|
-
["artifact-attempts-exhausted", "repair-attempts-exhausted"].includes(
|
|
329
|
-
code,
|
|
330
|
-
))
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Classify only code-owned, locally actionable recovery evidence. The result is
|
|
336
|
-
* parent guidance, never new command or acceptance authority.
|
|
337
|
-
*/
|
|
338
|
-
export function parentRecoveryStrategy(
|
|
339
|
-
code: string,
|
|
340
|
-
diagnostic: SafeAttemptDiagnostic | undefined,
|
|
341
|
-
): ParentRecoveryStrategy | undefined {
|
|
342
|
-
const prerequisite = normalizeVerificationPrerequisite(
|
|
343
|
-
diagnostic?.prerequisite,
|
|
344
|
-
);
|
|
345
|
-
if (prerequisite) {
|
|
346
|
-
return prerequisite.cause === "capability" &&
|
|
347
|
-
ENVIRONMENT_RECOVERY_CODES.has(code)
|
|
348
|
-
? "restore-verification-capability"
|
|
349
|
-
: undefined;
|
|
350
|
-
}
|
|
351
|
-
if (hasCurrentExhaustedRecovery(code, diagnostic))
|
|
352
|
-
return "inspect-exhausted-correction";
|
|
353
|
-
if (ROUTE_RECOVERY_CODES.has(code)) return "inspect-route-availability";
|
|
354
|
-
if (ENVIRONMENT_RECOVERY_CODES.has(code))
|
|
355
|
-
return "restore-runtime-environment";
|
|
356
|
-
return undefined;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/** A failed candidate/repair may be decomposed only from retained exhaustion evidence. */
|
|
360
|
-
export function permitsRecoveryAmendment(
|
|
361
|
-
code: string,
|
|
362
|
-
diagnostic: SafeAttemptDiagnostic | undefined,
|
|
363
|
-
): boolean {
|
|
364
|
-
return (
|
|
365
|
-
hasCurrentExhaustedRecovery(code, diagnostic) &&
|
|
366
|
-
diagnostic.recovery.feedback.strategy !== "refresh-candidate" &&
|
|
367
|
-
RECOVERY_AMENDMENT_CODES.has(diagnostic.recovery.feedback.code)
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/** Parent-verifier facts about an original-revision obligation. */
|
|
372
|
-
export interface WorkflowVerificationPrerequisite {
|
|
373
|
-
kind: "verification-prerequisite";
|
|
374
|
-
scope: "baseline-task-affected" | "baseline-full-suite";
|
|
375
|
-
cause:
|
|
376
|
-
| "future-output"
|
|
377
|
-
| "missing-input"
|
|
378
|
-
| "unsafe-input"
|
|
379
|
-
| "capability"
|
|
380
|
-
| "unknown";
|
|
381
|
-
verificationId: string;
|
|
382
|
-
contractIdentity: string;
|
|
383
|
-
originalRevisionId: string;
|
|
384
|
-
environmentIdentity: string;
|
|
385
|
-
taskId?: string;
|
|
386
|
-
input?: { path: string; kind: "absent" | "unsafe" };
|
|
387
|
-
producer?: { taskId: string; phase: "red" | "green" | "refactor" };
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
export function normalizeVerificationPrerequisite(
|
|
391
|
-
value: unknown,
|
|
392
|
-
): WorkflowVerificationPrerequisite | undefined {
|
|
393
|
-
if (
|
|
394
|
-
!isRecord(value) ||
|
|
395
|
-
value.kind !== "verification-prerequisite" ||
|
|
396
|
-
!["baseline-task-affected", "baseline-full-suite"].includes(
|
|
397
|
-
String(value.scope),
|
|
398
|
-
) ||
|
|
399
|
-
![
|
|
400
|
-
"future-output",
|
|
401
|
-
"missing-input",
|
|
402
|
-
"unsafe-input",
|
|
403
|
-
"capability",
|
|
404
|
-
"unknown",
|
|
405
|
-
].includes(String(value.cause)) ||
|
|
406
|
-
typeof value.verificationId !== "string" ||
|
|
407
|
-
!IDENTIFIER.test(value.verificationId) ||
|
|
408
|
-
typeof value.contractIdentity !== "string" ||
|
|
409
|
-
!SHA256.test(value.contractIdentity) ||
|
|
410
|
-
typeof value.originalRevisionId !== "string" ||
|
|
411
|
-
!SHA256.test(value.originalRevisionId) ||
|
|
412
|
-
typeof value.environmentIdentity !== "string" ||
|
|
413
|
-
!(
|
|
414
|
-
SHA256.test(value.environmentIdentity) ||
|
|
415
|
-
value.environmentIdentity === "unobserved"
|
|
416
|
-
) ||
|
|
417
|
-
(value.taskId !== undefined &&
|
|
418
|
-
(typeof value.taskId !== "string" || !IDENTIFIER.test(value.taskId)))
|
|
419
|
-
)
|
|
420
|
-
return undefined;
|
|
421
|
-
if (
|
|
422
|
-
value.input !== undefined &&
|
|
423
|
-
(!isRecord(value.input) ||
|
|
424
|
-
!isValidRelativePath(value.input.path) ||
|
|
425
|
-
!["absent", "unsafe"].includes(String(value.input.kind)))
|
|
426
|
-
)
|
|
427
|
-
return undefined;
|
|
428
|
-
if (
|
|
429
|
-
value.producer !== undefined &&
|
|
430
|
-
(!isRecord(value.producer) ||
|
|
431
|
-
typeof value.producer.taskId !== "string" ||
|
|
432
|
-
!IDENTIFIER.test(value.producer.taskId) ||
|
|
433
|
-
!["red", "green", "refactor"].includes(String(value.producer.phase)))
|
|
434
|
-
)
|
|
435
|
-
return undefined;
|
|
436
|
-
if (
|
|
437
|
-
(value.cause === "future-output" &&
|
|
438
|
-
(!value.producer ||
|
|
439
|
-
!isRecord(value.input) ||
|
|
440
|
-
value.input.kind !== "absent")) ||
|
|
441
|
-
(value.cause === "missing-input" &&
|
|
442
|
-
(!isRecord(value.input) || value.input.kind !== "absent")) ||
|
|
443
|
-
(value.cause === "unsafe-input" &&
|
|
444
|
-
(!isRecord(value.input) || value.input.kind !== "unsafe"))
|
|
445
|
-
)
|
|
446
|
-
return undefined;
|
|
447
|
-
return {
|
|
448
|
-
kind: "verification-prerequisite",
|
|
449
|
-
scope: value.scope as WorkflowVerificationPrerequisite["scope"],
|
|
450
|
-
cause: value.cause as WorkflowVerificationPrerequisite["cause"],
|
|
451
|
-
verificationId: value.verificationId,
|
|
452
|
-
contractIdentity: value.contractIdentity,
|
|
453
|
-
originalRevisionId: value.originalRevisionId,
|
|
454
|
-
environmentIdentity: value.environmentIdentity,
|
|
455
|
-
...(typeof value.taskId === "string" ? { taskId: value.taskId } : {}),
|
|
456
|
-
...(isRecord(value.input)
|
|
457
|
-
? {
|
|
458
|
-
input: {
|
|
459
|
-
path: value.input.path as string,
|
|
460
|
-
kind: value.input.kind as "absent" | "unsafe",
|
|
461
|
-
},
|
|
462
|
-
}
|
|
463
|
-
: {}),
|
|
464
|
-
...(isRecord(value.producer)
|
|
465
|
-
? {
|
|
466
|
-
producer: {
|
|
467
|
-
taskId: value.producer.taskId as string,
|
|
468
|
-
phase: value.producer.phase as "red" | "green" | "refactor",
|
|
469
|
-
},
|
|
470
|
-
}
|
|
471
|
-
: {}),
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
export function permitsPrerequisiteAmendment(value: unknown): boolean {
|
|
476
|
-
const fact = normalizeVerificationPrerequisite(value);
|
|
477
|
-
return (
|
|
478
|
-
fact !== undefined &&
|
|
479
|
-
["future-output", "missing-input"].includes(fact.cause)
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
export const APPROVAL_REQUIREMENT_BY_CODE = {
|
|
484
|
-
"agents-contract-insufficient": {
|
|
485
|
-
category: "agents-contract",
|
|
486
|
-
requiredGates: ["gate-b"],
|
|
487
|
-
},
|
|
488
|
-
"architecture-contract-insufficient": {
|
|
489
|
-
category: "architecture-policy",
|
|
490
|
-
requiredGates: ["gate-b"],
|
|
491
|
-
},
|
|
492
|
-
"behavior-contract-insufficient": {
|
|
493
|
-
category: "observable-behavior",
|
|
494
|
-
requiredGates: ["gate-a", "gate-b"],
|
|
495
|
-
},
|
|
496
|
-
"boundary-review-needed": {
|
|
497
|
-
category: "path-boundary",
|
|
498
|
-
requiredGates: ["gate-b"],
|
|
499
|
-
},
|
|
500
|
-
"conflict-resource-authority-insufficient": {
|
|
501
|
-
category: "conflict-resource",
|
|
502
|
-
requiredGates: ["gate-b"],
|
|
503
|
-
},
|
|
504
|
-
"irreversible-scope-insufficient": {
|
|
505
|
-
category: "irreversible-scope",
|
|
506
|
-
requiredGates: ["gate-a", "gate-b"],
|
|
507
|
-
},
|
|
508
|
-
"repair-boundary-expansion": {
|
|
509
|
-
category: "path-boundary",
|
|
510
|
-
requiredGates: ["gate-b"],
|
|
511
|
-
},
|
|
512
|
-
"task-scope-insufficient": {
|
|
513
|
-
category: "path-boundary",
|
|
514
|
-
requiredGates: ["gate-b"],
|
|
515
|
-
},
|
|
516
|
-
"task-write-set-empty": {
|
|
517
|
-
category: "path-boundary",
|
|
518
|
-
requiredGates: ["gate-b"],
|
|
519
|
-
},
|
|
520
|
-
"unapproved-dependency-change": {
|
|
521
|
-
category: "dependency",
|
|
522
|
-
requiredGates: ["gate-b"],
|
|
523
|
-
},
|
|
524
|
-
"verification-contract-insufficient": {
|
|
525
|
-
category: "verification-contract",
|
|
526
|
-
requiredGates: ["gate-b"],
|
|
527
|
-
},
|
|
528
|
-
} as const satisfies Record<
|
|
529
|
-
WorkflowApprovalCode,
|
|
530
|
-
{
|
|
531
|
-
category: ApprovalAuthorityCategory;
|
|
532
|
-
requiredGates: readonly ("gate-a" | "gate-b")[];
|
|
533
|
-
}
|
|
534
|
-
>;
|
|
535
|
-
|
|
536
|
-
export type WorkflowRetryPolicy =
|
|
537
|
-
| "artifact"
|
|
538
|
-
| "stale"
|
|
539
|
-
| "verification"
|
|
540
|
-
| "checkpoint";
|
|
541
|
-
|
|
542
|
-
export type WorkflowAttemptOutcome = (
|
|
543
|
-
| {
|
|
544
|
-
kind: "phase-committed";
|
|
545
|
-
artifactHash: string;
|
|
546
|
-
isolatedRevisionId: string;
|
|
547
|
-
exitCode: number;
|
|
548
|
-
classification: "expected-red" | "expected-green" | "expected-refactor";
|
|
549
|
-
}
|
|
550
|
-
| {
|
|
551
|
-
kind: "paused" | "retryable" | "approval-needed";
|
|
552
|
-
code: string;
|
|
553
|
-
untrustedCandidate?: Uint8Array;
|
|
554
|
-
verification?: WorkflowVerificationStatus;
|
|
555
|
-
prerequisite?: WorkflowVerificationPrerequisite;
|
|
556
|
-
contextRequest?: WorkflowContextRequest;
|
|
557
|
-
retryPolicy?: WorkflowRetryPolicy;
|
|
558
|
-
}
|
|
559
|
-
| { kind: "operation-cancelled"; code: "cancelled" }
|
|
560
|
-
) &
|
|
561
|
-
WorkflowWorkspaceFacts &
|
|
562
|
-
WorkflowRouteFacts & {
|
|
563
|
-
attemptDiagnostic?: SafeAttemptDiagnostic;
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
export interface WorkflowWorker {
|
|
567
|
-
/** Baseline admission before reserving Worker work; never launches a child. */
|
|
568
|
-
prepareTask?(
|
|
569
|
-
input: Parameters<WorkflowWorker["runAttempt"]>[0],
|
|
570
|
-
): Promise<
|
|
571
|
-
({ kind: "prepared" } & WorkflowWorkspaceFacts) | WorkflowAttemptOutcome
|
|
572
|
-
>;
|
|
573
|
-
hasPendingVerification?(
|
|
574
|
-
input: Parameters<WorkflowWorker["runAttempt"]>[0],
|
|
575
|
-
): Promise<boolean>;
|
|
576
|
-
isContextReadAvailable?(input: {
|
|
577
|
-
runId: string;
|
|
578
|
-
deliveryRevision: number;
|
|
579
|
-
plan: ImplementPlan;
|
|
580
|
-
baselineRevisionId: string;
|
|
581
|
-
currentWorkspaceRevisionId: string;
|
|
582
|
-
path: string;
|
|
583
|
-
}): boolean;
|
|
584
|
-
runAttempt(input: {
|
|
585
|
-
runId: string;
|
|
586
|
-
operationId: string;
|
|
587
|
-
deliveryRevision: number;
|
|
588
|
-
taskId: string;
|
|
589
|
-
phase: "red" | "green" | "refactor";
|
|
590
|
-
task: PlanTaskDraft;
|
|
591
|
-
plan: ImplementPlan;
|
|
592
|
-
routeId?: string;
|
|
593
|
-
routeFingerprint?: string;
|
|
594
|
-
baselineRevisionId?: string;
|
|
595
|
-
currentWorkspaceRevisionId?: string;
|
|
596
|
-
repair?: {
|
|
597
|
-
attribution: "introduced";
|
|
598
|
-
failureIdentities: string[];
|
|
599
|
-
};
|
|
600
|
-
artifactCorrection?: RedArtifactCorrection;
|
|
601
|
-
contextRequest?: WorkflowContextRequest;
|
|
602
|
-
contextReadPaths?: string[];
|
|
603
|
-
recoveryFeedback?: WorkflowRecoveryFeedback;
|
|
604
|
-
additionalAttempt?: boolean;
|
|
605
|
-
verificationOnly?: boolean;
|
|
606
|
-
reserveCandidate?: () => boolean;
|
|
607
|
-
recoveryDecision?: (
|
|
608
|
-
request: RecoveryActionRequest,
|
|
609
|
-
) => RecoveryActionDecision;
|
|
610
|
-
signal: AbortSignal;
|
|
611
|
-
onActivity?: (
|
|
612
|
-
event: Pick<
|
|
613
|
-
WorkflowActivityUpdate,
|
|
614
|
-
"state" | "code" | "attempt" | "maxAttempts" | "wait"
|
|
615
|
-
>,
|
|
616
|
-
) => void;
|
|
617
|
-
}): Promise<WorkflowAttemptOutcome>;
|
|
618
|
-
rebind(input: {
|
|
619
|
-
runId: string;
|
|
620
|
-
taskId: string;
|
|
621
|
-
task: PlanTaskDraft;
|
|
622
|
-
role: "implementation-worker";
|
|
623
|
-
routeId: string;
|
|
624
|
-
}):
|
|
625
|
-
| {
|
|
626
|
-
ok: true;
|
|
627
|
-
routeId?: string;
|
|
628
|
-
routeFingerprint?: string;
|
|
629
|
-
route?: { id?: string; fingerprint?: string };
|
|
630
|
-
}
|
|
631
|
-
| { ok: false; code: string };
|
|
632
|
-
revalidateDelivery?(input: {
|
|
633
|
-
runId: string;
|
|
634
|
-
deliveryRevision: number;
|
|
635
|
-
plan: ImplementPlan;
|
|
636
|
-
tasks: Array<{
|
|
637
|
-
taskId: string;
|
|
638
|
-
deliveryRevision: number;
|
|
639
|
-
state: string;
|
|
640
|
-
phase: "red" | "green" | "refactor";
|
|
641
|
-
contextReadPaths?: string[];
|
|
642
|
-
}>;
|
|
643
|
-
invalidatedTaskIds: string[];
|
|
644
|
-
baselineRevisionId: string;
|
|
645
|
-
currentWorkspaceRevisionId: string;
|
|
646
|
-
}): {
|
|
647
|
-
invalidatedTaskIds: string[];
|
|
648
|
-
baselineRevisionId: string;
|
|
649
|
-
currentWorkspaceRevisionId: string;
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
export interface WorkflowDelivery {
|
|
654
|
-
gate: "gate-a" | "gate-b";
|
|
655
|
-
revision: number;
|
|
656
|
-
receiptHash: string;
|
|
657
|
-
plan: ImplementPlan;
|
|
658
|
-
approvalProofs?: {
|
|
659
|
-
gateA: GateApprovalProof;
|
|
660
|
-
gateB: GateApprovalProof;
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
export interface WorkflowAvailableDelivery {
|
|
665
|
-
deliveryRevision: number;
|
|
666
|
-
receiptHash: string;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
export interface WorkflowDeliverySource {
|
|
670
|
-
load(input: {
|
|
671
|
-
stage: ControlStage;
|
|
672
|
-
change: string;
|
|
673
|
-
signal?: AbortSignal;
|
|
674
|
-
deliveryRevision?: number;
|
|
675
|
-
receiptHash?: string;
|
|
676
|
-
}): Promise<WorkflowDelivery>;
|
|
677
|
-
discoverLatest?(input: {
|
|
678
|
-
stage: ControlStage;
|
|
679
|
-
change: string;
|
|
680
|
-
signal?: AbortSignal;
|
|
681
|
-
}): Promise<WorkflowAvailableDelivery | undefined>;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
export interface WorkflowChangeVerifier {
|
|
685
|
-
verify(input: {
|
|
686
|
-
taskEvidence?: Array<{ taskId: string; deliveryRevision: number }>;
|
|
687
|
-
runId: string;
|
|
688
|
-
deliveryRevision: number;
|
|
689
|
-
plan: ImplementPlan;
|
|
690
|
-
baselineRevisionId?: string;
|
|
691
|
-
currentWorkspaceRevisionId?: string;
|
|
692
|
-
signal: AbortSignal;
|
|
693
|
-
}): Promise<
|
|
694
|
-
| {
|
|
695
|
-
kind: "verified";
|
|
696
|
-
verificationId: string;
|
|
697
|
-
currentWorkspaceRevisionId?: string;
|
|
698
|
-
}
|
|
699
|
-
| {
|
|
700
|
-
kind: "paused" | "retryable" | "approval-needed";
|
|
701
|
-
code: string;
|
|
702
|
-
verification?: WorkflowVerificationStatus;
|
|
703
|
-
}
|
|
704
|
-
>;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
export interface WorkflowApplication {
|
|
708
|
-
prepare?(input: {
|
|
709
|
-
transactionId: string;
|
|
710
|
-
runId: string;
|
|
711
|
-
consumerRoot: string;
|
|
712
|
-
deliveryRevision: number;
|
|
713
|
-
plan: ImplementPlan;
|
|
714
|
-
contextReadPaths?: string[];
|
|
715
|
-
baselineRevisionId?: string;
|
|
716
|
-
currentWorkspaceRevisionId?: string;
|
|
717
|
-
signal: AbortSignal;
|
|
718
|
-
}): Promise<Record<string, unknown>>;
|
|
719
|
-
applyPrepared?(input: {
|
|
720
|
-
transactionId: string;
|
|
721
|
-
runId: string;
|
|
722
|
-
consumerRoot: string;
|
|
723
|
-
deliveryRevision: number;
|
|
724
|
-
plan: ImplementPlan;
|
|
725
|
-
contextReadPaths?: string[];
|
|
726
|
-
baselineRevisionId?: string;
|
|
727
|
-
currentWorkspaceRevisionId?: string;
|
|
728
|
-
signal: AbortSignal;
|
|
729
|
-
}): Promise<Record<string, unknown>>;
|
|
730
|
-
begin(input: {
|
|
731
|
-
transactionId: string;
|
|
732
|
-
runId: string;
|
|
733
|
-
consumerRoot: string;
|
|
734
|
-
deliveryRevision: number;
|
|
735
|
-
plan: ImplementPlan;
|
|
736
|
-
contextReadPaths?: string[];
|
|
737
|
-
baselineRevisionId?: string;
|
|
738
|
-
currentWorkspaceRevisionId?: string;
|
|
739
|
-
signal: AbortSignal;
|
|
740
|
-
}): Promise<Record<string, unknown>>;
|
|
741
|
-
requestControl(
|
|
742
|
-
transactionId: string,
|
|
743
|
-
intent: "cancel" | "discard",
|
|
744
|
-
context?: WorkflowApplicationContext,
|
|
745
|
-
): Record<string, unknown>;
|
|
746
|
-
recover(
|
|
747
|
-
transactionId: string,
|
|
748
|
-
context?: WorkflowApplicationContext,
|
|
749
|
-
): Promise<Record<string, unknown>>;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
export interface WorkflowApplicationContext {
|
|
753
|
-
runId: string;
|
|
754
|
-
deliveryRevision: number;
|
|
755
|
-
plan: ImplementPlan;
|
|
756
|
-
contextReadPaths?: string[];
|
|
757
|
-
baselineRevisionId?: string;
|
|
758
|
-
currentWorkspaceRevisionId?: string;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
export interface WorkflowRunLifecycle {
|
|
762
|
-
cleanup(runId: string): void;
|
|
763
|
-
close?(): void;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
export interface WorkflowEngineOptions {
|
|
767
|
-
consumerRoot: string;
|
|
768
|
-
stateRoot: ResolvedStateRoot;
|
|
769
|
-
deliverySource: WorkflowDeliverySource;
|
|
770
|
-
worker: WorkflowWorker;
|
|
771
|
-
changeVerifier: WorkflowChangeVerifier;
|
|
772
|
-
application?: WorkflowApplication;
|
|
773
|
-
lifecycle?: WorkflowRunLifecycle;
|
|
774
|
-
now?: () => number;
|
|
775
|
-
leaseTtlMs?: number;
|
|
776
|
-
workHardLimit?: number;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
export interface EngineRunRow {
|
|
780
|
-
current_revision: number | null;
|
|
781
|
-
baseline_workspace_revision: string | null;
|
|
782
|
-
current_workspace_revision: string | null;
|
|
783
|
-
cleanup_state: "none" | "retained" | "complete";
|
|
784
|
-
route_id: string | null;
|
|
785
|
-
route_fingerprint: string | null;
|
|
786
|
-
transaction_id: string | null;
|
|
787
|
-
verification_json: string | null;
|
|
788
|
-
delivery_diagnostics_json: string | null;
|
|
789
|
-
next_queue_position: number;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
export interface EngineTaskRow {
|
|
793
|
-
task_id: string;
|
|
794
|
-
task_order: number;
|
|
795
|
-
delivery_revision: number;
|
|
796
|
-
plan_json: string;
|
|
797
|
-
state: string;
|
|
798
|
-
phase: "red" | "green" | "refactor";
|
|
799
|
-
pause_code: string | null;
|
|
800
|
-
context_request_json: string | null;
|
|
801
|
-
attempt_diagnostic_json: string | null;
|
|
802
|
-
route_id: string | null;
|
|
803
|
-
route_fingerprint: string | null;
|
|
804
|
-
queue_position: number | null;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
export interface SafeAttemptDiagnostic {
|
|
808
|
-
fingerprint?: string;
|
|
809
|
-
finalCategory?: string;
|
|
810
|
-
submitAttempts?: number;
|
|
811
|
-
schema?: string;
|
|
812
|
-
identityMismatch?: string[];
|
|
813
|
-
sameFailureCount?: number;
|
|
814
|
-
action?: "rebind-or-revise-delivery";
|
|
815
|
-
recovery?: WorkflowRecoveryFact;
|
|
816
|
-
prerequisite?: WorkflowVerificationPrerequisite;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
export function recoveryKey(
|
|
820
|
-
task: PlanTaskDraft,
|
|
821
|
-
phase: string,
|
|
822
|
-
_row?: EngineTaskRow,
|
|
823
|
-
_run?: EngineRunRow,
|
|
824
|
-
): string {
|
|
825
|
-
// Keep the Red witness and the active phase's obligation across replanning.
|
|
826
|
-
// Execution identities, verifier names, and wording are not progress.
|
|
827
|
-
const obligation = (boundary: PlanTaskDraft["phases"]["red"]) => {
|
|
828
|
-
const verification = structuredClone(
|
|
829
|
-
boundary.verification,
|
|
830
|
-
) as unknown as Record<string, unknown>;
|
|
831
|
-
delete verification.id;
|
|
832
|
-
return {
|
|
833
|
-
verification,
|
|
834
|
-
verificationInputs: boundary.verificationInputs
|
|
835
|
-
.map((binding) => canonicalJson(binding))
|
|
836
|
-
.sort(compareCanonicalStrings),
|
|
837
|
-
};
|
|
838
|
-
};
|
|
839
|
-
const boundary = task.phases[phase as keyof PlanTaskDraft["phases"]];
|
|
840
|
-
if (!boundary) throw new Error("workflow-task-phase-invalid");
|
|
841
|
-
return hash(
|
|
842
|
-
canonicalJson({
|
|
843
|
-
red: obligation(task.phases.red),
|
|
844
|
-
phase: obligation(boundary),
|
|
845
|
-
}),
|
|
846
|
-
phase,
|
|
847
|
-
);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
/** Original-baseline recovery belongs to a stable execution boundary, not its label. */
|
|
851
|
-
export function baselineRecoveryKey(
|
|
852
|
-
task: PlanTaskDraft,
|
|
853
|
-
phase: string,
|
|
854
|
-
): string {
|
|
855
|
-
return hash(
|
|
856
|
-
"baseline-recovery-owner-v1",
|
|
857
|
-
recoveryKey(task, phase),
|
|
858
|
-
canonicalJson(
|
|
859
|
-
Object.fromEntries(
|
|
860
|
-
Object.entries(task.phases).map(([name, boundary]) => [
|
|
861
|
-
name,
|
|
862
|
-
Object.fromEntries(
|
|
863
|
-
(["read", "write", "delete"] as const).map((access) => [
|
|
864
|
-
access,
|
|
865
|
-
[...new Set(boundary[access])].sort(compareCanonicalStrings),
|
|
866
|
-
]),
|
|
867
|
-
),
|
|
868
|
-
]),
|
|
869
|
-
),
|
|
870
|
-
),
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
export interface EngineOperationRow {
|
|
875
|
-
command: string;
|
|
876
|
-
state: string;
|
|
877
|
-
outcome_json: string | null;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
export interface ActiveOperation {
|
|
881
|
-
operationId: string;
|
|
882
|
-
kind: "work" | "applying";
|
|
883
|
-
controller: AbortController;
|
|
884
|
-
settled: Promise<Record<string, unknown>>;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
export interface HeldOperationLease {
|
|
888
|
-
lease: OperationLease;
|
|
889
|
-
timer: ReturnType<typeof setInterval>;
|
|
890
|
-
error?: Error;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
export interface BootstrapRow {
|
|
894
|
-
handoff_id: string;
|
|
895
|
-
owner_run_id: string;
|
|
896
|
-
receipt_hash: string;
|
|
897
|
-
state: string;
|
|
898
|
-
facts_json: string;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
902
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
export function normalizeWorkflowVerificationStatus(
|
|
906
|
-
value: unknown,
|
|
907
|
-
): WorkflowVerificationStatus {
|
|
908
|
-
if (
|
|
909
|
-
!isRecord(value) ||
|
|
910
|
-
typeof value.scope !== "string" ||
|
|
911
|
-
![
|
|
912
|
-
"baseline",
|
|
913
|
-
"baseline-task-affected",
|
|
914
|
-
"baseline-full-suite",
|
|
915
|
-
"task-affected",
|
|
916
|
-
"change-task-affected",
|
|
917
|
-
"change-full-suite",
|
|
918
|
-
"agents-checkpoint",
|
|
919
|
-
"post-apply",
|
|
920
|
-
].includes(value.scope) ||
|
|
921
|
-
!["pre-existing", "introduced", "unresolved", "environment"].includes(
|
|
922
|
-
String(value.attribution),
|
|
923
|
-
) ||
|
|
924
|
-
(value.taskId !== undefined && typeof value.taskId !== "string") ||
|
|
925
|
-
(value.failureIdentities !== undefined &&
|
|
926
|
-
(!Array.isArray(value.failureIdentities) ||
|
|
927
|
-
value.failureIdentities.length > 256 ||
|
|
928
|
-
value.failureIdentities.some(
|
|
929
|
-
(identity) => typeof identity !== "string" || !SHA256.test(identity),
|
|
930
|
-
)))
|
|
931
|
-
) {
|
|
932
|
-
throw new Error("workflow-verification-status-invalid");
|
|
933
|
-
}
|
|
934
|
-
return {
|
|
935
|
-
scope: value.scope as WorkflowVerificationStatus["scope"],
|
|
936
|
-
attribution: value.attribution as WorkflowVerificationStatus["attribution"],
|
|
937
|
-
...(typeof value.taskId === "string" ? { taskId: value.taskId } : {}),
|
|
938
|
-
...(Array.isArray(value.failureIdentities)
|
|
939
|
-
? {
|
|
940
|
-
failureIdentities: [
|
|
941
|
-
...new Set(value.failureIdentities as string[]),
|
|
942
|
-
].sort(),
|
|
943
|
-
}
|
|
944
|
-
: {}),
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
export function normalizeWorkflowContextRequest(
|
|
949
|
-
value: unknown,
|
|
950
|
-
): WorkflowContextRequest {
|
|
951
|
-
if (
|
|
952
|
-
!isRecord(value) ||
|
|
953
|
-
![
|
|
954
|
-
"approved-context-needed",
|
|
955
|
-
"task-split-needed",
|
|
956
|
-
"boundary-review-needed",
|
|
957
|
-
].includes(String(value.code))
|
|
958
|
-
) {
|
|
959
|
-
throw new Error("workflow-context-request-invalid");
|
|
960
|
-
}
|
|
961
|
-
let refs: CandidateContextRef[];
|
|
962
|
-
try {
|
|
963
|
-
refs = normalizeCandidateContextRefs(value.refs);
|
|
964
|
-
} catch {
|
|
965
|
-
throw new Error("workflow-context-request-invalid");
|
|
966
|
-
}
|
|
967
|
-
return {
|
|
968
|
-
code: value.code as WorkflowContextRequest["code"],
|
|
969
|
-
refs,
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
export function contextApprovalRefs(request: WorkflowContextRequest): string[] {
|
|
974
|
-
return request.refs.flatMap((ref) =>
|
|
975
|
-
ref.kind === "requested-path" ? [ref.path] : [],
|
|
976
|
-
);
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
export function contextBoundaryForTask(
|
|
980
|
-
task: PlanTaskDraft,
|
|
981
|
-
phase: "red" | "green" | "refactor",
|
|
982
|
-
): CandidateContextBoundary {
|
|
983
|
-
const boundary = task.phases[phase];
|
|
984
|
-
if (!boundary) throw new Error("workflow-task-phase-invalid");
|
|
985
|
-
return {
|
|
986
|
-
phase,
|
|
987
|
-
contextReadRoots: task.roots,
|
|
988
|
-
readPaths: boundary.read,
|
|
989
|
-
writePaths: boundary.write,
|
|
990
|
-
deletePaths: boundary.delete,
|
|
991
|
-
taskPaths: [
|
|
992
|
-
...new Set(
|
|
993
|
-
Object.values(task.phases).flatMap((candidate) => [
|
|
994
|
-
...candidate.read,
|
|
995
|
-
...candidate.write,
|
|
996
|
-
...candidate.delete,
|
|
997
|
-
]),
|
|
998
|
-
),
|
|
999
|
-
],
|
|
1000
|
-
redWritePaths: task.phases.red.write,
|
|
1001
|
-
agents: {
|
|
1002
|
-
impact: task.agents.impact,
|
|
1003
|
-
...(task.agents.target ? { target: task.agents.target } : {}),
|
|
1004
|
-
},
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
export function classifyPersistedContextRequest(
|
|
1009
|
-
request: WorkflowContextRequest,
|
|
1010
|
-
task: PlanTaskDraft,
|
|
1011
|
-
phase: "red" | "green" | "refactor",
|
|
1012
|
-
) {
|
|
1013
|
-
return classifyCandidateContextRequest(
|
|
1014
|
-
{
|
|
1015
|
-
kind: "context-request",
|
|
1016
|
-
candidateId: "candidate-durable-reclassification",
|
|
1017
|
-
code: request.code,
|
|
1018
|
-
refs: request.refs,
|
|
1019
|
-
},
|
|
1020
|
-
contextBoundaryForTask(task, phase),
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
export function approvalRequirement(
|
|
1025
|
-
code: string,
|
|
1026
|
-
contextRequest?: WorkflowContextRequest,
|
|
1027
|
-
): ApprovalRequirement {
|
|
1028
|
-
const requirement = Object.hasOwn(APPROVAL_REQUIREMENT_BY_CODE, code)
|
|
1029
|
-
? APPROVAL_REQUIREMENT_BY_CODE[
|
|
1030
|
-
code as keyof typeof APPROVAL_REQUIREMENT_BY_CODE
|
|
1031
|
-
]
|
|
1032
|
-
: undefined;
|
|
1033
|
-
if (!requirement) throw new Error("approval-code-invalid");
|
|
1034
|
-
return {
|
|
1035
|
-
category: requirement.category,
|
|
1036
|
-
requiredGates: [...requirement.requiredGates],
|
|
1037
|
-
refs: contextRequest ? contextApprovalRefs(contextRequest) : [],
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
export function isWorkflowApprovalCode(
|
|
1042
|
-
code: string,
|
|
1043
|
-
): code is WorkflowApprovalCode {
|
|
1044
|
-
return Object.hasOwn(APPROVAL_REQUIREMENT_BY_CODE, code);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
export function hash(...values: string[]): string {
|
|
1048
|
-
const digest = createHash("sha256");
|
|
1049
|
-
for (const value of values) {
|
|
1050
|
-
digest.update(`${Buffer.byteLength(value)}:`);
|
|
1051
|
-
digest.update(value);
|
|
1052
|
-
}
|
|
1053
|
-
return digest.digest("hex");
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
export interface DependencyContractEntry {
|
|
1057
|
-
value: string;
|
|
1058
|
-
names: string[];
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
export class DependencyManifestError extends Error {
|
|
1062
|
-
constructor() {
|
|
1063
|
-
super("package-manifest-invalid");
|
|
1064
|
-
this.name = "DependencyManifestError";
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
export function dependencyName(selector: string): string | null {
|
|
1069
|
-
const scoped = /(@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*)/iu.exec(
|
|
1070
|
-
selector,
|
|
1071
|
-
)?.[1];
|
|
1072
|
-
if (scoped) return scoped;
|
|
1073
|
-
const segment = selector.split("/").at(-1) ?? "";
|
|
1074
|
-
const name = segment.replace(/@.*$/u, "");
|
|
1075
|
-
return PACKAGE_NAME.test(name) ? name : null;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
export function addDependencyResolutionEntries(
|
|
1079
|
-
result: Map<string, DependencyContractEntry>,
|
|
1080
|
-
section: string,
|
|
1081
|
-
value: unknown,
|
|
1082
|
-
selectorPath: string[] = [],
|
|
1083
|
-
names: string[] = [],
|
|
1084
|
-
): void {
|
|
1085
|
-
if (isRecord(value)) {
|
|
1086
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
1087
|
-
const name = dependencyName(key);
|
|
1088
|
-
addDependencyResolutionEntries(
|
|
1089
|
-
result,
|
|
1090
|
-
section,
|
|
1091
|
-
entry,
|
|
1092
|
-
[...selectorPath, key],
|
|
1093
|
-
name && key !== "." ? [...new Set([...names, name])] : names,
|
|
1094
|
-
);
|
|
1095
|
-
}
|
|
1096
|
-
return;
|
|
1097
|
-
}
|
|
1098
|
-
result.set(`${section}:${selectorPath.join(":")}`, {
|
|
1099
|
-
value: canonicalJson(value),
|
|
1100
|
-
names,
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
export function addDependencyListPolicy(
|
|
1105
|
-
result: Map<string, DependencyContractEntry>,
|
|
1106
|
-
section: string,
|
|
1107
|
-
value: unknown,
|
|
1108
|
-
): void {
|
|
1109
|
-
if (value === undefined) return;
|
|
1110
|
-
if (
|
|
1111
|
-
!Array.isArray(value) ||
|
|
1112
|
-
value.some(
|
|
1113
|
-
(name) => typeof name !== "string" || !PACKAGE_NAME.test(name),
|
|
1114
|
-
) ||
|
|
1115
|
-
new Set(value).size !== value.length
|
|
1116
|
-
) {
|
|
1117
|
-
throw new DependencyManifestError();
|
|
1118
|
-
}
|
|
1119
|
-
result.set(section, {
|
|
1120
|
-
value: canonicalJson([...value].sort()),
|
|
1121
|
-
names: [],
|
|
1122
|
-
});
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
export function addPatchedDependencyPolicy(
|
|
1126
|
-
result: Map<string, DependencyContractEntry>,
|
|
1127
|
-
section: string,
|
|
1128
|
-
value: unknown,
|
|
1129
|
-
): void {
|
|
1130
|
-
if (value === undefined) return;
|
|
1131
|
-
if (!isRecord(value)) throw new DependencyManifestError();
|
|
1132
|
-
for (const [selector, patchPath] of Object.entries(value)) {
|
|
1133
|
-
if (
|
|
1134
|
-
!dependencyName(selector) ||
|
|
1135
|
-
typeof patchPath !== "string" ||
|
|
1136
|
-
!isValidRelativePath(patchPath)
|
|
1137
|
-
) {
|
|
1138
|
-
throw new DependencyManifestError();
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
result.set(section, { value: canonicalJson(value), names: [] });
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
export function dependencyContract(
|
|
1145
|
-
root: string,
|
|
1146
|
-
): Map<string, DependencyContractEntry> {
|
|
1147
|
-
let manifest: unknown;
|
|
1148
|
-
try {
|
|
1149
|
-
manifest = JSON.parse(
|
|
1150
|
-
readFileSync(path.join(root, "package.json"), "utf8"),
|
|
1151
|
-
);
|
|
1152
|
-
} catch {
|
|
1153
|
-
throw new DependencyManifestError();
|
|
1154
|
-
}
|
|
1155
|
-
if (!isRecord(manifest)) throw new DependencyManifestError();
|
|
1156
|
-
|
|
1157
|
-
const result = new Map<string, DependencyContractEntry>();
|
|
1158
|
-
for (const section of [
|
|
1159
|
-
"dependencies",
|
|
1160
|
-
"devDependencies",
|
|
1161
|
-
"peerDependencies",
|
|
1162
|
-
"optionalDependencies",
|
|
1163
|
-
]) {
|
|
1164
|
-
const dependencies = manifest[section];
|
|
1165
|
-
if (dependencies === undefined) continue;
|
|
1166
|
-
if (!isRecord(dependencies)) throw new DependencyManifestError();
|
|
1167
|
-
for (const [name, version] of Object.entries(dependencies)) {
|
|
1168
|
-
if (typeof version !== "string") throw new DependencyManifestError();
|
|
1169
|
-
result.set(`${section}:${name}`, { value: version, names: [name] });
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
for (const section of [
|
|
1173
|
-
"overrides",
|
|
1174
|
-
"resolutions",
|
|
1175
|
-
"dependenciesMeta",
|
|
1176
|
-
"peerDependenciesMeta",
|
|
1177
|
-
]) {
|
|
1178
|
-
if (manifest[section] !== undefined) {
|
|
1179
|
-
addDependencyResolutionEntries(result, section, manifest[section]);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
for (const section of ["workspaces", "catalog", "catalogs"]) {
|
|
1183
|
-
if (manifest[section] !== undefined) {
|
|
1184
|
-
result.set(section, {
|
|
1185
|
-
value: canonicalJson(manifest[section]),
|
|
1186
|
-
names: [],
|
|
1187
|
-
});
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
for (const section of [
|
|
1191
|
-
"trustedDependencies",
|
|
1192
|
-
"blockedDependencies",
|
|
1193
|
-
"bundledDependencies",
|
|
1194
|
-
"bundleDependencies",
|
|
1195
|
-
]) {
|
|
1196
|
-
addDependencyListPolicy(result, section, manifest[section]);
|
|
1197
|
-
}
|
|
1198
|
-
addPatchedDependencyPolicy(
|
|
1199
|
-
result,
|
|
1200
|
-
"patchedDependencies",
|
|
1201
|
-
manifest.patchedDependencies,
|
|
1202
|
-
);
|
|
1203
|
-
|
|
1204
|
-
const pnpm = manifest.pnpm;
|
|
1205
|
-
if (pnpm !== undefined) {
|
|
1206
|
-
if (!isRecord(pnpm)) throw new DependencyManifestError();
|
|
1207
|
-
const resolutionSections = [
|
|
1208
|
-
"overrides",
|
|
1209
|
-
"packageExtensions",
|
|
1210
|
-
"peerDependencyRules",
|
|
1211
|
-
"allowedDeprecatedVersions",
|
|
1212
|
-
];
|
|
1213
|
-
for (const section of resolutionSections) {
|
|
1214
|
-
if (pnpm[section] !== undefined) {
|
|
1215
|
-
addDependencyResolutionEntries(
|
|
1216
|
-
result,
|
|
1217
|
-
`pnpm.${section}`,
|
|
1218
|
-
pnpm[section],
|
|
1219
|
-
);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
addPatchedDependencyPolicy(
|
|
1223
|
-
result,
|
|
1224
|
-
"pnpm.patchedDependencies",
|
|
1225
|
-
pnpm.patchedDependencies,
|
|
1226
|
-
);
|
|
1227
|
-
const tracked = new Set([...resolutionSections, "patchedDependencies"]);
|
|
1228
|
-
const policy = Object.fromEntries(
|
|
1229
|
-
Object.entries(pnpm).filter(([key]) => !tracked.has(key)),
|
|
1230
|
-
);
|
|
1231
|
-
if (Object.keys(policy).length > 0) {
|
|
1232
|
-
result.set("pnpm.policy", { value: canonicalJson(policy), names: [] });
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
return result;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
export function hasUnapprovedDependencyChange(
|
|
1239
|
-
before: Map<string, DependencyContractEntry>,
|
|
1240
|
-
after: Map<string, DependencyContractEntry>,
|
|
1241
|
-
approvedDependencies: readonly string[],
|
|
1242
|
-
lockfileChanged: boolean,
|
|
1243
|
-
): boolean {
|
|
1244
|
-
if (lockfileChanged) return true;
|
|
1245
|
-
const approved = new Set(approvedDependencies);
|
|
1246
|
-
for (const key of new Set([...before.keys(), ...after.keys()])) {
|
|
1247
|
-
const previous = before.get(key);
|
|
1248
|
-
const next = after.get(key);
|
|
1249
|
-
if (previous?.value === next?.value) continue;
|
|
1250
|
-
const names = new Set([...(previous?.names ?? []), ...(next?.names ?? [])]);
|
|
1251
|
-
if (names.size === 0 || [...names].some((name) => !approved.has(name))) {
|
|
1252
|
-
return true;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
return false;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
export function parseJsonRecord(value: string): Record<string, unknown> {
|
|
1259
|
-
const parsed: unknown = JSON.parse(value);
|
|
1260
|
-
if (!isRecord(parsed)) throw new Error("workflow-engine-record-invalid");
|
|
1261
|
-
return parsed;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
export function parseDeliveryDiagnostics(value: string): string[] {
|
|
1265
|
-
const parsed: unknown = JSON.parse(value);
|
|
1266
|
-
if (
|
|
1267
|
-
!Array.isArray(parsed) ||
|
|
1268
|
-
parsed.length === 0 ||
|
|
1269
|
-
parsed.length > 256 ||
|
|
1270
|
-
parsed.some(
|
|
1271
|
-
(diagnostic) =>
|
|
1272
|
-
typeof diagnostic !== "string" ||
|
|
1273
|
-
diagnostic.length === 0 ||
|
|
1274
|
-
diagnostic.length > 1024,
|
|
1275
|
-
) ||
|
|
1276
|
-
new Set(parsed).size !== parsed.length
|
|
1277
|
-
) {
|
|
1278
|
-
throw new Error("workflow-delivery-diagnostics-invalid");
|
|
1279
|
-
}
|
|
1280
|
-
return [...(parsed as string[])];
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
export interface BootstrapAcceptanceFact {
|
|
1284
|
-
command: string;
|
|
1285
|
-
exitCode: 0;
|
|
1286
|
-
evidenceHash: string;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
export function normalizeBootstrapAcceptanceFacts(
|
|
1290
|
-
value: unknown,
|
|
1291
|
-
): BootstrapAcceptanceFact[] | undefined {
|
|
1292
|
-
if (value === undefined) return undefined;
|
|
1293
|
-
if (!Array.isArray(value) || value.length === 0 || value.length > 32) {
|
|
1294
|
-
throw new Error("bootstrap-handoff-invalid");
|
|
1295
|
-
}
|
|
1296
|
-
const commands = new Set<string>();
|
|
1297
|
-
const facts = value.map((entry): BootstrapAcceptanceFact => {
|
|
1298
|
-
if (
|
|
1299
|
-
!isRecord(entry) ||
|
|
1300
|
-
Object.keys(entry).length !== 3 ||
|
|
1301
|
-
!Object.hasOwn(entry, "command") ||
|
|
1302
|
-
!Object.hasOwn(entry, "exitCode") ||
|
|
1303
|
-
!Object.hasOwn(entry, "evidenceHash") ||
|
|
1304
|
-
typeof entry.command !== "string" ||
|
|
1305
|
-
entry.command.length === 0 ||
|
|
1306
|
-
entry.command.length > 512 ||
|
|
1307
|
-
/[\n\r\0]/u.test(entry.command) ||
|
|
1308
|
-
entry.exitCode !== 0 ||
|
|
1309
|
-
typeof entry.evidenceHash !== "string" ||
|
|
1310
|
-
!SHA256.test(entry.evidenceHash) ||
|
|
1311
|
-
commands.has(entry.command)
|
|
1312
|
-
) {
|
|
1313
|
-
throw new Error("bootstrap-handoff-invalid");
|
|
1314
|
-
}
|
|
1315
|
-
commands.add(entry.command);
|
|
1316
|
-
return {
|
|
1317
|
-
command: entry.command,
|
|
1318
|
-
exitCode: 0,
|
|
1319
|
-
evidenceHash: entry.evidenceHash,
|
|
1320
|
-
};
|
|
1321
|
-
});
|
|
1322
|
-
return facts.sort((left, right) =>
|
|
1323
|
-
compareCanonicalStrings(left.command, right.command),
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
export function bootstrapAcceptanceHash(
|
|
1328
|
-
facts: readonly BootstrapAcceptanceFact[],
|
|
1329
|
-
workspaceManifestHash: string,
|
|
1330
|
-
) {
|
|
1331
|
-
return createHash("sha256")
|
|
1332
|
-
.update(
|
|
1333
|
-
canonicalJson({
|
|
1334
|
-
acceptanceFacts: facts,
|
|
1335
|
-
workspaceManifestHash,
|
|
1336
|
-
}),
|
|
1337
|
-
)
|
|
1338
|
-
.digest("hex");
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
export function parsePlan(value: string): ImplementPlan {
|
|
1342
|
-
const parsed: unknown = JSON.parse(value);
|
|
1343
|
-
assertPlan(parsed);
|
|
1344
|
-
return parsed;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
export function assertPlan(value: unknown): asserts value is ImplementPlan {
|
|
1348
|
-
if (
|
|
1349
|
-
!isRecord(value) ||
|
|
1350
|
-
typeof value.changeId !== "string" ||
|
|
1351
|
-
!Array.isArray(value.tasks) ||
|
|
1352
|
-
!Array.isArray(value.outputs) ||
|
|
1353
|
-
!isRecord(value.verification) ||
|
|
1354
|
-
!isRecord(value.verification.artifactCorrection) ||
|
|
1355
|
-
!Number.isSafeInteger(value.verification.artifactCorrection.maxAttempts) ||
|
|
1356
|
-
(value.verification.artifactCorrection.maxAttempts as number) < 2 ||
|
|
1357
|
-
(value.verification.artifactCorrection.maxAttempts as number) > 3 ||
|
|
1358
|
-
value.tasks.length === 0
|
|
1359
|
-
) {
|
|
1360
|
-
throw new Error("delivery-plan-invalid");
|
|
1361
|
-
}
|
|
1362
|
-
const identifiers = new Set<string>();
|
|
1363
|
-
for (const task of value.tasks) {
|
|
1364
|
-
if (
|
|
1365
|
-
!isRecord(task) ||
|
|
1366
|
-
typeof task.taskId !== "string" ||
|
|
1367
|
-
!IDENTIFIER.test(task.taskId) ||
|
|
1368
|
-
identifiers.has(task.taskId) ||
|
|
1369
|
-
!Array.isArray(task.dependsOn) ||
|
|
1370
|
-
!isRecord(task.phases) ||
|
|
1371
|
-
!isRecord(task.phases.red) ||
|
|
1372
|
-
!isRecord(task.phases.green) ||
|
|
1373
|
-
!isRecord(task.scheduling)
|
|
1374
|
-
) {
|
|
1375
|
-
throw new Error("delivery-plan-invalid-task");
|
|
1376
|
-
}
|
|
1377
|
-
identifiers.add(task.taskId);
|
|
1378
|
-
}
|
|
1379
|
-
for (const task of value.tasks as unknown as PlanTaskDraft[]) {
|
|
1380
|
-
if (task.dependsOn.some((dependency) => !identifiers.has(dependency))) {
|
|
1381
|
-
throw new Error("delivery-plan-invalid-dependency");
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
export function assertDelivery(
|
|
1387
|
-
value: unknown,
|
|
1388
|
-
stage: ControlStage,
|
|
1389
|
-
change: string,
|
|
1390
|
-
requestedRevision?: number,
|
|
1391
|
-
requestedReceipt?: string,
|
|
1392
|
-
): asserts value is WorkflowDelivery {
|
|
1393
|
-
if (
|
|
1394
|
-
!isRecord(value) ||
|
|
1395
|
-
(value.gate !== "gate-a" && value.gate !== "gate-b") ||
|
|
1396
|
-
!Number.isSafeInteger(value.revision) ||
|
|
1397
|
-
(value.revision as number) < 1 ||
|
|
1398
|
-
typeof value.receiptHash !== "string" ||
|
|
1399
|
-
!SHA256.test(value.receiptHash)
|
|
1400
|
-
) {
|
|
1401
|
-
throw new Error("delivery-invalid");
|
|
1402
|
-
}
|
|
1403
|
-
assertPlan(value.plan);
|
|
1404
|
-
if (value.approvalProofs !== undefined) {
|
|
1405
|
-
if (!isRecord(value.approvalProofs)) throw new Error("delivery-invalid");
|
|
1406
|
-
for (const gate of ["gateA", "gateB"] as const) {
|
|
1407
|
-
const proof = value.approvalProofs[gate];
|
|
1408
|
-
if (
|
|
1409
|
-
!isRecord(proof) ||
|
|
1410
|
-
!Number.isSafeInteger(proof.revision) ||
|
|
1411
|
-
(proof.revision as number) < 1 ||
|
|
1412
|
-
typeof proof.contractHash !== "string" ||
|
|
1413
|
-
!SHA256.test(proof.contractHash) ||
|
|
1414
|
-
typeof proof.recordHash !== "string" ||
|
|
1415
|
-
!SHA256.test(proof.recordHash)
|
|
1416
|
-
) {
|
|
1417
|
-
throw new Error("delivery-invalid");
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
if (stage === "abel-implement" && value.gate !== "gate-b") {
|
|
1422
|
-
throw new Error("delivery-gate-b-required");
|
|
1423
|
-
}
|
|
1424
|
-
if ((value.plan as ImplementPlan).changeId !== change) {
|
|
1425
|
-
throw new Error("delivery-change-mismatch");
|
|
1426
|
-
}
|
|
1427
|
-
if (requestedRevision !== undefined && value.revision !== requestedRevision) {
|
|
1428
|
-
throw new Error("delivery-revision-mismatch");
|
|
1429
|
-
}
|
|
1430
|
-
if (
|
|
1431
|
-
requestedReceipt !== undefined &&
|
|
1432
|
-
value.receiptHash !== requestedReceipt
|
|
1433
|
-
) {
|
|
1434
|
-
throw new Error("delivery-receipt-mismatch");
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
export function phases(
|
|
1439
|
-
task: PlanTaskDraft,
|
|
1440
|
-
): Array<"red" | "green" | "refactor"> {
|
|
1441
|
-
return [
|
|
1442
|
-
...(!task.verificationMode || task.verificationMode === "behavior"
|
|
1443
|
-
? ["red" as const]
|
|
1444
|
-
: []),
|
|
1445
|
-
"green",
|
|
1446
|
-
...(task.phases.refactor ? (["refactor"] as const) : []),
|
|
1447
|
-
];
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
export function stringArray(value: unknown): string[] {
|
|
1451
|
-
return Array.isArray(value)
|
|
1452
|
-
? value.filter((entry): entry is string => typeof entry === "string")
|
|
1453
|
-
: [];
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
export function declarationPathsConflict(left: string, right: string): boolean {
|
|
1457
|
-
return (
|
|
1458
|
-
left === right ||
|
|
1459
|
-
left === "." ||
|
|
1460
|
-
right === "." ||
|
|
1461
|
-
left.startsWith(`${right}/`) ||
|
|
1462
|
-
right.startsWith(`${left}/`)
|
|
1463
|
-
);
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
export function taskConflicts(
|
|
1467
|
-
left: PlanTaskDraft,
|
|
1468
|
-
right: PlanTaskDraft,
|
|
1469
|
-
): { taskLifetime: boolean; verification: boolean } {
|
|
1470
|
-
if (
|
|
1471
|
-
left.scheduling.conflicts.includes(right.taskId) ||
|
|
1472
|
-
right.scheduling.conflicts.includes(left.taskId)
|
|
1473
|
-
) {
|
|
1474
|
-
return { taskLifetime: true, verification: false };
|
|
1475
|
-
}
|
|
1476
|
-
const leftResources = new Set(left.scheduling.resources);
|
|
1477
|
-
if (right.scheduling.resources.some((entry) => leftResources.has(entry))) {
|
|
1478
|
-
return { taskLifetime: true, verification: false };
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
const declarations = (task: PlanTaskDraft) => {
|
|
1482
|
-
const reads = new Set<string>();
|
|
1483
|
-
const writes = new Set<string>();
|
|
1484
|
-
const verificationLocks = new Set<string>();
|
|
1485
|
-
for (const phase of Object.values(task.phases)) {
|
|
1486
|
-
for (const entry of stringArray(phase.read)) reads.add(entry);
|
|
1487
|
-
for (const entry of stringArray(phase.write)) writes.add(entry);
|
|
1488
|
-
for (const entry of stringArray(phase.delete)) writes.add(entry);
|
|
1489
|
-
if (typeof phase.verificationLock === "string") {
|
|
1490
|
-
verificationLocks.add(phase.verificationLock);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
return { reads, writes, verificationLocks };
|
|
1494
|
-
};
|
|
1495
|
-
const leftDeclarations = declarations(left);
|
|
1496
|
-
const rightDeclarations = declarations(right);
|
|
1497
|
-
const leftWriteConflict = [...leftDeclarations.writes].some((entry) =>
|
|
1498
|
-
[...rightDeclarations.writes, ...rightDeclarations.reads].some((other) =>
|
|
1499
|
-
declarationPathsConflict(entry, other),
|
|
1500
|
-
),
|
|
1501
|
-
);
|
|
1502
|
-
const rightWriteConflict = [...rightDeclarations.writes].some((entry) =>
|
|
1503
|
-
[...leftDeclarations.reads].some((other) =>
|
|
1504
|
-
declarationPathsConflict(entry, other),
|
|
1505
|
-
),
|
|
1506
|
-
);
|
|
1507
|
-
const agentsConflict =
|
|
1508
|
-
left.agents.impact !== "none" &&
|
|
1509
|
-
right.agents.impact !== "none" &&
|
|
1510
|
-
left.agents.target !== undefined &&
|
|
1511
|
-
left.agents.target === right.agents.target;
|
|
1512
|
-
const verification = [...leftDeclarations.verificationLocks].some((lock) =>
|
|
1513
|
-
rightDeclarations.verificationLocks.has(lock),
|
|
1514
|
-
);
|
|
1515
|
-
return {
|
|
1516
|
-
taskLifetime: leftWriteConflict || rightWriteConflict || agentsConflict,
|
|
1517
|
-
verification,
|
|
1518
|
-
};
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
/** Original-baseline obligations do not change a committed candidate contract. */
|
|
1522
|
-
export function taskExecutionContract(task: PlanTaskDraft) {
|
|
1523
|
-
const { baselineVerification: _baselineVerification, ...execution } = task;
|
|
1524
|
-
return execution;
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
export function taskApprovalBoundary(
|
|
1528
|
-
plan: ImplementPlan,
|
|
1529
|
-
task: PlanTaskDraft,
|
|
1530
|
-
): string {
|
|
1531
|
-
const planBoundary = structuredClone(plan) as unknown as Record<
|
|
1532
|
-
string,
|
|
1533
|
-
unknown
|
|
1534
|
-
>;
|
|
1535
|
-
delete planBoundary.tasks;
|
|
1536
|
-
delete planBoundary.outputs;
|
|
1537
|
-
if (isRecord(planBoundary.tracking)) {
|
|
1538
|
-
delete planBoundary.tracking.taskIds;
|
|
1539
|
-
}
|
|
1540
|
-
// Retry limits and presentation do not invalidate verified product facts.
|
|
1541
|
-
if (isRecord(planBoundary.verification)) {
|
|
1542
|
-
delete planBoundary.verification.baseline;
|
|
1543
|
-
delete planBoundary.verification.artifactCorrection;
|
|
1544
|
-
if (isRecord(planBoundary.verification.repair))
|
|
1545
|
-
delete planBoundary.verification.repair.maxAttempts;
|
|
1546
|
-
}
|
|
1547
|
-
return canonicalJson({
|
|
1548
|
-
plan: planBoundary,
|
|
1549
|
-
task: taskExecutionContract(task),
|
|
1550
|
-
outputs: plan.outputs.filter(
|
|
1551
|
-
(output) => output.producer.taskId === task.taskId,
|
|
1552
|
-
),
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
export function workspaceEntryEqual(
|
|
1557
|
-
left: WorkspaceEntry | undefined,
|
|
1558
|
-
right: WorkspaceEntry | undefined,
|
|
1559
|
-
): boolean {
|
|
1560
|
-
if (!left || !right) return left === right;
|
|
1561
|
-
if (left.kind !== right.kind) return false;
|
|
1562
|
-
return (
|
|
1563
|
-
left.kind === "absent" ||
|
|
1564
|
-
(right.kind === "file" &&
|
|
1565
|
-
left.hash === right.hash &&
|
|
1566
|
-
left.bytes === right.bytes &&
|
|
1567
|
-
left.mode === right.mode)
|
|
1568
|
-
);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
export function expectedClassification(
|
|
1572
|
-
phase: string,
|
|
1573
|
-
): "expected-red" | "expected-green" | "expected-refactor" {
|
|
1574
|
-
return phase === "red"
|
|
1575
|
-
? "expected-red"
|
|
1576
|
-
: phase === "refactor"
|
|
1577
|
-
? "expected-refactor"
|
|
1578
|
-
: "expected-green";
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
export type DurableCandidateProposal =
|
|
1582
|
-
| { kind: "candidate"; bytes: Uint8Array }
|
|
1583
|
-
| {
|
|
1584
|
-
kind: "sealed-candidate";
|
|
1585
|
-
candidateId: string;
|
|
1586
|
-
artifactHash: string;
|
|
1587
|
-
bytes: number;
|
|
1588
|
-
paths: string[];
|
|
1589
|
-
}
|
|
1590
|
-
| {
|
|
1591
|
-
kind: "paused" | "retryable" | "approval-needed";
|
|
1592
|
-
code: string;
|
|
1593
|
-
contextRequest?: WorkflowContextRequest;
|
|
1594
|
-
attemptDiagnostic?: SafeAttemptDiagnostic;
|
|
1595
|
-
}
|
|
1596
|
-
| { kind: "operation-cancelled"; code: "cancelled" };
|
|
1597
|
-
|
|
1598
|
-
export type DurableVerificationScope =
|
|
1599
|
-
| "baseline-task-affected"
|
|
1600
|
-
| "baseline-full-suite"
|
|
1601
|
-
| "task-affected"
|
|
1602
|
-
| "change-task-affected"
|
|
1603
|
-
| "change-full-suite"
|
|
1604
|
-
| "agents-checkpoint"
|
|
1605
|
-
| "post-apply";
|
|
1606
|
-
|
|
1607
|
-
export function deliveryTrackingPath(plan: ImplementPlan): string | undefined {
|
|
1608
|
-
if (!isRecord(plan.tracking) || plan.tracking.path !== "tasks.md") {
|
|
1609
|
-
return undefined;
|
|
1610
|
-
}
|
|
1611
|
-
const relative = `openspec/changes/${plan.changeId}/tasks.md`;
|
|
1612
|
-
return isValidRelativePath(relative) ? relative : undefined;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
export function deliveryBoundPaths(plan: ImplementPlan): string[] {
|
|
1616
|
-
const paths = new Set<string>();
|
|
1617
|
-
const bindVerification = (verification: StructuredVerificationContract) => {
|
|
1618
|
-
for (const relative of verificationBoundInputPaths(verification)) {
|
|
1619
|
-
if (relative !== ".") paths.add(relative);
|
|
1620
|
-
}
|
|
1621
|
-
};
|
|
1622
|
-
for (const task of plan.tasks) {
|
|
1623
|
-
for (const phase of Object.values(task.phases)) {
|
|
1624
|
-
for (const relative of [...phase.read, ...phase.write, ...phase.delete]) {
|
|
1625
|
-
if (relative !== ".") paths.add(relative);
|
|
1626
|
-
}
|
|
1627
|
-
for (const binding of phase.verificationInputs) {
|
|
1628
|
-
if (binding.kind === "workspace") paths.add(binding.path);
|
|
1629
|
-
}
|
|
1630
|
-
bindVerification(phase.verification);
|
|
1631
|
-
}
|
|
1632
|
-
if (hasVerificationLifecycle(plan)) {
|
|
1633
|
-
if (task.baselineVerification)
|
|
1634
|
-
bindVerification(task.baselineVerification);
|
|
1635
|
-
bindVerification(task.affectedVerification);
|
|
1636
|
-
bindVerification(task.repairVerification);
|
|
1637
|
-
}
|
|
1638
|
-
if (task.agents.target) paths.add(task.agents.target);
|
|
1639
|
-
}
|
|
1640
|
-
for (const output of plan.outputs) paths.add(output.path);
|
|
1641
|
-
if (hasVerificationLifecycle(plan)) {
|
|
1642
|
-
bindVerification(plan.verification.baseline.fullSuite);
|
|
1643
|
-
bindVerification(plan.verification.change.fullSuite);
|
|
1644
|
-
bindVerification(plan.verification.change.postApply);
|
|
1645
|
-
if (plan.verification.agentsCheckpoint.verification) {
|
|
1646
|
-
bindVerification(plan.verification.agentsCheckpoint.verification);
|
|
1647
|
-
}
|
|
1648
|
-
for (const operation of plan.verification.agentsCheckpoint.operations) {
|
|
1649
|
-
paths.add(operation.target);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
const trackingPath = deliveryTrackingPath(plan);
|
|
1653
|
-
if (trackingPath) paths.add(trackingPath);
|
|
1654
|
-
return [...paths].sort();
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
export function hasVerificationLifecycle(plan: ImplementPlan): boolean {
|
|
1658
|
-
return (
|
|
1659
|
-
isRecord(plan.verification) &&
|
|
1660
|
-
isRecord(plan.verification.baseline) &&
|
|
1661
|
-
isRecord(plan.verification.change) &&
|
|
1662
|
-
isRecord(plan.verification.repair) &&
|
|
1663
|
-
isRecord(plan.tracking) &&
|
|
1664
|
-
plan.tasks.every(
|
|
1665
|
-
(task) =>
|
|
1666
|
-
isRecord(task.affectedVerification) &&
|
|
1667
|
-
isRecord(task.repairVerification),
|
|
1668
|
-
)
|
|
1669
|
-
);
|
|
1670
|
-
}
|