owlrunkit 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +274 -66
  3. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +223 -117
  4. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  5. package/integrations/codex/skills/owlcoda-runkit/assets/templates/ready-for-commit-request.json +2 -2
  6. package/package.json +2 -2
  7. package/packages/attest/distribution-manifest-v1.json +10 -0
  8. package/packages/attest/src/quick.mjs +10 -0
  9. package/registry-release-policy-v1.json +8 -2
  10. package/sbom.spdx.json +5 -5
  11. package/scripts/runkit-contract/assurance-router.mjs +119 -0
  12. package/scripts/runkit-contract/core-contract.mjs +250 -115
  13. package/scripts/runkit-contract/core-successor.mjs +2043 -0
  14. package/scripts/runkit-contract/deployment-workflow.mjs +2040 -0
  15. package/scripts/runkit-contract/finalize.mjs +242 -21
  16. package/scripts/runkit-contract/fleet-discovery.mjs +456 -0
  17. package/scripts/runkit-contract/formal-workflow.mjs +850 -0
  18. package/scripts/runkit-contract/human-status.mjs +553 -0
  19. package/scripts/runkit-contract/inspect-presentation.mjs +25 -8
  20. package/scripts/runkit-contract/install-codex-skill.mjs +73 -30
  21. package/scripts/runkit-contract/lease-lifecycle.mjs +8 -2
  22. package/scripts/runkit-contract/lifecycle-orchestration.mjs +135 -12
  23. package/scripts/runkit-contract/onboarding-doctor.mjs +43 -90
  24. package/scripts/runkit-contract/owner-authority-trust.mjs +230 -0
  25. package/scripts/runkit-contract/owner-deployment-decision.mjs +385 -0
  26. package/scripts/runkit-contract/profile-onboarding.mjs +841 -13
  27. package/scripts/runkit-contract/project-cli-resolver.mjs +701 -0
  28. package/scripts/runkit-contract/project-control-parser.mjs +830 -0
  29. package/scripts/runkit-contract/project-control-state.mjs +28 -0
  30. package/scripts/runkit-contract/provenance-common.mjs +19 -2
  31. package/scripts/runkit-contract/ready-for-commit.mjs +330 -18
  32. package/scripts/runkit-contract/remote-deployment.mjs +1596 -0
  33. package/scripts/runkit-contract/remote-process-adapter.mjs +177 -0
  34. package/scripts/runkit-contract/runkit-bootstrap.mjs +131 -0
  35. package/scripts/runkit-contract/runkit-cli.mjs +1097 -21
  36. package/scripts/runkit-contract/schemas/assurance-route-v1.schema.json +26 -0
  37. package/scripts/runkit-contract/schemas/core-successor-batch-receipt-v1.schema.json +126 -0
  38. package/scripts/runkit-contract/schemas/core-successor-plan-v1.schema.json +155 -0
  39. package/scripts/runkit-contract/schemas/core-successor-project-success-receipt-v1.schema.json +72 -0
  40. package/scripts/runkit-contract/schemas/deployment-execute-request-v1.schema.json +63 -0
  41. package/scripts/runkit-contract/schemas/deployment-lineage-v1.schema.json +292 -0
  42. package/scripts/runkit-contract/schemas/deployment-prepare-receipt-v1.schema.json +140 -0
  43. package/scripts/runkit-contract/schemas/fleet-discovery-v1.schema.json +31 -0
  44. package/scripts/runkit-contract/schemas/fleet-manifest-v1.schema.json +20 -0
  45. package/scripts/runkit-contract/schemas/fleet-root-registry-v1.schema.json +34 -0
  46. package/scripts/runkit-contract/schemas/human-status-v1.schema.json +102 -0
  47. package/scripts/runkit-contract/schemas/owner-authority-trust-v1.schema.json +66 -0
  48. package/scripts/runkit-contract/schemas/owner-deployment-authority-v1.schema.json +46 -0
  49. package/scripts/runkit-contract/schemas/owner-deployment-authority-v2.schema.json +94 -0
  50. package/scripts/runkit-contract/schemas/owner-deployment-decision-v1.schema.json +102 -0
  51. package/scripts/runkit-contract/schemas/owner-migration-authority-v1.schema.json +33 -0
  52. package/scripts/runkit-contract/schemas/owner-migration-authority-v2.schema.json +67 -0
  53. package/scripts/runkit-contract/schemas/profile-apply-receipt-v1.schema.json +32 -0
  54. package/scripts/runkit-contract/schemas/profile-detection-v2.schema.json +145 -0
  55. package/scripts/runkit-contract/schemas/ready-for-commit-v2.schema.json +37 -0
  56. package/scripts/runkit-contract/schemas/remote-deployment-manifest-v1.schema.json +379 -0
  57. package/scripts/runkit-contract/schemas/remote-deployment-result-v1.schema.json +153 -0
  58. package/scripts/runkit-contract/schemas/remote-deployment-stage-journal-v1.schema.json +183 -0
  59. package/scripts/runkit-contract/schemas/remote-target-v1.schema.json +32 -0
  60. package/scripts/runkit-contract/schemas/source-candidate-v2.schema.json +152 -0
  61. package/scripts/runkit-contract/schemas/verification-receipt-v2.schema.json +13 -0
  62. package/scripts/runkit-contract/snapshot.mjs +1 -1
  63. package/scripts/runkit-contract/source-candidate.mjs +1382 -0
  64. package/scripts/runkit-contract/ssh-remote-adapter.mjs +1184 -0
  65. package/scripts/runkit-contract/sync-core016-release-identity.mjs +161 -0
  66. package/scripts/runkit-contract/templates/assurance-request.json +13 -0
  67. package/scripts/runkit-contract/templates/core-successor-owner-authority.json +15 -0
  68. package/scripts/runkit-contract/templates/deployment-execute-request.json +39 -0
  69. package/scripts/runkit-contract/templates/deployment-owner-authority.json +23 -0
  70. package/scripts/runkit-contract/templates/deployment-owner-decision.json +26 -0
  71. package/scripts/runkit-contract/templates/fleet-manifest.json +8 -0
  72. package/scripts/runkit-contract/templates/fleet-root-registry.json +8 -0
  73. package/scripts/runkit-contract/templates/owner-authority-trust.json +16 -0
  74. package/scripts/runkit-contract/templates/ready-for-commit-request.json +2 -2
  75. package/scripts/runkit-contract/templates/remote-deployment-manifest.json +108 -0
  76. package/scripts/runkit-contract/verification-receipt-gate.mjs +28 -0
@@ -0,0 +1,701 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ existsSync,
4
+ lstatSync,
5
+ readFileSync,
6
+ realpathSync,
7
+ } from "node:fs";
8
+ import path from "node:path";
9
+ import { currentCoreIdentity } from "./core-contract.mjs";
10
+
11
+ const MAX_JSON_BYTES = 1_048_576;
12
+ const MAX_LOCK_BYTES = 33_554_432;
13
+ const MAX_CORE_FILE_BYTES = 16_777_216;
14
+ const MAX_CORE_TOTAL_BYTES = 67_108_864;
15
+ const PACKAGE_NAME = "owlrunkit";
16
+ const OFFICIAL_CLI_ENTRYPOINT =
17
+ "scripts/runkit-contract/runkit-bootstrap.mjs";
18
+ const EXACT_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/;
19
+ const SHA256_REF = /^sha256:[a-f0-9]{64}$/;
20
+ const TRUSTED_PRIOR_CORE_IDENTITIES = new Map([
21
+ ["0.15.1", new Set([
22
+ "sha256:e8ca57522a8e473da356ceb3768bb650267894638d450d5e49463ab6f51c752b",
23
+ ])],
24
+ ["0.15.0", new Set([
25
+ "sha256:06d7616c369b4b3ece3aca32f05c505d4b30781d3ca87a13bd8d7293c1491f64",
26
+ ])],
27
+ ["0.14.0", new Set([
28
+ "sha256:d3b498562bebb2fa180d6861cb834ce3551288bf499ce0d769ee6c64b2663231",
29
+ ])],
30
+ ["0.13.0", new Set([
31
+ "sha256:0e3233a417365afb4e2ce22db5260608a9c697a819cd5c02897276d6454dde1f",
32
+ "sha256:037c012751b32abbbb48ce8a8d2cd8faa4fc2c38d6797db391205477e667065f",
33
+ "sha256:2f840d1884b34656902f8f23af3bd8052dde3212aa516dac2cbb0e0b29627ea1",
34
+ "sha256:febf2551317fce9e4bb412b833a392a20376d702dda09c57d6116e8a3ca4f857",
35
+ "sha256:4b72f572b6964f149c4a6fe5c2c9da85f73b8060c88023dc96ba5ca50a036972",
36
+ "sha256:0455c847bf2df77703583c6f19c20cf103206bafc4f122795bf34d3f85f0263d",
37
+ ])],
38
+ ["0.12.0", new Set([
39
+ "sha256:c415b10cb00d2a7891744b7257774fa501ddf40f8ec2f290356505a17fefb40f",
40
+ "sha256:be4b079fb0bc29e71858af03a1e579f864d4414c815ac612c3b514d8d663d07b",
41
+ ])],
42
+ ]);
43
+ const LOCKFILES = [
44
+ { manager: "npm", path: "package-lock.json" },
45
+ { manager: "pnpm", path: "pnpm-lock.yaml" },
46
+ { manager: "yarn", path: "yarn.lock" },
47
+ { manager: "bun", path: "bun.lock" },
48
+ ];
49
+
50
+ function strictDescendant(root, candidate) {
51
+ const relative = path.relative(root, candidate);
52
+ return relative !== ""
53
+ && relative !== ".."
54
+ && !relative.startsWith(`..${path.sep}`)
55
+ && !path.isAbsolute(relative);
56
+ }
57
+
58
+ function canonicalWorkspaceRoot(workspaceRoot) {
59
+ if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
60
+ throw new Error("workspaceRoot is required.");
61
+ }
62
+ const requested = path.resolve(workspaceRoot);
63
+ const stat = lstatSync(requested);
64
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
65
+ throw new Error("workspace root must be a regular directory");
66
+ }
67
+ return realpathSync(requested);
68
+ }
69
+
70
+ function assertRegularDescendant(root, target, type, symlinkCode) {
71
+ if (!strictDescendant(root, target)) throw new Error("project_cli_path_invalid");
72
+ let current = root;
73
+ for (const segment of path.relative(root, target).split(path.sep)) {
74
+ current = path.join(current, segment);
75
+ const stat = lstatSync(current);
76
+ if (stat.isSymbolicLink()) throw new Error(symlinkCode);
77
+ }
78
+ const stat = lstatSync(target);
79
+ if (
80
+ (type === "file" && !stat.isFile())
81
+ || (type === "directory" && !stat.isDirectory())
82
+ || realpathSync(target) !== path.resolve(target)
83
+ ) {
84
+ throw new Error("project_cli_path_invalid");
85
+ }
86
+ }
87
+
88
+ function readJson(root, relativePath) {
89
+ const target = path.join(root, relativePath);
90
+ assertRegularDescendant(root, target, "file", "project_cli_symlink_rejected");
91
+ const stat = lstatSync(target);
92
+ if (stat.size > MAX_JSON_BYTES) throw new Error("project_cli_json_too_large");
93
+ return JSON.parse(readFileSync(target, "utf8"));
94
+ }
95
+
96
+ function readJsonAt(root, target) {
97
+ assertRegularDescendant(root, target, "file", "project_cli_symlink_rejected");
98
+ const stat = lstatSync(target);
99
+ if (stat.size > MAX_JSON_BYTES) throw new Error("project_cli_json_too_large");
100
+ return JSON.parse(readFileSync(target, "utf8"));
101
+ }
102
+
103
+ function readLock(root, relativePath) {
104
+ const target = path.join(root, relativePath);
105
+ assertRegularDescendant(root, target, "file", "project_cli_symlink_rejected");
106
+ const stat = lstatSync(target);
107
+ if (stat.size > MAX_LOCK_BYTES) throw new Error("project_cli_lockfile_too_large");
108
+ return readFileSync(target, "utf8");
109
+ }
110
+
111
+ function dependencyDeclarations(manifest) {
112
+ return ["dependencies", "devDependencies", "optionalDependencies"]
113
+ .filter(section => Object.hasOwn(manifest[section] ?? {}, PACKAGE_NAME))
114
+ .map(section => manifest[section][PACKAGE_NAME]);
115
+ }
116
+
117
+ function exactDeclaredVersion(manifest) {
118
+ const declarations = dependencyDeclarations(manifest);
119
+ if (declarations.length !== 1) {
120
+ throw new Error("project_cli_dependency_ambiguous");
121
+ }
122
+ const version = declarations[0];
123
+ if (typeof version !== "string" || !EXACT_VERSION.test(version)) {
124
+ throw new Error("project_cli_dependency_not_exact");
125
+ }
126
+ return version;
127
+ }
128
+
129
+ function mismatch(issueCode) {
130
+ return {
131
+ status: "mismatch",
132
+ issueCodes: [issueCode],
133
+ authorizationGranted: false,
134
+ };
135
+ }
136
+
137
+ function packageManagerName(manifest) {
138
+ if (manifest.packageManager === undefined) return null;
139
+ if (typeof manifest.packageManager !== "string") {
140
+ throw new Error("project_cli_package_manager_invalid");
141
+ }
142
+ const match = /^(npm|pnpm|yarn|bun)@[^\s]+$/.exec(manifest.packageManager);
143
+ if (!match) throw new Error("project_cli_package_manager_invalid");
144
+ return match[1];
145
+ }
146
+
147
+ function selectLockfile(root, manifest) {
148
+ const present = LOCKFILES.filter(entry => existsSync(path.join(root, entry.path)));
149
+ if (existsSync(path.join(root, "bun.lockb"))) {
150
+ throw new Error("project_cli_lockfile_unsupported");
151
+ }
152
+ if (present.length !== 1) throw new Error("project_cli_lockfile_ambiguous");
153
+ const declaredManager = packageManagerName(manifest);
154
+ if (declaredManager !== null && declaredManager !== present[0].manager) {
155
+ throw new Error("project_cli_package_manager_mismatch");
156
+ }
157
+ return present[0];
158
+ }
159
+
160
+ function officialTarballUrl(version) {
161
+ return `https://registry.npmjs.org/${PACKAGE_NAME}/-/`
162
+ + `${PACKAGE_NAME}-${version}.tgz`;
163
+ }
164
+
165
+ function canonicalSha512Sri(value) {
166
+ if (
167
+ typeof value !== "string"
168
+ || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(value)
169
+ ) {
170
+ throw new Error("project_cli_integrity_invalid");
171
+ }
172
+ const encoded = value.slice("sha512-".length);
173
+ const digest = Buffer.from(encoded, "base64");
174
+ if (digest.length !== 64 || digest.toString("base64") !== encoded) {
175
+ throw new Error("project_cli_integrity_invalid");
176
+ }
177
+ return value;
178
+ }
179
+
180
+ function validateRegistryBinding(binding, version) {
181
+ if (binding.resolved !== officialTarballUrl(version)) {
182
+ throw new Error("project_cli_registry_provenance_invalid");
183
+ }
184
+ return {
185
+ resolved: binding.resolved,
186
+ integrity: canonicalSha512Sri(binding.integrity),
187
+ };
188
+ }
189
+
190
+ function npmLockBinding(root, version) {
191
+ const lock = JSON.parse(readLock(root, "package-lock.json"));
192
+ const rootDeclarations = dependencyDeclarations(lock.packages?.[""] ?? {});
193
+ const entry = lock.packages?.[`node_modules/${PACKAGE_NAME}`];
194
+ if (
195
+ (
196
+ rootDeclarations.length > 0
197
+ && (
198
+ rootDeclarations.length !== 1
199
+ || rootDeclarations[0] !== version
200
+ )
201
+ )
202
+ || entry?.version !== version
203
+ || typeof entry.resolved !== "string"
204
+ || typeof entry.integrity !== "string"
205
+ ) {
206
+ throw new Error("project_cli_lock_binding_mismatch");
207
+ }
208
+ return {
209
+ resolved: entry.resolved,
210
+ integrity: entry.integrity,
211
+ };
212
+ }
213
+
214
+ function yamlScalar(value) {
215
+ const trimmed = String(value).trim();
216
+ if (
217
+ (trimmed.startsWith("\"") && trimmed.endsWith("\""))
218
+ || (trimmed.startsWith("'") && trimmed.endsWith("'"))
219
+ ) return trimmed.slice(1, -1);
220
+ return trimmed;
221
+ }
222
+
223
+ function indentedBlock(lines, start) {
224
+ const indent = lines[start].match(/^\s*/)[0].length;
225
+ const block = [];
226
+ for (let index = start + 1; index < lines.length; index += 1) {
227
+ const line = lines[index];
228
+ if (line.trim() === "" || line.trimStart().startsWith("#")) {
229
+ block.push(line);
230
+ continue;
231
+ }
232
+ if (line.match(/^\s*/)[0].length <= indent) break;
233
+ block.push(line);
234
+ }
235
+ return block;
236
+ }
237
+
238
+ function pnpmLockBinding(root, version) {
239
+ const lines = readLock(root, "pnpm-lock.yaml").split(/\r?\n/);
240
+ const dependencyBound = lines.some((line, index) => {
241
+ if (!/^\s+owlrunkit:\s*$/.test(line)) return false;
242
+ const block = indentedBlock(lines, index).join("\n");
243
+ const specifier = /^\s*specifier:\s*(.+?)\s*$/m.exec(block)?.[1];
244
+ const resolvedVersion = /^\s*version:\s*(.+?)\s*$/m.exec(block)?.[1];
245
+ return yamlScalar(specifier) === version
246
+ && yamlScalar(resolvedVersion).split("(")[0] === version;
247
+ });
248
+ const escaped = version.replaceAll(".", "\\.");
249
+ const packageHeader = new RegExp(
250
+ `^\\s{2}(?:/)?owlrunkit@${escaped}(?:\\([^)]*\\))?:\\s*$`,
251
+ );
252
+ let resolution = null;
253
+ for (let index = 0; index < lines.length; index += 1) {
254
+ if (!packageHeader.test(lines[index])) continue;
255
+ const block = indentedBlock(lines, index).join("\n");
256
+ const integrity = /(?:^|[{,\s])integrity:\s*([^,}\s]+)/m.exec(block)?.[1];
257
+ if (!integrity) continue;
258
+ const tarball = /(?:^|[{,\s])tarball:\s*([^,}\s]+)/m.exec(block)?.[1];
259
+ resolution = {
260
+ resolved: tarball ? yamlScalar(tarball) : officialTarballUrl(version),
261
+ integrity: yamlScalar(integrity),
262
+ };
263
+ break;
264
+ }
265
+ if (!dependencyBound || !resolution) {
266
+ throw new Error("project_cli_lock_binding_mismatch");
267
+ }
268
+ return resolution;
269
+ }
270
+
271
+ function yarnLockBinding(root, version) {
272
+ const lines = readLock(root, "yarn.lock").split(/\r?\n/);
273
+ const escaped = version.replaceAll(".", "\\.");
274
+ const selector = new RegExp(
275
+ `(?:^|,\\s*)"?owlrunkit@(?:npm:)?${escaped}"?(?:,|:)`,
276
+ );
277
+ for (let index = 0; index < lines.length; index += 1) {
278
+ if (/^\s/.test(lines[index]) || !selector.test(lines[index])) continue;
279
+ const block = indentedBlock(lines, index).join("\n");
280
+ const lockedVersion = /(?:^|\n)\s*version(?::|\s)\s*"?([^"\s]+)"?/m.exec(block)?.[1];
281
+ const resolved =
282
+ /(?:^|\n)\s*resolved\s+"?([^"\s]+)"?/m.exec(block)?.[1]
283
+ ?? /(?:^|\n)\s*resolution:\s*"?([^"\s]+)"?/m.exec(block)?.[1];
284
+ const integrity =
285
+ /(?:^|\n)\s*integrity\s+([^\s]+)/m.exec(block)?.[1]
286
+ ?? /(?:^|\n)\s*checksum:\s*"?([^"\s]+)"?/m.exec(block)?.[1];
287
+ if (lockedVersion === version && resolved && integrity) {
288
+ return { resolved, integrity };
289
+ }
290
+ }
291
+ throw new Error("project_cli_lock_binding_mismatch");
292
+ }
293
+
294
+ function stripJsonComments(value) {
295
+ let output = "";
296
+ let inString = false;
297
+ let escaped = false;
298
+ for (let index = 0; index < value.length; index += 1) {
299
+ const character = value[index];
300
+ const next = value[index + 1];
301
+ if (inString) {
302
+ output += character;
303
+ if (escaped) escaped = false;
304
+ else if (character === "\\") escaped = true;
305
+ else if (character === "\"") inString = false;
306
+ continue;
307
+ }
308
+ if (character === "\"") {
309
+ inString = true;
310
+ output += character;
311
+ continue;
312
+ }
313
+ if (character === "/" && next === "/") {
314
+ while (index < value.length && value[index] !== "\n") index += 1;
315
+ output += "\n";
316
+ continue;
317
+ }
318
+ if (character === "/" && next === "*") {
319
+ index += 2;
320
+ while (
321
+ index < value.length
322
+ && !(value[index] === "*" && value[index + 1] === "/")
323
+ ) index += 1;
324
+ index += 1;
325
+ continue;
326
+ }
327
+ output += character;
328
+ }
329
+ return output;
330
+ }
331
+
332
+ function removeTrailingCommas(value) {
333
+ let output = "";
334
+ let inString = false;
335
+ let escaped = false;
336
+ for (let index = 0; index < value.length; index += 1) {
337
+ const character = value[index];
338
+ if (inString) {
339
+ output += character;
340
+ if (escaped) escaped = false;
341
+ else if (character === "\\") escaped = true;
342
+ else if (character === "\"") inString = false;
343
+ continue;
344
+ }
345
+ if (character === "\"") {
346
+ inString = true;
347
+ output += character;
348
+ continue;
349
+ }
350
+ if (character === ",") {
351
+ let cursor = index + 1;
352
+ while (/\s/.test(value[cursor] ?? "")) cursor += 1;
353
+ if (value[cursor] === "}" || value[cursor] === "]") continue;
354
+ }
355
+ output += character;
356
+ }
357
+ return output;
358
+ }
359
+
360
+ function bunLockBinding(root, version) {
361
+ const lock = JSON.parse(removeTrailingCommas(stripJsonComments(
362
+ readLock(root, "bun.lock"),
363
+ )));
364
+ const workspaceVersion = exactDeclaredVersion(lock.workspaces?.[""] ?? {});
365
+ const entry =
366
+ lock.packages?.[PACKAGE_NAME]
367
+ ?? lock.packages?.[`${PACKAGE_NAME}@${version}`];
368
+ let resolved;
369
+ let integrity;
370
+ if (Array.isArray(entry)) {
371
+ const identity = entry.find(value =>
372
+ typeof value === "string"
373
+ && new Set([
374
+ `${PACKAGE_NAME}@${version}`,
375
+ `${PACKAGE_NAME}@npm:${version}`,
376
+ ]).has(value));
377
+ resolved = entry.find(value =>
378
+ typeof value === "string" && /^https?:\/\//.test(value))
379
+ ?? (identity ? officialTarballUrl(version) : null);
380
+ integrity = [...entry].reverse().find(value =>
381
+ typeof value === "string" && /^(?:sha512-|[a-f0-9]{64,})/i.test(value));
382
+ } else if (entry && typeof entry === "object") {
383
+ if (entry.version !== version) {
384
+ throw new Error("project_cli_lock_binding_mismatch");
385
+ }
386
+ resolved = entry.resolved ?? entry.resolution;
387
+ integrity = entry.integrity ?? entry.checksum;
388
+ }
389
+ if (workspaceVersion !== version || !resolved || !integrity) {
390
+ throw new Error("project_cli_lock_binding_mismatch");
391
+ }
392
+ return { resolved, integrity };
393
+ }
394
+
395
+ function lockBinding(root, lockfile, version) {
396
+ const binding = lockfile.manager === "npm"
397
+ ? npmLockBinding(root, version)
398
+ : lockfile.manager === "pnpm"
399
+ ? pnpmLockBinding(root, version)
400
+ : lockfile.manager === "yarn"
401
+ ? yarnLockBinding(root, version)
402
+ : bunLockBinding(root, version);
403
+ return validateRegistryBinding(binding, version);
404
+ }
405
+
406
+ function storeRoot(root, manager) {
407
+ if (manager === "pnpm") return path.join(root, "node_modules", ".pnpm");
408
+ if (manager === "yarn") return path.join(root, ".yarn", "unplugged");
409
+ if (manager === "bun") return path.join(root, "node_modules", ".bun");
410
+ return null;
411
+ }
412
+
413
+ function resolvePackageRoot(root, manager) {
414
+ const logicalPackageRoot = path.join(root, "node_modules", PACKAGE_NAME);
415
+ assertRegularDescendant(
416
+ root,
417
+ path.dirname(logicalPackageRoot),
418
+ "directory",
419
+ "project_cli_symlink_rejected",
420
+ );
421
+ const stat = lstatSync(logicalPackageRoot);
422
+ if (!stat.isSymbolicLink()) {
423
+ assertRegularDescendant(
424
+ root,
425
+ logicalPackageRoot,
426
+ "directory",
427
+ "project_cli_symlink_rejected",
428
+ );
429
+ return {
430
+ logicalPackageRoot,
431
+ packageRoot: logicalPackageRoot,
432
+ symlinkedStore: false,
433
+ };
434
+ }
435
+ const packageRoot = realpathSync(logicalPackageRoot);
436
+ const allowedStore = storeRoot(root, manager);
437
+ if (
438
+ allowedStore === null
439
+ || !strictDescendant(root, packageRoot)
440
+ || !strictDescendant(allowedStore, packageRoot)
441
+ ) {
442
+ throw new Error("project_cli_symlink_rejected");
443
+ }
444
+ assertRegularDescendant(
445
+ root,
446
+ packageRoot,
447
+ "directory",
448
+ "project_cli_symlink_rejected",
449
+ );
450
+ return {
451
+ logicalPackageRoot,
452
+ packageRoot,
453
+ symlinkedStore: true,
454
+ };
455
+ }
456
+
457
+ function sha256(value) {
458
+ return createHash("sha256").update(value).digest("hex");
459
+ }
460
+
461
+ function coreFileList(coreContractSource, identifier) {
462
+ const marker = `const ${identifier} = [`;
463
+ const start = coreContractSource.indexOf(marker);
464
+ if (start < 0) throw new Error("project_cli_core_contract_invalid");
465
+ const bodyStart = start + marker.length;
466
+ const end = coreContractSource.indexOf("];", bodyStart);
467
+ if (end < 0) throw new Error("project_cli_core_contract_invalid");
468
+ const body = coreContractSource.slice(bodyStart, end);
469
+ const values = body
470
+ .split(/\r?\n/u)
471
+ .map((line) => line.trim())
472
+ .filter(Boolean)
473
+ .map((line) => {
474
+ const match = /^("(?:[^"\\]|\\.)*"),?$/u.exec(line);
475
+ if (!match) throw new Error("project_cli_core_contract_invalid");
476
+ return JSON.parse(match[1]);
477
+ });
478
+ if (
479
+ values.length > 512
480
+ || new Set(values).size !== values.length
481
+ || values.some((value) => (
482
+ typeof value !== "string"
483
+ || value.length === 0
484
+ || path.isAbsolute(value)
485
+ || value.includes("\\")
486
+ || value.split("/").some((segment) => (
487
+ segment.length === 0 || segment === "." || segment === ".."
488
+ ))
489
+ ))
490
+ ) {
491
+ throw new Error("project_cli_core_contract_invalid");
492
+ }
493
+ return values;
494
+ }
495
+
496
+ function installedCoreManifestSha256(packageRoot) {
497
+ const coreRoot = path.join(packageRoot, "scripts", "runkit-contract");
498
+ const coreContractPath = path.join(coreRoot, "core-contract.mjs");
499
+ assertRegularDescendant(
500
+ packageRoot,
501
+ coreContractPath,
502
+ "file",
503
+ "project_cli_symlink_rejected",
504
+ );
505
+ if (lstatSync(coreContractPath).size > MAX_JSON_BYTES) {
506
+ throw new Error("project_cli_core_contract_too_large");
507
+ }
508
+ const source = readFileSync(coreContractPath, "utf8");
509
+ const coreFiles = coreFileList(source, "CORE_FILES");
510
+ const dependencyFiles = coreFileList(source, "CORE_DEPENDENCY_FILES");
511
+ const entries = [
512
+ ...coreFiles.map((relativePath) => ({
513
+ relativePath,
514
+ target: path.join(coreRoot, relativePath),
515
+ })),
516
+ ...dependencyFiles.map((relativePath) => ({
517
+ relativePath,
518
+ target: path.join(packageRoot, relativePath),
519
+ })),
520
+ ];
521
+ if (
522
+ entries.length === 0
523
+ || new Set(entries.map((entry) => entry.target)).size !== entries.length
524
+ ) {
525
+ throw new Error("project_cli_core_contract_invalid");
526
+ }
527
+ let totalBytes = 0;
528
+ const stream = entries.map(({ relativePath, target }) => {
529
+ assertRegularDescendant(
530
+ packageRoot,
531
+ target,
532
+ "file",
533
+ "project_cli_symlink_rejected",
534
+ );
535
+ const size = lstatSync(target).size;
536
+ totalBytes += size;
537
+ if (
538
+ size > MAX_CORE_FILE_BYTES
539
+ || totalBytes > MAX_CORE_TOTAL_BYTES
540
+ ) {
541
+ throw new Error("project_cli_core_closure_too_large");
542
+ }
543
+ return `${relativePath}\tsha256:${sha256(readFileSync(target))}\n`;
544
+ }).join("");
545
+ return `sha256:${sha256(stream)}`;
546
+ }
547
+
548
+ function trustedCoreManifestHashes(version) {
549
+ const running = currentCoreIdentity();
550
+ if (running.coreVersion === version) {
551
+ return {
552
+ hashes: new Set([running.coreManifestSha256]),
553
+ source: "running_bootstrap_core",
554
+ };
555
+ }
556
+ const prior = TRUSTED_PRIOR_CORE_IDENTITIES.get(version);
557
+ if (!prior) throw new Error("project_cli_core_version_untrusted");
558
+ return {
559
+ hashes: prior,
560
+ source: "bootstrap_embedded_prior_core_catalog",
561
+ };
562
+ }
563
+
564
+ function verifyInstalledCoreBinding(root, packageRoot, version) {
565
+ let config;
566
+ try {
567
+ config = readJson(root, ".owlcoda/runkit/config.json");
568
+ } catch (error) {
569
+ if (error?.code === "ENOENT") {
570
+ throw new Error("project_cli_core_config_missing");
571
+ }
572
+ throw error;
573
+ }
574
+ const core = config?.core;
575
+ if (
576
+ !new Set([
577
+ "OwlCodaRunKitConfigV1",
578
+ "OwlCodaRunKitConfigV2",
579
+ ]).has(config?.schemaVersion)
580
+ || typeof core?.contractVersion !== "string"
581
+ || core.coreVersion !== version
582
+ || !SHA256_REF.test(core.coreManifestSha256)
583
+ || core.coreSourceRef !== `artifact:${core.coreManifestSha256}`
584
+ ) {
585
+ throw new Error("project_cli_core_config_invalid");
586
+ }
587
+ const actualCoreManifestSha256 = installedCoreManifestSha256(packageRoot);
588
+ const trusted = trustedCoreManifestHashes(version);
589
+ if (!trusted.hashes.has(actualCoreManifestSha256)) {
590
+ throw new Error("project_cli_untrusted_core_identity");
591
+ }
592
+ if (actualCoreManifestSha256 !== core.coreManifestSha256) {
593
+ throw new Error("project_cli_installed_core_drift");
594
+ }
595
+ return {
596
+ schemaVersion: "OwlCodaRunKitInstalledCoreBindingV1",
597
+ coreVersion: version,
598
+ coreManifestSha256: actualCoreManifestSha256,
599
+ trustedIdentitySource: trusted.source,
600
+ configPath: ".owlcoda/runkit/config.json",
601
+ };
602
+ }
603
+
604
+ export function resolveProjectCli({
605
+ workspaceRoot,
606
+ expectedVersion = null,
607
+ requireEntrypoint = true,
608
+ } = {}) {
609
+ let root;
610
+ try {
611
+ root = canonicalWorkspaceRoot(workspaceRoot);
612
+ } catch {
613
+ return mismatch("project_cli_workspace_invalid");
614
+ }
615
+ try {
616
+ const manifest = readJson(root, "package.json");
617
+ const version = exactDeclaredVersion(manifest);
618
+ if (expectedVersion !== null && version !== expectedVersion) {
619
+ return mismatch("project_cli_version_mismatch");
620
+ }
621
+ const selectedLockfile = selectLockfile(root, manifest);
622
+ const binding = lockBinding(root, selectedLockfile, version);
623
+ const installed = resolvePackageRoot(root, selectedLockfile.manager);
624
+ const installedManifest = readJsonAt(
625
+ root,
626
+ path.join(installed.packageRoot, "package.json"),
627
+ );
628
+ if (
629
+ installedManifest.name !== PACKAGE_NAME
630
+ || installedManifest.version !== version
631
+ ) {
632
+ return mismatch("project_cli_install_binding_mismatch");
633
+ }
634
+ const bin = typeof installedManifest.bin === "string"
635
+ ? installedManifest.bin
636
+ : installedManifest.bin?.[PACKAGE_NAME];
637
+ if (typeof bin !== "string" || bin.length === 0) {
638
+ if (requireEntrypoint) return mismatch("project_cli_bin_missing");
639
+ return {
640
+ schemaVersion: "OwlCodaRunKitProjectCliBindingV1",
641
+ status: "bound",
642
+ workspaceRoot: root,
643
+ packageName: PACKAGE_NAME,
644
+ packageManager: selectedLockfile.manager,
645
+ lockfilePath: selectedLockfile.path,
646
+ logicalPackageRoot: installed.logicalPackageRoot,
647
+ packageRoot: installed.packageRoot,
648
+ symlinkedStore: installed.symlinkedStore,
649
+ version,
650
+ requestedSpec: `${PACKAGE_NAME}@${version}`,
651
+ resolved: binding.resolved,
652
+ integrity: binding.integrity,
653
+ cliStatus: "unavailable",
654
+ authorizationGranted: false,
655
+ };
656
+ }
657
+ if (bin !== OFFICIAL_CLI_ENTRYPOINT) {
658
+ return mismatch("project_cli_bin_invalid");
659
+ }
660
+ const cliPath = path.resolve(installed.packageRoot, bin);
661
+ if (!strictDescendant(installed.packageRoot, cliPath)) {
662
+ return mismatch("project_cli_path_invalid");
663
+ }
664
+ assertRegularDescendant(
665
+ installed.packageRoot,
666
+ cliPath,
667
+ "file",
668
+ "project_cli_symlink_rejected",
669
+ );
670
+ const installedCoreBinding = requireEntrypoint
671
+ ? verifyInstalledCoreBinding(root, installed.packageRoot, version)
672
+ : null;
673
+ return {
674
+ schemaVersion: "OwlCodaRunKitProjectCliBindingV1",
675
+ status: "bound",
676
+ workspaceRoot: root,
677
+ packageName: PACKAGE_NAME,
678
+ packageManager: selectedLockfile.manager,
679
+ lockfilePath: selectedLockfile.path,
680
+ logicalPackageRoot: installed.logicalPackageRoot,
681
+ packageRoot: installed.packageRoot,
682
+ symlinkedStore: installed.symlinkedStore,
683
+ version,
684
+ requestedSpec: `${PACKAGE_NAME}@${version}`,
685
+ resolved: binding.resolved,
686
+ integrity: binding.integrity,
687
+ cliPath,
688
+ cliStatus: "bound",
689
+ installedCoreBinding,
690
+ argvPrefix: [process.execPath, cliPath],
691
+ authorizationGranted: false,
692
+ };
693
+ } catch (error) {
694
+ const message = error instanceof Error ? error.message : "";
695
+ return mismatch(
696
+ message.startsWith("project_cli_")
697
+ ? message
698
+ : "project_cli_install_binding_mismatch",
699
+ );
700
+ }
701
+ }