owlrunkit 0.18.3 → 0.19.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/CHANGELOG.md +80 -0
- package/README.md +223 -52
- package/docs/architecture/OWLCODA_RUNKIT_0184_EXTERNAL_DELIVERY_INTAKE_PRODUCT_DECISION_20260812.md +209 -0
- package/docs/architecture/OWLCODA_RUNKIT_0190_CONSOLIDATED_DAILY_USE_PRODUCT_DECISION_20260812.md +97 -0
- package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +8 -7
- package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +57 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +133 -77
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +57 -0
- package/package.json +1 -1
- package/packages/attest/README.md +9 -5
- package/packages/attest/consumer-surface-v2.json +263 -0
- package/packages/attest/distribution-manifest-v1.json +14 -1
- package/packages/attest/schemas/quick-verification-receipt-v3.schema.json +244 -0
- package/packages/attest/schemas/workspace-snapshot-v2.schema.json +114 -0
- package/packages/attest/src/dependency-environment.mjs +128 -0
- package/packages/attest/src/index.mjs +1 -0
- package/packages/attest/src/quick-receipt-contract.mjs +205 -12
- package/packages/attest/src/quick.mjs +45 -4
- package/packages/attest/src/workspace-snapshot.mjs +143 -7
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/assurance-router.mjs +25 -6
- package/scripts/runkit-contract/core-contract.mjs +29 -1
- package/scripts/runkit-contract/efficiency-metrics.mjs +559 -0
- package/scripts/runkit-contract/evidence-store.mjs +130 -0
- package/scripts/runkit-contract/onboarding-bootstrap.mjs +685 -0
- package/scripts/runkit-contract/onboarding-doctor.mjs +3 -2
- package/scripts/runkit-contract/outcome-presentation.mjs +171 -0
- package/scripts/runkit-contract/profile-onboarding.mjs +285 -17
- package/scripts/runkit-contract/project-cli-resolver.mjs +29 -5
- package/scripts/runkit-contract/project-external-delivery.mjs +707 -0
- package/scripts/runkit-contract/quick-evidence-reuse.mjs +590 -0
- package/scripts/runkit-contract/quick-isolation.mjs +314 -0
- package/scripts/runkit-contract/quick-metrics.mjs +51 -3
- package/scripts/runkit-contract/quick-receipt.mjs +31 -1
- package/scripts/runkit-contract/quick-verify.mjs +217 -26
- package/scripts/runkit-contract/quick-workspace-snapshot.mjs +1 -0
- package/scripts/runkit-contract/runkit-bootstrap.mjs +7 -1
- package/scripts/runkit-contract/runkit-cli.mjs +602 -152
- package/scripts/runkit-contract/runtime-transition.mjs +478 -0
- package/scripts/runkit-contract/schemas/assurance-route-v2.schema.json +26 -0
- package/scripts/runkit-contract/schemas/efficiency-event-v1.schema.json +56 -0
- package/scripts/runkit-contract/schemas/efficiency-metrics-v1.schema.json +220 -0
- package/scripts/runkit-contract/schemas/external-runtime-report-v1.schema.json +125 -0
- package/scripts/runkit-contract/schemas/onboarding-bootstrap-plan-v1.schema.json +168 -0
- package/scripts/runkit-contract/schemas/onboarding-bootstrap-receipt-v1.schema.json +130 -0
- package/scripts/runkit-contract/schemas/outcome-summary-v1.schema.json +83 -0
- package/scripts/runkit-contract/schemas/profile-detection-v3.schema.json +170 -0
- package/scripts/runkit-contract/schemas/quick-evidence-reuse-plan-v1.schema.json +59 -0
- package/scripts/runkit-contract/schemas/quick-evidence-reuse-receipt-v1.schema.json +79 -0
- package/scripts/runkit-contract/schemas/runtime-transition-receipt-v1.schema.json +185 -0
- package/scripts/runkit-contract/schemas/team-project-event-v3.schema.json +146 -0
- package/scripts/runkit-contract/schemas/team-project-event-v4.schema.json +142 -0
- package/scripts/runkit-contract/schemas/team-project-status-v2.schema.json +85 -0
- package/scripts/runkit-contract/schemas/usage-mode-recommendation-v2.schema.json +136 -0
- package/scripts/runkit-contract/team-project.mjs +1172 -22
- package/scripts/runkit-contract/usage-mode.mjs +28 -7
- package/scripts/runkit-contract/verification-envelope.mjs +15 -18
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
import { captureWorkspaceSnapshot } from "./quick-workspace-snapshot.mjs";
|
|
13
|
+
import {
|
|
14
|
+
decodeUtf8Strict,
|
|
15
|
+
readFileBytesBounded,
|
|
16
|
+
} from "../../packages/attest/src/formal.mjs";
|
|
17
|
+
import {
|
|
18
|
+
quickSnapshotFingerprintValid,
|
|
19
|
+
validWorkspaceSnapshotShape,
|
|
20
|
+
} from "../../packages/attest/src/quick-receipt-contract.mjs";
|
|
21
|
+
import { validateLeaseOwnedPaths } from "./core-contract.mjs";
|
|
22
|
+
import {
|
|
23
|
+
relativeToWorkspace,
|
|
24
|
+
safeIdentifier,
|
|
25
|
+
writeJsonExclusiveAtomically,
|
|
26
|
+
} from "./provenance-common.mjs";
|
|
27
|
+
import { verifyDeliveryPacket } from "./source-fingerprint.mjs";
|
|
28
|
+
import { parseJsonStrict } from "./quick-canonical.mjs";
|
|
29
|
+
import {
|
|
30
|
+
appendTeamProjectEventV1,
|
|
31
|
+
normalizeTeamProjectDefinitionV1,
|
|
32
|
+
readTeamProjectStatusV2,
|
|
33
|
+
teamProjectDefinitionBindingV1,
|
|
34
|
+
withTeamProjectControlLockV1,
|
|
35
|
+
} from "./team-project.mjs";
|
|
36
|
+
|
|
37
|
+
const SNAPSHOT_SCHEMA = "OwlCodaRunKitTargetWorkspaceSnapshotV1";
|
|
38
|
+
const EVENT_SCHEMA = "OwlCodaRunKitTeamProjectEventV3";
|
|
39
|
+
const PROJECT_ROOT = ".owlcoda/runkit/project";
|
|
40
|
+
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
41
|
+
const DEPENDENCY_ENVIRONMENT_NAMES = new Set([
|
|
42
|
+
"bun.lock",
|
|
43
|
+
"bun.lockb",
|
|
44
|
+
"Cargo.lock",
|
|
45
|
+
"composer.lock",
|
|
46
|
+
"Gemfile.lock",
|
|
47
|
+
"go.sum",
|
|
48
|
+
"npm-shrinkwrap.json",
|
|
49
|
+
"package-lock.json",
|
|
50
|
+
"pnpm-lock.yaml",
|
|
51
|
+
"poetry.lock",
|
|
52
|
+
"uv.lock",
|
|
53
|
+
"yarn.lock",
|
|
54
|
+
]);
|
|
55
|
+
const TOOL_CACHE_SEGMENTS = new Set([
|
|
56
|
+
".cache",
|
|
57
|
+
".mypy_cache",
|
|
58
|
+
".pytest_cache",
|
|
59
|
+
".ruff_cache",
|
|
60
|
+
".turbo",
|
|
61
|
+
".vite",
|
|
62
|
+
".vitest",
|
|
63
|
+
"__pycache__",
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
function canonical(value) {
|
|
67
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
68
|
+
if (value && typeof value === "object") {
|
|
69
|
+
return `{${Object.keys(value).sort().map(key => (
|
|
70
|
+
`${JSON.stringify(key)}:${canonical(value[key])}`
|
|
71
|
+
)).join(",")}}`;
|
|
72
|
+
}
|
|
73
|
+
const encoded = JSON.stringify(value);
|
|
74
|
+
if (encoded === undefined) throw new Error("External delivery artifact is not canonical JSON.");
|
|
75
|
+
return encoded;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sha256(value) {
|
|
79
|
+
return createHash("sha256").update(value).digest("hex");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function artifactSha256(value) {
|
|
83
|
+
return `sha256:${sha256(canonical(value))}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateTimestamp(value, label) {
|
|
87
|
+
if (typeof value !== "string"
|
|
88
|
+
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value)
|
|
89
|
+
|| Number.isNaN(Date.parse(value))) {
|
|
90
|
+
throw new Error(`${label} must be an ISO-8601 UTC timestamp.`);
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compareIdentifiers(left, right) {
|
|
96
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function uniqueSorted(values) {
|
|
100
|
+
return [...new Set(values)].sort(compareIdentifiers);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function pathPrefix(rule) {
|
|
104
|
+
return rule.endsWith("/**") ? rule.slice(0, -3) : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function ruleCovers(rule, candidate) {
|
|
108
|
+
if (rule === candidate) return true;
|
|
109
|
+
const prefix = pathPrefix(rule);
|
|
110
|
+
return prefix !== null && (candidate === prefix || candidate.startsWith(`${prefix}/`));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function allowedBy(filePath, rules) {
|
|
114
|
+
return rules.some(rule => ruleCovers(rule, filePath));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function canonicalControllerRoot(workspaceRoot) {
|
|
118
|
+
const requested = path.resolve(workspaceRoot);
|
|
119
|
+
const stat = lstatSync(requested);
|
|
120
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
121
|
+
throw new Error("Controller workspace must be a real directory.");
|
|
122
|
+
}
|
|
123
|
+
return realpathSync(requested);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function canonicalTargetRoot(targetWorkspaceRoot) {
|
|
127
|
+
const requested = path.resolve(targetWorkspaceRoot);
|
|
128
|
+
const stat = lstatSync(requested);
|
|
129
|
+
const resolved = realpathSync(requested);
|
|
130
|
+
if (stat.isSymbolicLink() || !stat.isDirectory() || resolved !== requested) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
"Target workspace must be a real directory without symlink traversal. "
|
|
133
|
+
+ `Retry with --target-workspace '${resolved.replaceAll("'", `'\"'\"'`)}'.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
let gitRoot;
|
|
137
|
+
try {
|
|
138
|
+
gitRoot = realpathSync(execFileSync(
|
|
139
|
+
"git",
|
|
140
|
+
["-C", resolved, "rev-parse", "--show-toplevel"],
|
|
141
|
+
{
|
|
142
|
+
encoding: "utf8",
|
|
143
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
144
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
145
|
+
},
|
|
146
|
+
).trim());
|
|
147
|
+
} catch {
|
|
148
|
+
throw new Error("Target workspace must be the exact Git worktree root.");
|
|
149
|
+
}
|
|
150
|
+
if (gitRoot !== resolved) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Target workspace must be the exact Git worktree root: ${gitRoot}.`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return resolved;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function containedBy(parent, child) {
|
|
159
|
+
const relative = path.relative(parent, child);
|
|
160
|
+
return relative === ""
|
|
161
|
+
|| (!relative.startsWith(`..${path.sep}`)
|
|
162
|
+
&& relative !== ".."
|
|
163
|
+
&& !path.isAbsolute(relative));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function assertDisjointWorkspaces(controllerRoot, targetRoot) {
|
|
167
|
+
if (containedBy(controllerRoot, targetRoot) || containedBy(targetRoot, controllerRoot)) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
"Controller and target workspaces must be disjoint real directories.",
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function readProjectDefinition(controllerRoot) {
|
|
175
|
+
const definitionPath = path.join(controllerRoot, PROJECT_ROOT, "definition.json");
|
|
176
|
+
const stat = lstatSync(definitionPath);
|
|
177
|
+
if (stat.isSymbolicLink() || !stat.isFile() || realpathSync(definitionPath) !== definitionPath) {
|
|
178
|
+
throw new Error("Team project definition must be a regular file without symlinks.");
|
|
179
|
+
}
|
|
180
|
+
return normalizeTeamProjectDefinitionV1(JSON.parse(readFileSync(definitionPath, "utf8")));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resolveAllowedPaths(workItem, requested) {
|
|
184
|
+
if (requested === undefined || requested.length === 0) return [...workItem.ownedPaths];
|
|
185
|
+
const allowedPaths = validateLeaseOwnedPaths(requested);
|
|
186
|
+
for (const rule of allowedPaths) {
|
|
187
|
+
if (!workItem.ownedPaths.some(owner => (
|
|
188
|
+
owner === rule
|
|
189
|
+
|| (pathPrefix(owner) !== null
|
|
190
|
+
&& (pathPrefix(rule) ?? rule) !== null
|
|
191
|
+
&& ruleCovers(owner, pathPrefix(rule) ?? rule))
|
|
192
|
+
))) {
|
|
193
|
+
throw new Error(`Allowed target path is outside WorkItem ownership: ${rule}.`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return allowedPaths;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function stableWorkspaceSnapshot(targetRoot) {
|
|
200
|
+
const before = captureWorkspaceSnapshot(targetRoot);
|
|
201
|
+
const after = captureWorkspaceSnapshot(targetRoot);
|
|
202
|
+
if (canonical(before) !== canonical(after)) {
|
|
203
|
+
throw new Error("Target workspace changed while its snapshot was being captured.");
|
|
204
|
+
}
|
|
205
|
+
return after;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function ensureSnapshotRoot(controllerRoot) {
|
|
209
|
+
const projectRoot = path.join(controllerRoot, PROJECT_ROOT);
|
|
210
|
+
const projectStat = lstatSync(projectRoot);
|
|
211
|
+
if (projectStat.isSymbolicLink()
|
|
212
|
+
|| !projectStat.isDirectory()
|
|
213
|
+
|| realpathSync(projectRoot) !== projectRoot) {
|
|
214
|
+
throw new Error("Team project control root must be a regular directory without symlinks.");
|
|
215
|
+
}
|
|
216
|
+
const snapshotRoot = path.join(projectRoot, "target-snapshots");
|
|
217
|
+
if (!existsSync(snapshotRoot)) mkdirSync(snapshotRoot);
|
|
218
|
+
const stat = lstatSync(snapshotRoot);
|
|
219
|
+
if (stat.isSymbolicLink() || !stat.isDirectory() || realpathSync(snapshotRoot) !== snapshotRoot) {
|
|
220
|
+
throw new Error("Target snapshot root must be a regular directory without symlinks.");
|
|
221
|
+
}
|
|
222
|
+
return snapshotRoot;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function snapshotBody({
|
|
226
|
+
snapshotId,
|
|
227
|
+
occurredAt,
|
|
228
|
+
projectBinding,
|
|
229
|
+
workItemId,
|
|
230
|
+
targetRoot,
|
|
231
|
+
allowedPaths,
|
|
232
|
+
workspaceSnapshot,
|
|
233
|
+
}) {
|
|
234
|
+
return {
|
|
235
|
+
schemaVersion: SNAPSHOT_SCHEMA,
|
|
236
|
+
snapshotId,
|
|
237
|
+
capturedAt: occurredAt,
|
|
238
|
+
projectId: projectBinding.projectId,
|
|
239
|
+
projectDefinitionSha256: projectBinding.projectDefinitionSha256,
|
|
240
|
+
workItemId,
|
|
241
|
+
targetWorkspaceRef: targetRoot,
|
|
242
|
+
allowedPaths,
|
|
243
|
+
workspaceSnapshot,
|
|
244
|
+
targetWritePerformed: false,
|
|
245
|
+
authorizationGranted: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function captureTargetWorkspaceSnapshotV1({
|
|
250
|
+
controllerWorkspaceRoot,
|
|
251
|
+
snapshotId,
|
|
252
|
+
occurredAt,
|
|
253
|
+
workItemId,
|
|
254
|
+
targetWorkspaceRoot,
|
|
255
|
+
allowedPaths,
|
|
256
|
+
apply = false,
|
|
257
|
+
}) {
|
|
258
|
+
safeIdentifier(snapshotId, "snapshotId");
|
|
259
|
+
validateTimestamp(occurredAt, "Target snapshot capturedAt");
|
|
260
|
+
const controllerRoot = canonicalControllerRoot(controllerWorkspaceRoot);
|
|
261
|
+
const targetRoot = canonicalTargetRoot(targetWorkspaceRoot);
|
|
262
|
+
assertDisjointWorkspaces(controllerRoot, targetRoot);
|
|
263
|
+
const definition = readProjectDefinition(controllerRoot);
|
|
264
|
+
const workItem = definition.workItems.find(row => row.id === workItemId);
|
|
265
|
+
if (!workItem) throw new Error(`Unknown team project WorkItem: ${workItemId}.`);
|
|
266
|
+
const projectBinding = teamProjectDefinitionBindingV1(definition);
|
|
267
|
+
const selectedAllowedPaths = resolveAllowedPaths(workItem, allowedPaths);
|
|
268
|
+
const body = snapshotBody({
|
|
269
|
+
snapshotId,
|
|
270
|
+
occurredAt,
|
|
271
|
+
projectBinding,
|
|
272
|
+
workItemId,
|
|
273
|
+
targetRoot,
|
|
274
|
+
allowedPaths: selectedAllowedPaths,
|
|
275
|
+
workspaceSnapshot: stableWorkspaceSnapshot(targetRoot),
|
|
276
|
+
});
|
|
277
|
+
const artifact = { ...body, snapshotSha256: artifactSha256(body) };
|
|
278
|
+
if (!apply) {
|
|
279
|
+
return {
|
|
280
|
+
status: "target_snapshot_preview",
|
|
281
|
+
exitCode: 0,
|
|
282
|
+
snapshotId,
|
|
283
|
+
targetWorkspaceRef: targetRoot,
|
|
284
|
+
targetHead: artifact.workspaceSnapshot.headCommit,
|
|
285
|
+
workspaceSnapshotFingerprint: artifact.workspaceSnapshot.sourceFingerprint,
|
|
286
|
+
allowedPaths: selectedAllowedPaths,
|
|
287
|
+
snapshotSha256: artifact.snapshotSha256,
|
|
288
|
+
persisted: false,
|
|
289
|
+
targetWritePerformed: false,
|
|
290
|
+
authorizationGranted: false,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const persisted = withTeamProjectControlLockV1({
|
|
295
|
+
workspaceRoot: controllerRoot,
|
|
296
|
+
operation({ definition: currentDefinition, definitionBinding }) {
|
|
297
|
+
if (canonical(definitionBinding) !== canonical(projectBinding)) {
|
|
298
|
+
throw new Error("Team project identity changed before target snapshot persistence.");
|
|
299
|
+
}
|
|
300
|
+
const currentWorkItem = currentDefinition.workItems.find(row => row.id === workItemId);
|
|
301
|
+
if (!currentWorkItem
|
|
302
|
+
|| canonical(resolveAllowedPaths(currentWorkItem, selectedAllowedPaths))
|
|
303
|
+
!== canonical(selectedAllowedPaths)) {
|
|
304
|
+
throw new Error("Team project WorkItem ownership changed before target snapshot persistence.");
|
|
305
|
+
}
|
|
306
|
+
const snapshotRoot = ensureSnapshotRoot(controllerRoot);
|
|
307
|
+
const snapshotPath = path.join(snapshotRoot, `${snapshotId}.json`);
|
|
308
|
+
let resumed = false;
|
|
309
|
+
if (existsSync(snapshotPath)) {
|
|
310
|
+
const existing = JSON.parse(readFileSync(snapshotPath, "utf8"));
|
|
311
|
+
if (canonical(existing) !== canonical(artifact)) {
|
|
312
|
+
throw new Error(`Immutable target snapshot differs: ${snapshotId}.`);
|
|
313
|
+
}
|
|
314
|
+
resumed = true;
|
|
315
|
+
} else {
|
|
316
|
+
writeJsonExclusiveAtomically(snapshotPath, artifact);
|
|
317
|
+
}
|
|
318
|
+
return { snapshotPath, resumed };
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
status: "target_snapshot_captured",
|
|
323
|
+
exitCode: 0,
|
|
324
|
+
snapshotId,
|
|
325
|
+
snapshotPath: relativeToWorkspace(controllerRoot, persisted.snapshotPath),
|
|
326
|
+
targetWorkspaceRef: targetRoot,
|
|
327
|
+
targetHead: artifact.workspaceSnapshot.headCommit,
|
|
328
|
+
workspaceSnapshotFingerprint: artifact.workspaceSnapshot.sourceFingerprint,
|
|
329
|
+
allowedPaths: selectedAllowedPaths,
|
|
330
|
+
snapshotSha256: artifact.snapshotSha256,
|
|
331
|
+
persisted: true,
|
|
332
|
+
resumed: persisted.resumed,
|
|
333
|
+
targetWritePerformed: false,
|
|
334
|
+
authorizationGranted: false,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function readPacket(packetPath) {
|
|
339
|
+
const requested = path.resolve(packetPath);
|
|
340
|
+
let loaded;
|
|
341
|
+
try {
|
|
342
|
+
loaded = readFileBytesBounded(requested);
|
|
343
|
+
} catch {
|
|
344
|
+
throw new Error("External DeliveryPacket must be a bounded regular file without symlinks.");
|
|
345
|
+
}
|
|
346
|
+
if (loaded.absolutePath !== requested) {
|
|
347
|
+
throw new Error("External DeliveryPacket must be a bounded regular file without symlinks.");
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
packet: parseJsonStrict(decodeUtf8Strict(loaded.bytes)),
|
|
351
|
+
bytes: loaded.bytes,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function packetIdentity(packet) {
|
|
356
|
+
const identity = {
|
|
357
|
+
schemaVersion: packet.schemaVersion ?? "ExternalDeliveryPacket",
|
|
358
|
+
changedFiles: packet.changedFiles,
|
|
359
|
+
};
|
|
360
|
+
if (packet.productSourceFingerprint !== undefined) {
|
|
361
|
+
identity.productSourceFingerprint = packet.productSourceFingerprint;
|
|
362
|
+
}
|
|
363
|
+
if (packet.sourceFingerprint !== undefined) {
|
|
364
|
+
identity.sourceFingerprint = packet.sourceFingerprint;
|
|
365
|
+
}
|
|
366
|
+
return identity;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function packetFiles(packet) {
|
|
370
|
+
const files = packet.changedFiles?.files ?? packet.changedFiles?.wholeFileSha256;
|
|
371
|
+
if (!files || typeof files !== "object" || Array.isArray(files)) {
|
|
372
|
+
throw new Error("External DeliveryPacket changed file map is missing.");
|
|
373
|
+
}
|
|
374
|
+
return Object.entries(files)
|
|
375
|
+
.map(([filePath, hash]) => ({ path: filePath, sha256: String(hash).toLowerCase() }))
|
|
376
|
+
.sort((left, right) => compareIdentifiers(left.path, right.path));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function readEntrySnapshot(controllerRoot, snapshotId) {
|
|
380
|
+
safeIdentifier(snapshotId, "entrySnapshotId");
|
|
381
|
+
const snapshotPath = path.join(
|
|
382
|
+
controllerRoot,
|
|
383
|
+
PROJECT_ROOT,
|
|
384
|
+
"target-snapshots",
|
|
385
|
+
`${snapshotId}.json`,
|
|
386
|
+
);
|
|
387
|
+
const stat = lstatSync(snapshotPath);
|
|
388
|
+
if (stat.isSymbolicLink() || !stat.isFile() || realpathSync(snapshotPath) !== snapshotPath) {
|
|
389
|
+
throw new Error("Entry target snapshot must be a regular file without symlinks.");
|
|
390
|
+
}
|
|
391
|
+
const artifact = JSON.parse(readFileSync(snapshotPath, "utf8"));
|
|
392
|
+
const { snapshotSha256, ...body } = artifact;
|
|
393
|
+
if (artifact.schemaVersion !== SNAPSHOT_SCHEMA
|
|
394
|
+
|| !/^sha256:[a-f0-9]{64}$/u.test(snapshotSha256)
|
|
395
|
+
|| snapshotSha256 !== artifactSha256(body)
|
|
396
|
+
|| !validWorkspaceSnapshotShape(artifact.workspaceSnapshot)
|
|
397
|
+
|| !quickSnapshotFingerprintValid(artifact.workspaceSnapshot)) {
|
|
398
|
+
throw new Error("Entry target snapshot identity is invalid.");
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
artifact,
|
|
402
|
+
ref: relativeToWorkspace(controllerRoot, snapshotPath),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function overlayChanges(entrySnapshot, currentSnapshot) {
|
|
407
|
+
const before = new Map(entrySnapshot.dirtyOverlay.map(row => [row.path, canonical(row)]));
|
|
408
|
+
const after = new Map(currentSnapshot.dirtyOverlay.map(row => [row.path, canonical(row)]));
|
|
409
|
+
return uniqueSorted([...before.keys(), ...after.keys()].filter(filePath => (
|
|
410
|
+
before.get(filePath) !== after.get(filePath)
|
|
411
|
+
)));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function baseIdentityChanges(entrySnapshot, currentSnapshot) {
|
|
415
|
+
const changes = [];
|
|
416
|
+
if (entrySnapshot.headCommit !== currentSnapshot.headCommit) {
|
|
417
|
+
changes.push("head_commit_changed");
|
|
418
|
+
}
|
|
419
|
+
if (entrySnapshot.trackedTreeIdentity !== currentSnapshot.trackedTreeIdentity) {
|
|
420
|
+
changes.push("tracked_tree_changed");
|
|
421
|
+
}
|
|
422
|
+
if (canonical(entrySnapshot.submodules) !== canonical(currentSnapshot.submodules)) {
|
|
423
|
+
changes.push("submodules_changed");
|
|
424
|
+
}
|
|
425
|
+
return changes;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function toolCachePath(filePath) {
|
|
429
|
+
const segments = filePath.split("/");
|
|
430
|
+
if (segments.some(segment => TOOL_CACHE_SEGMENTS.has(segment))) return true;
|
|
431
|
+
if (filePath.endsWith(".tsbuildinfo") || filePath.endsWith("/.eslintcache")) return true;
|
|
432
|
+
return segments.includes(".next") && segments.includes("cache");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function dependencyEnvironmentPath(filePath) {
|
|
436
|
+
const basename = path.posix.basename(filePath);
|
|
437
|
+
return DEPENDENCY_ENVIRONMENT_NAMES.has(basename)
|
|
438
|
+
|| filePath === ".pnp.cjs"
|
|
439
|
+
|| filePath === ".pnp.loader.mjs"
|
|
440
|
+
|| filePath.startsWith("node_modules/");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function classifyOverlay({
|
|
444
|
+
entry,
|
|
445
|
+
currentSnapshot,
|
|
446
|
+
candidateFiles,
|
|
447
|
+
allowedPaths,
|
|
448
|
+
}) {
|
|
449
|
+
const candidatePaths = candidateFiles.map(row => row.path);
|
|
450
|
+
if (entry === null) {
|
|
451
|
+
const disallowed = candidatePaths.filter(filePath => !allowedBy(filePath, allowedPaths));
|
|
452
|
+
return {
|
|
453
|
+
status: disallowed.length === 0 ? "candidate_only" : "rejected",
|
|
454
|
+
overlayAttested: false,
|
|
455
|
+
candidatePaths,
|
|
456
|
+
changedPaths: [],
|
|
457
|
+
toolCachePaths: [],
|
|
458
|
+
dependencyEnvironmentPaths: [],
|
|
459
|
+
unchangedPacketPaths: [],
|
|
460
|
+
outOfScopeSourcePaths: uniqueSorted(disallowed),
|
|
461
|
+
baseIdentityChanges: [],
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const changedPaths = overlayChanges(entry.workspaceSnapshot, currentSnapshot);
|
|
465
|
+
const identityChanges = baseIdentityChanges(entry.workspaceSnapshot, currentSnapshot);
|
|
466
|
+
const candidateSet = new Set(candidatePaths);
|
|
467
|
+
const changedSet = new Set(changedPaths);
|
|
468
|
+
const toolCachePaths = [];
|
|
469
|
+
const dependencyEnvironmentPaths = [];
|
|
470
|
+
const outOfScopeSourcePaths = candidatePaths.filter(filePath => !allowedBy(filePath, allowedPaths));
|
|
471
|
+
for (const filePath of changedPaths) {
|
|
472
|
+
if (candidateSet.has(filePath) && allowedBy(filePath, allowedPaths)) continue;
|
|
473
|
+
if (toolCachePath(filePath)) toolCachePaths.push(filePath);
|
|
474
|
+
else if (dependencyEnvironmentPath(filePath)) dependencyEnvironmentPaths.push(filePath);
|
|
475
|
+
else outOfScopeSourcePaths.push(filePath);
|
|
476
|
+
}
|
|
477
|
+
const uniqueOutOfScope = uniqueSorted(outOfScopeSourcePaths);
|
|
478
|
+
return {
|
|
479
|
+
status: uniqueOutOfScope.length > 0 || identityChanges.length > 0
|
|
480
|
+
? "rejected"
|
|
481
|
+
: toolCachePaths.length > 0 || dependencyEnvironmentPaths.length > 0
|
|
482
|
+
? "attested_with_non_source_changes"
|
|
483
|
+
: "attested",
|
|
484
|
+
overlayAttested: uniqueOutOfScope.length === 0 && identityChanges.length === 0,
|
|
485
|
+
candidatePaths,
|
|
486
|
+
changedPaths,
|
|
487
|
+
toolCachePaths: uniqueSorted(toolCachePaths),
|
|
488
|
+
dependencyEnvironmentPaths: uniqueSorted(dependencyEnvironmentPaths),
|
|
489
|
+
unchangedPacketPaths: candidatePaths.filter(filePath => !changedSet.has(filePath)),
|
|
490
|
+
outOfScopeSourcePaths: uniqueOutOfScope,
|
|
491
|
+
baseIdentityChanges: identityChanges,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function proofBoundary(overlay, { entrySnapshotSupplied }) {
|
|
496
|
+
const proven = [
|
|
497
|
+
"The DeliveryPacket candidate file hashes match the current target workspace.",
|
|
498
|
+
"The target workspace was read twice without observed source drift.",
|
|
499
|
+
];
|
|
500
|
+
const notProven = [
|
|
501
|
+
"Test correctness, Git integration, release, deployment, and business acceptance were not proven.",
|
|
502
|
+
];
|
|
503
|
+
if (overlay.overlayAttested) {
|
|
504
|
+
proven.push("The entry-to-candidate visible Git dirty overlay contains no unclassified source delta.");
|
|
505
|
+
} else if (!entrySnapshotSupplied) {
|
|
506
|
+
notProven.unshift(
|
|
507
|
+
"The entry-to-candidate dirty overlay was not proven because no entry snapshot was supplied.",
|
|
508
|
+
);
|
|
509
|
+
} else if (overlay.baseIdentityChanges.length > 0) {
|
|
510
|
+
notProven.unshift(
|
|
511
|
+
`The entry-to-candidate base identity changed: ${overlay.baseIdentityChanges.join(", ")}.`,
|
|
512
|
+
);
|
|
513
|
+
} else {
|
|
514
|
+
notProven.unshift(
|
|
515
|
+
"The entry-to-candidate dirty overlay was rejected because it contains out-of-scope or unclassified source changes.",
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
return { proven, notProven };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function validateCurrentAssignment(controllerRoot, workItemId, assignmentId) {
|
|
522
|
+
safeIdentifier(assignmentId, "assignmentId");
|
|
523
|
+
const status = readTeamProjectStatusV2({ workspaceRoot: controllerRoot });
|
|
524
|
+
const row = status.workItems.find(item => item.workItemId === workItemId);
|
|
525
|
+
if (row?.assignmentId !== assignmentId) {
|
|
526
|
+
throw new Error(
|
|
527
|
+
`External delivery must bind the current assignment ${row?.assignmentId ?? "none"}.`,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
if (["completed", "failed"].includes(row.status)) {
|
|
531
|
+
throw new Error(
|
|
532
|
+
"External delivery requires a non-terminal WorkItem; use an explicit reopen or rework contract first.",
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function importExternalDeliveryV1({
|
|
538
|
+
controllerWorkspaceRoot,
|
|
539
|
+
deliveryId,
|
|
540
|
+
occurredAt,
|
|
541
|
+
assignmentId,
|
|
542
|
+
workItemId,
|
|
543
|
+
producerId,
|
|
544
|
+
targetWorkspaceRoot,
|
|
545
|
+
packetPath,
|
|
546
|
+
entrySnapshotId = null,
|
|
547
|
+
apply = false,
|
|
548
|
+
onAfterPacketVerification,
|
|
549
|
+
}) {
|
|
550
|
+
safeIdentifier(deliveryId, "deliveryId");
|
|
551
|
+
safeIdentifier(producerId, "producerId");
|
|
552
|
+
validateTimestamp(occurredAt, "External delivery occurredAt");
|
|
553
|
+
const controllerRoot = canonicalControllerRoot(controllerWorkspaceRoot);
|
|
554
|
+
const targetRoot = canonicalTargetRoot(targetWorkspaceRoot);
|
|
555
|
+
assertDisjointWorkspaces(controllerRoot, targetRoot);
|
|
556
|
+
const definition = readProjectDefinition(controllerRoot);
|
|
557
|
+
const workItem = definition.workItems.find(row => row.id === workItemId);
|
|
558
|
+
if (!workItem) throw new Error(`Unknown team project WorkItem: ${workItemId}.`);
|
|
559
|
+
const projectBinding = teamProjectDefinitionBindingV1(definition);
|
|
560
|
+
validateCurrentAssignment(controllerRoot, workItemId, assignmentId);
|
|
561
|
+
const { packet, bytes } = readPacket(packetPath);
|
|
562
|
+
const identity = packetIdentity(packet);
|
|
563
|
+
const snapshotBeforeVerification = stableWorkspaceSnapshot(targetRoot);
|
|
564
|
+
const packetGate = verifyDeliveryPacket({ workspaceRoot: targetRoot, packet: identity });
|
|
565
|
+
if (packetGate.status !== "valid") {
|
|
566
|
+
return {
|
|
567
|
+
status: "external_delivery_rejected",
|
|
568
|
+
exitCode: packetGate.exitCode,
|
|
569
|
+
deliveryId,
|
|
570
|
+
packetGate,
|
|
571
|
+
persisted: false,
|
|
572
|
+
targetWritePerformed: false,
|
|
573
|
+
authorizationGranted: false,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
if (onAfterPacketVerification !== undefined) {
|
|
577
|
+
if (typeof onAfterPacketVerification !== "function") {
|
|
578
|
+
throw new Error("onAfterPacketVerification must be a function.");
|
|
579
|
+
}
|
|
580
|
+
onAfterPacketVerification();
|
|
581
|
+
}
|
|
582
|
+
const candidateFiles = packetFiles(identity);
|
|
583
|
+
if (candidateFiles.some(row => !SHA256.test(row.sha256))) {
|
|
584
|
+
throw new Error("External DeliveryPacket contains an invalid file hash.");
|
|
585
|
+
}
|
|
586
|
+
const currentSnapshot = stableWorkspaceSnapshot(targetRoot);
|
|
587
|
+
if (canonical(snapshotBeforeVerification) !== canonical(currentSnapshot)) {
|
|
588
|
+
throw new Error(
|
|
589
|
+
"Target workspace changed while its DeliveryPacket was being verified.",
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
const entry = entrySnapshotId === null
|
|
593
|
+
? null
|
|
594
|
+
: readEntrySnapshot(controllerRoot, entrySnapshotId);
|
|
595
|
+
if (entry && (
|
|
596
|
+
entry.artifact.projectId !== projectBinding.projectId
|
|
597
|
+
|| entry.artifact.projectDefinitionSha256 !== projectBinding.projectDefinitionSha256
|
|
598
|
+
|| entry.artifact.workItemId !== workItemId
|
|
599
|
+
|| entry.artifact.targetWorkspaceRef !== targetRoot
|
|
600
|
+
|| entry.artifact.workspaceSnapshot.repositoryIdentity
|
|
601
|
+
!== currentSnapshot.repositoryIdentity
|
|
602
|
+
)) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
"Entry target snapshot does not bind this project identity, WorkItem, and target repository.",
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
const allowedPaths = entry
|
|
608
|
+
? resolveAllowedPaths(workItem, entry.artifact.allowedPaths)
|
|
609
|
+
: workItem.ownedPaths;
|
|
610
|
+
const overlay = classifyOverlay({
|
|
611
|
+
entry: entry?.artifact ?? null,
|
|
612
|
+
currentSnapshot,
|
|
613
|
+
candidateFiles,
|
|
614
|
+
allowedPaths,
|
|
615
|
+
});
|
|
616
|
+
const boundary = proofBoundary(overlay, { entrySnapshotSupplied: entry !== null });
|
|
617
|
+
if (overlay.status === "rejected") {
|
|
618
|
+
return {
|
|
619
|
+
status: "external_delivery_rejected",
|
|
620
|
+
exitCode: 2,
|
|
621
|
+
deliveryId,
|
|
622
|
+
packetGate,
|
|
623
|
+
overlay,
|
|
624
|
+
proven: boundary.proven,
|
|
625
|
+
notProven: boundary.notProven,
|
|
626
|
+
persisted: false,
|
|
627
|
+
targetWritePerformed: false,
|
|
628
|
+
authorizationGranted: false,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const event = {
|
|
633
|
+
schemaVersion: EVENT_SCHEMA,
|
|
634
|
+
eventId: `source-delivery-${deliveryId}`,
|
|
635
|
+
type: "source_delivery_imported",
|
|
636
|
+
occurredAt,
|
|
637
|
+
deliveryId,
|
|
638
|
+
projectId: projectBinding.projectId,
|
|
639
|
+
projectDefinitionSha256: projectBinding.projectDefinitionSha256,
|
|
640
|
+
assignmentId,
|
|
641
|
+
workItemId,
|
|
642
|
+
producerId,
|
|
643
|
+
targetWorkspaceRef: targetRoot,
|
|
644
|
+
targetHead: currentSnapshot.headCommit,
|
|
645
|
+
entrySnapshotRef: entry?.ref ?? null,
|
|
646
|
+
entrySnapshotSha256: entry?.artifact.snapshotSha256 ?? null,
|
|
647
|
+
deliveryPacketSha256: `sha256:${sha256(bytes)}`,
|
|
648
|
+
deliveryCandidateFingerprint: packetGate.declaredFingerprint,
|
|
649
|
+
workspaceSnapshotFingerprint: currentSnapshot.sourceFingerprint,
|
|
650
|
+
overlayStatus: overlay.status,
|
|
651
|
+
candidateFiles,
|
|
652
|
+
changedPaths: overlay.changedPaths,
|
|
653
|
+
toolCachePaths: overlay.toolCachePaths,
|
|
654
|
+
dependencyEnvironmentPaths: overlay.dependencyEnvironmentPaths,
|
|
655
|
+
unchangedPacketPaths: overlay.unchangedPacketPaths,
|
|
656
|
+
outOfScopeSourcePaths: [],
|
|
657
|
+
targetSnapshot: currentSnapshot,
|
|
658
|
+
proven: boundary.proven,
|
|
659
|
+
notProven: boundary.notProven,
|
|
660
|
+
targetWritePerformed: false,
|
|
661
|
+
authorizationGranted: false,
|
|
662
|
+
};
|
|
663
|
+
if (!apply) {
|
|
664
|
+
return {
|
|
665
|
+
status: "external_delivery_preview",
|
|
666
|
+
exitCode: 0,
|
|
667
|
+
deliveryId,
|
|
668
|
+
workItemId,
|
|
669
|
+
assignmentId,
|
|
670
|
+
producerId,
|
|
671
|
+
targetWorkspaceRef: targetRoot,
|
|
672
|
+
deliveryCandidateFingerprint: packetGate.declaredFingerprint,
|
|
673
|
+
workspaceSnapshotFingerprint: currentSnapshot.sourceFingerprint,
|
|
674
|
+
overlay,
|
|
675
|
+
proven: boundary.proven,
|
|
676
|
+
notProven: boundary.notProven,
|
|
677
|
+
persisted: false,
|
|
678
|
+
targetWritePerformed: false,
|
|
679
|
+
authorizationGranted: false,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const recorded = appendTeamProjectEventV1({ workspaceRoot: controllerRoot, event });
|
|
684
|
+
const status = readTeamProjectStatusV2({ workspaceRoot: controllerRoot });
|
|
685
|
+
return {
|
|
686
|
+
status: "external_delivery_imported",
|
|
687
|
+
exitCode: 0,
|
|
688
|
+
deliveryId,
|
|
689
|
+
workItemId,
|
|
690
|
+
assignmentId,
|
|
691
|
+
producerId,
|
|
692
|
+
targetWorkspaceRef: targetRoot,
|
|
693
|
+
deliveryCandidateFingerprint: packetGate.declaredFingerprint,
|
|
694
|
+
workspaceSnapshotFingerprint: currentSnapshot.sourceFingerprint,
|
|
695
|
+
overlay,
|
|
696
|
+
proven: boundary.proven,
|
|
697
|
+
notProven: boundary.notProven,
|
|
698
|
+
eventId: recorded.eventId,
|
|
699
|
+
eventPath: recorded.eventPath,
|
|
700
|
+
eventSha256: recorded.eventSha256,
|
|
701
|
+
projectTruthHash: status.projectTruthHash,
|
|
702
|
+
persisted: true,
|
|
703
|
+
resumed: recorded.resumed,
|
|
704
|
+
targetWritePerformed: false,
|
|
705
|
+
authorizationGranted: false,
|
|
706
|
+
};
|
|
707
|
+
}
|