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
@@ -1,8 +1,14 @@
1
+ import { createHash } from "node:crypto";
1
2
  import {
2
3
  existsSync,
3
4
  lstatSync,
5
+ linkSync,
6
+ mkdirSync,
7
+ readFileSync,
4
8
  readdirSync,
5
9
  realpathSync,
10
+ rmSync,
11
+ writeFileSync,
6
12
  } from "node:fs";
7
13
  import path from "node:path";
8
14
 
@@ -36,6 +42,355 @@ function resolveRegularExecutable(name) {
36
42
  return null;
37
43
  }
38
44
 
45
+ function canonicalJson(value) {
46
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
47
+ if (value && typeof value === "object") {
48
+ return `{${Object.entries(value)
49
+ .sort(([left], [right]) => left.localeCompare(right))
50
+ .map(([key, nested]) => `${JSON.stringify(key)}:${canonicalJson(nested)}`)
51
+ .join(",")}}`;
52
+ }
53
+ const encoded = JSON.stringify(value);
54
+ if (encoded === undefined) throw new Error("profile_detection_noncanonical_value");
55
+ return encoded;
56
+ }
57
+
58
+ function sha256(value) {
59
+ return createHash("sha256").update(value).digest("hex");
60
+ }
61
+
62
+ function boundedRegularFile(root, relativePath) {
63
+ safeRelativePath(relativePath, "profile detection input");
64
+ const absolute = path.resolve(root, relativePath);
65
+ const relative = path.relative(root, absolute);
66
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
67
+ throw new Error(`profile_detection_input_outside_workspace:${relativePath}`);
68
+ }
69
+ const stat = lstatSync(absolute);
70
+ const resolved = realpathSync(absolute);
71
+ if (
72
+ stat.isSymbolicLink()
73
+ || !stat.isFile()
74
+ || resolved !== absolute
75
+ ) {
76
+ throw new Error(`profile_detection_input_not_regular:${relativePath}`);
77
+ }
78
+ return absolute;
79
+ }
80
+
81
+ function insideRoot(root, candidate) {
82
+ const relative = path.relative(root, candidate);
83
+ return relative !== ".."
84
+ && !relative.startsWith(`..${path.sep}`)
85
+ && !path.isAbsolute(relative);
86
+ }
87
+
88
+ function ensureDirectoryChain(root, relativeDirectory) {
89
+ let current = root;
90
+ for (const segment of relativeDirectory.split("/")) {
91
+ current = path.join(current, segment);
92
+ try {
93
+ lstatSync(current);
94
+ } catch (error) {
95
+ if (error?.code !== "ENOENT") throw error;
96
+ mkdirSync(current);
97
+ }
98
+ const stat = lstatSync(current);
99
+ const resolved = realpathSync(current);
100
+ if (
101
+ stat.isSymbolicLink()
102
+ || !stat.isDirectory()
103
+ || resolved !== current
104
+ || !insideRoot(root, resolved)
105
+ ) {
106
+ throw new Error("profile_apply_directory_untrusted");
107
+ }
108
+ }
109
+ return current;
110
+ }
111
+
112
+ function regularExecutable(value) {
113
+ if (typeof value !== "string" || !path.isAbsolute(value)) return null;
114
+ try {
115
+ const resolved = realpathSync(value);
116
+ const stat = lstatSync(resolved);
117
+ return stat.isFile() && !stat.isSymbolicLink() ? resolved : null;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ function toolIdentity(executable, argvPrefix, version) {
124
+ const launcher = regularExecutable(argvPrefix[0] ?? executable);
125
+ if (!launcher || typeof version !== "string" || !/^[0-9]+\.[0-9]+\.[0-9]+/.test(version)) {
126
+ return null;
127
+ }
128
+ return {
129
+ version,
130
+ executableSha256: sha256(readFileSync(executable)),
131
+ launcherSha256: sha256(readFileSync(launcher)),
132
+ };
133
+ }
134
+
135
+ function defaultProjectNpm(root, manifest) {
136
+ try {
137
+ if (
138
+ typeof manifest.packageManager === "string"
139
+ && !manifest.packageManager.startsWith("npm@")
140
+ ) return null;
141
+ const lockPath = boundedRegularFile(root, "package-lock.json");
142
+ const lock = JSON.parse(readFileSync(lockPath, "utf8"));
143
+ if (!Number.isInteger(lock.lockfileVersion)) return null;
144
+ const executable = regularExecutable(process.execPath);
145
+ if (!executable) return null;
146
+ const npmEntry = path.join(
147
+ path.dirname(executable),
148
+ process.platform === "win32" ? "npm.cmd" : "npm",
149
+ );
150
+ const npmCli = regularExecutable(npmEntry);
151
+ if (!npmCli || process.platform === "win32") return null;
152
+ const npmManifestPath = path.resolve(npmCli, "..", "..", "package.json");
153
+ const npmManifest = JSON.parse(readFileSync(npmManifestPath, "utf8"));
154
+ if (
155
+ npmManifest.name !== "npm"
156
+ || typeof npmManifest.version !== "string"
157
+ || (
158
+ typeof manifest.packageManager === "string"
159
+ && manifest.packageManager.startsWith("npm@")
160
+ && manifest.packageManager.slice(4) !== npmManifest.version
161
+ )
162
+ ) {
163
+ return null;
164
+ }
165
+ const identity = toolIdentity(executable, [npmCli], npmManifest.version);
166
+ if (!identity) return null;
167
+ return {
168
+ status: "resolved",
169
+ confidence: "high",
170
+ source: "project_lockfile",
171
+ packageManager: "npm",
172
+ executable,
173
+ argvPrefix: [npmCli],
174
+ inputFiles: ["package.json", "package-lock.json"],
175
+ issueCodes: [],
176
+ ...identity,
177
+ };
178
+ } catch {
179
+ return null;
180
+ }
181
+ }
182
+
183
+ function selectedPackageManager(root, manifest) {
184
+ const declared = typeof manifest.packageManager === "string"
185
+ ? /^(npm|pnpm|yarn|bun)@([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?)$/
186
+ .exec(manifest.packageManager)
187
+ : null;
188
+ const lockfiles = [
189
+ { manager: "npm", refs: ["package-lock.json"] },
190
+ { manager: "pnpm", refs: ["pnpm-lock.yaml"] },
191
+ { manager: "yarn", refs: ["yarn.lock"] },
192
+ { manager: "bun", refs: ["bun.lock", "bun.lockb"] },
193
+ ].flatMap(entry => entry.refs
194
+ .filter(ref => existsSync(path.join(root, ref)))
195
+ .map(ref => ({ manager: entry.manager, ref })));
196
+ if (lockfiles.length !== 1) return {
197
+ manager: declared?.[1] ?? null,
198
+ version: declared?.[2] ?? null,
199
+ lockfile: null,
200
+ };
201
+ if (declared && declared[1] !== lockfiles[0].manager) {
202
+ return {
203
+ manager: declared[1],
204
+ version: declared[2],
205
+ lockfile: null,
206
+ };
207
+ }
208
+ return {
209
+ manager: declared?.[1] ?? lockfiles[0].manager,
210
+ version: declared?.[2] ?? null,
211
+ lockfile: lockfiles[0].ref,
212
+ };
213
+ }
214
+
215
+ function defaultProjectInstalledManager(root, manifest) {
216
+ const selected = selectedPackageManager(root, manifest);
217
+ if (
218
+ selected.manager === null
219
+ || selected.manager === "npm"
220
+ || selected.version === null
221
+ || selected.lockfile === null
222
+ ) return null;
223
+ try {
224
+ boundedRegularFile(root, selected.lockfile);
225
+ const requestedPackageRoot = path.join(
226
+ root,
227
+ "node_modules",
228
+ selected.manager,
229
+ );
230
+ const packageRoot = realpathSync(requestedPackageRoot);
231
+ if (!insideRoot(root, packageRoot)) return null;
232
+ const managerManifestPath = path.join(packageRoot, "package.json");
233
+ const managerManifestStat = lstatSync(managerManifestPath);
234
+ if (
235
+ managerManifestStat.isSymbolicLink()
236
+ || !managerManifestStat.isFile()
237
+ || realpathSync(managerManifestPath) !== managerManifestPath
238
+ ) return null;
239
+ const managerManifest = JSON.parse(readFileSync(managerManifestPath, "utf8"));
240
+ if (
241
+ managerManifest.name !== selected.manager
242
+ || managerManifest.version !== selected.version
243
+ ) return null;
244
+ const declaredBin = typeof managerManifest.bin === "string"
245
+ ? managerManifest.bin
246
+ : managerManifest.bin?.[selected.manager];
247
+ if (typeof declaredBin !== "string" || declaredBin.length === 0) return null;
248
+ safeRelativePath(declaredBin, "project package manager bin");
249
+ const launcher = realpathSync(path.join(packageRoot, declaredBin));
250
+ const launcherStat = lstatSync(launcher);
251
+ if (
252
+ !insideRoot(root, launcher)
253
+ || launcherStat.isSymbolicLink()
254
+ || !launcherStat.isFile()
255
+ ) return null;
256
+ const executable = regularExecutable(process.execPath);
257
+ if (!executable) return null;
258
+ const identity = toolIdentity(executable, [launcher], selected.version);
259
+ if (!identity) return null;
260
+ const managerManifestRef = path.relative(root, managerManifestPath)
261
+ .split(path.sep)
262
+ .join("/");
263
+ const launcherRef = path.relative(root, launcher)
264
+ .split(path.sep)
265
+ .join("/");
266
+ return {
267
+ status: "resolved",
268
+ confidence: "high",
269
+ source: "project_install",
270
+ packageManager: selected.manager,
271
+ executable,
272
+ argvPrefix: [launcher],
273
+ inputFiles: [
274
+ "package.json",
275
+ selected.lockfile,
276
+ managerManifestRef,
277
+ launcherRef,
278
+ ].sort(),
279
+ issueCodes: [],
280
+ ...identity,
281
+ };
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ function resolveProjectTool(root, manifest, projectToolResolver) {
288
+ const selected = selectedPackageManager(root, manifest);
289
+ const packageManager = selected.manager ?? "npm";
290
+ if (typeof projectToolResolver === "function") {
291
+ try {
292
+ const resolved = projectToolResolver({
293
+ workspaceRoot: root,
294
+ tool: packageManager,
295
+ manifest: structuredClone(manifest),
296
+ });
297
+ const executable = regularExecutable(resolved?.executable);
298
+ const argvPrefix = Array.isArray(resolved?.argvPrefix)
299
+ && resolved.argvPrefix.every(value => typeof value === "string" && value.length > 0)
300
+ ? [...resolved.argvPrefix]
301
+ : null;
302
+ const inputFiles = Array.isArray(resolved?.inputFiles)
303
+ && resolved.inputFiles.every(value => typeof value === "string" && value.length > 0)
304
+ ? [...new Set(resolved.inputFiles)].sort()
305
+ : null;
306
+ const authoritativeSource = new Set([
307
+ "project_install",
308
+ "project_lockfile",
309
+ "project_toolchain",
310
+ ]).has(resolved?.source);
311
+ const identity = executable && argvPrefix
312
+ ? toolIdentity(executable, argvPrefix, resolved?.version)
313
+ : null;
314
+ const lockBound = inputFiles?.includes("package.json")
315
+ && inputFiles.some(ref => [
316
+ "bun.lock",
317
+ "bun.lockb",
318
+ "package-lock.json",
319
+ "pnpm-lock.yaml",
320
+ "yarn.lock",
321
+ ].includes(ref));
322
+ if (
323
+ resolved?.status === "resolved"
324
+ && executable
325
+ && argvPrefix
326
+ && inputFiles
327
+ && identity
328
+ ) {
329
+ const high = resolved.confidence === "high"
330
+ && authoritativeSource
331
+ && lockBound;
332
+ return {
333
+ status: "resolved",
334
+ confidence: high ? "high" : "review_required",
335
+ source: String(resolved.source ?? "injected_resolver"),
336
+ packageManager,
337
+ executable,
338
+ argvPrefix,
339
+ inputFiles,
340
+ issueCodes: high ? [] : ["project_tool_resolution_not_authoritative"],
341
+ ...identity,
342
+ };
343
+ }
344
+ return {
345
+ status: "invalid",
346
+ confidence: "review_required",
347
+ source: "injected_resolver",
348
+ packageManager,
349
+ executable: null,
350
+ argvPrefix: [],
351
+ inputFiles: [],
352
+ version: null,
353
+ executableSha256: null,
354
+ launcherSha256: null,
355
+ issueCodes: ["project_tool_resolution_invalid"],
356
+ };
357
+ } catch {
358
+ return {
359
+ status: "invalid",
360
+ confidence: "review_required",
361
+ source: "injected_resolver",
362
+ packageManager,
363
+ executable: null,
364
+ argvPrefix: [],
365
+ inputFiles: [],
366
+ version: null,
367
+ executableSha256: null,
368
+ launcherSha256: null,
369
+ issueCodes: ["project_tool_resolution_failed"],
370
+ };
371
+ }
372
+ }
373
+
374
+ const installedManager = defaultProjectInstalledManager(root, manifest);
375
+ if (installedManager) return installedManager;
376
+ const projectNpm = defaultProjectNpm(root, manifest);
377
+ if (projectNpm) return projectNpm;
378
+ const executable = resolveRegularExecutable(packageManager);
379
+ return {
380
+ status: executable ? "resolved" : "unavailable",
381
+ confidence: "review_required",
382
+ source: executable ? "path_fallback" : "unavailable",
383
+ packageManager,
384
+ executable,
385
+ argvPrefix: [],
386
+ inputFiles: [],
387
+ version: null,
388
+ executableSha256: executable ? sha256(readFileSync(executable)) : null,
389
+ launcherSha256: null,
390
+ issueCodes: ["project_tool_resolution_not_authoritative"],
391
+ };
392
+ }
393
+
39
394
  function command(id, script, executable) {
40
395
  return {
41
396
  id,
@@ -45,12 +400,20 @@ function command(id, script, executable) {
45
400
  };
46
401
  }
47
402
 
48
- function workspaceCommand(id, workspacePath, script, executable) {
403
+ function workspaceCommand(
404
+ id,
405
+ workspacePath,
406
+ script,
407
+ executable,
408
+ useWorkspaceCwd = false,
409
+ ) {
49
410
  return {
50
411
  id,
51
- cwd: ".",
412
+ cwd: useWorkspaceCwd ? workspacePath : ".",
52
413
  executable,
53
- argv: ["run", "--workspace", workspacePath, script],
414
+ argv: useWorkspaceCwd
415
+ ? ["run", script]
416
+ : ["run", "--workspace", workspacePath, script],
54
417
  };
55
418
  }
56
419
 
@@ -155,7 +518,12 @@ function profileIdForWorkspace(workspacePath) {
155
518
  return `workspace-${workspacePath.replace(/[^A-Za-z0-9._-]+/g, "-")}`;
156
519
  }
157
520
 
158
- function workspaceProfiles(workspaces, npmExecutable) {
521
+ function workspaceProfiles(
522
+ workspaces,
523
+ npmExecutable,
524
+ packageManager = "npm",
525
+ useWorkspaceCwd = false,
526
+ ) {
159
527
  return workspaces
160
528
  .map((workspace) => {
161
529
  const qualityScripts = workspace.scripts.filter((script) =>
@@ -170,10 +538,11 @@ function workspaceProfiles(workspaces, npmExecutable) {
170
538
  requiresProfileIds: [],
171
539
  commands: npmExecutable
172
540
  ? qualityScripts.map((script) => workspaceCommand(
173
- `npm-${profileId}-${script}`,
541
+ `${packageManager}-${profileId}-${script}`,
174
542
  workspace.path,
175
543
  script,
176
544
  npmExecutable,
545
+ useWorkspaceCwd,
177
546
  ))
178
547
  : [],
179
548
  };
@@ -181,17 +550,32 @@ function workspaceProfiles(workspaces, npmExecutable) {
181
550
  .filter(Boolean);
182
551
  }
183
552
 
184
- function profileCandidates(root, manifest, npmExecutable, workspaces) {
553
+ function profileCandidates(
554
+ root,
555
+ manifest,
556
+ npmExecutable,
557
+ workspaces,
558
+ packageManager = "npm",
559
+ useWorkspaceCwd = false,
560
+ includeLegacyDatabaseProfile = false,
561
+ ) {
185
562
  const scripts = new Set(detectedScripts(manifest));
186
563
  const candidates = [];
187
564
  if (
188
- existsSync(path.join(root, "prisma"))
189
- || [...scripts].some((script) => /^(db:|prisma)/.test(script))
565
+ includeLegacyDatabaseProfile
566
+ && (
567
+ existsSync(path.join(root, "prisma"))
568
+ || [...scripts].some((script) => /^(db:|prisma)/.test(script))
569
+ )
190
570
  ) {
191
571
  const commands = [...scripts]
192
572
  .filter((script) => /^(db:|prisma)/.test(script))
193
573
  .map((script) => npmExecutable
194
- ? command(`npm-${script.replaceAll(":", "-")}`, script, npmExecutable)
574
+ ? command(
575
+ `${packageManager}-${script.replaceAll(":", "-")}`,
576
+ script,
577
+ npmExecutable,
578
+ )
195
579
  : null)
196
580
  .filter(Boolean);
197
581
  candidates.push({
@@ -209,7 +593,9 @@ function profileCandidates(root, manifest, npmExecutable, workspaces) {
209
593
  ) {
210
594
  const commands = ["build", "lint", "test", "typecheck"]
211
595
  .filter((script) => scripts.has(script))
212
- .map((script) => npmExecutable ? command(`npm-${script}`, script, npmExecutable) : null)
596
+ .map((script) => npmExecutable
597
+ ? command(`${packageManager}-${script}`, script, npmExecutable)
598
+ : null)
213
599
  .filter(Boolean);
214
600
  candidates.push({
215
601
  id: "node-quality",
@@ -226,7 +612,7 @@ function profileCandidates(root, manifest, npmExecutable, workspaces) {
226
612
  || scripts.has("e2e")
227
613
  ) {
228
614
  const commands = scripts.has("e2e") && npmExecutable
229
- ? [command("npm-e2e", "e2e", npmExecutable)]
615
+ ? [command(`${packageManager}-e2e`, "e2e", npmExecutable)]
230
616
  : [];
231
617
  candidates.push({
232
618
  id: "web-e2e",
@@ -237,7 +623,12 @@ function profileCandidates(root, manifest, npmExecutable, workspaces) {
237
623
  commands,
238
624
  });
239
625
  }
240
- candidates.push(...workspaceProfiles(workspaces, npmExecutable));
626
+ candidates.push(...workspaceProfiles(
627
+ workspaces,
628
+ npmExecutable,
629
+ packageManager,
630
+ useWorkspaceCwd,
631
+ ));
241
632
  return candidates.sort((left, right) => left.id.localeCompare(right.id));
242
633
  }
243
634
 
@@ -246,7 +637,15 @@ export function detectProfiles({ workspaceRoot } = {}) {
246
637
  const manifest = readWorkspaceJsonBounded(root, "package.json");
247
638
  const npmExecutable = resolveRegularExecutable("npm");
248
639
  const discovered = discoverWorkspaces(root, manifest);
249
- const candidates = profileCandidates(root, manifest, npmExecutable, discovered.workspaces);
640
+ const candidates = profileCandidates(
641
+ root,
642
+ manifest,
643
+ npmExecutable,
644
+ discovered.workspaces,
645
+ "npm",
646
+ false,
647
+ true,
648
+ );
250
649
  const primaryCandidates = candidates.filter((profile) => profile.role === "primary");
251
650
  const warnings = primaryCandidates.length > 1
252
651
  ? [`ambiguous_primary_profile:${primaryCandidates.map((profile) => profile.id).join(",")}`]
@@ -277,6 +676,435 @@ export function detectProfiles({ workspaceRoot } = {}) {
277
676
  };
278
677
  }
279
678
 
679
+ function detectedInputManifest(root, workspaces, toolBinding) {
680
+ const refs = new Set([
681
+ "package.json",
682
+ ...[
683
+ "bun.lock",
684
+ "bun.lockb",
685
+ "package-lock.json",
686
+ "pnpm-lock.yaml",
687
+ "yarn.lock",
688
+ ].filter(ref => existsSync(path.join(root, ref))),
689
+ ...workspaces.map(workspace => `${workspace.path}/package.json`),
690
+ ...toolBinding.inputFiles,
691
+ ]);
692
+ return [...refs]
693
+ .sort()
694
+ .map((ref) => {
695
+ const absolute = boundedRegularFile(root, ref);
696
+ return {
697
+ path: ref,
698
+ sha256: sha256(readFileSync(absolute)),
699
+ };
700
+ });
701
+ }
702
+
703
+ function bindCommandsToProjectTool(profiles, toolBinding) {
704
+ return profiles.map(profile => ({
705
+ ...profile,
706
+ commands: profile.commands.map(entry => ({
707
+ ...entry,
708
+ executable: toolBinding.executable,
709
+ argv: [...toolBinding.argvPrefix, ...entry.argv],
710
+ })),
711
+ }));
712
+ }
713
+
714
+ function proposedProfileDocument(candidates, inputManifest) {
715
+ const actionable = candidates.filter(profile => profile.commands.length > 0);
716
+ const selectedPrimary = actionable.find(profile => profile.id === "node-quality")
717
+ ?? actionable[0]
718
+ ?? null;
719
+ const rootSurfacePaths = inputManifest
720
+ .map(entry => entry.path)
721
+ .filter(ref => !ref.includes("/"));
722
+ return {
723
+ schemaVersion: "OwlCodaRunKitProfilesV1",
724
+ profiles: candidates.map(profile => ({
725
+ ...profile,
726
+ paths: [...new Set([
727
+ ...profile.paths,
728
+ ...(profile.id === selectedPrimary?.id ? rootSurfacePaths : []),
729
+ ])].sort(),
730
+ primary: profile.id === selectedPrimary?.id,
731
+ })),
732
+ };
733
+ }
734
+
735
+ function profilesFileHash(root) {
736
+ const ref = path.join(root, ".owlcoda", "runkit", "profiles.json");
737
+ if (!existsSync(ref)) return null;
738
+ return sha256(readFileSync(boundedRegularFile(
739
+ root,
740
+ path.join(".owlcoda", "runkit", "profiles.json"),
741
+ )));
742
+ }
743
+
744
+ function exactRegularFileBytes(absolutePath, expectedBytes) {
745
+ try {
746
+ const stat = lstatSync(absolutePath);
747
+ return !stat.isSymbolicLink()
748
+ && stat.isFile()
749
+ && realpathSync(absolutePath) === absolutePath
750
+ && readFileSync(absolutePath, "utf8") === expectedBytes;
751
+ } catch {
752
+ return false;
753
+ }
754
+ }
755
+
756
+ function commitCreateOnlyStagedFile(stagedPath, finalPath, expectedBytes) {
757
+ if (!exactRegularFileBytes(stagedPath, expectedBytes)) {
758
+ throw new Error("profile_apply_staged_file_invalid");
759
+ }
760
+ try {
761
+ linkSync(stagedPath, finalPath);
762
+ } catch (error) {
763
+ if (
764
+ error?.code !== "EEXIST"
765
+ || !exactRegularFileBytes(finalPath, expectedBytes)
766
+ ) {
767
+ throw error;
768
+ }
769
+ }
770
+ rmSync(stagedPath, { force: true });
771
+ }
772
+
773
+ function recoverProfileApplyTransaction({
774
+ root,
775
+ transactionRoot,
776
+ profilesPath,
777
+ profilesBytes,
778
+ receiptAbsolutePath,
779
+ receiptBytes,
780
+ receipt,
781
+ }) {
782
+ const transactionStat = lstatSync(transactionRoot);
783
+ if (
784
+ transactionStat.isSymbolicLink()
785
+ || !transactionStat.isDirectory()
786
+ || realpathSync(transactionRoot) !== transactionRoot
787
+ || !insideRoot(root, transactionRoot)
788
+ ) {
789
+ throw new Error("profile_apply_transaction_directory_untrusted");
790
+ }
791
+ const entries = readdirSync(transactionRoot).sort();
792
+ if (entries.some((entry) => !["profiles.json", "receipt.json"].includes(entry))) {
793
+ throw new Error("profile_apply_transaction_contents_untrusted");
794
+ }
795
+ const stagedProfiles = path.join(transactionRoot, "profiles.json");
796
+ const stagedReceipt = path.join(transactionRoot, "receipt.json");
797
+ const currentProfilesSha256 = profilesFileHash(root);
798
+ const receiptExists = existsSync(receiptAbsolutePath);
799
+ if (
800
+ currentProfilesSha256 === null
801
+ && !exactRegularFileBytes(stagedProfiles, profilesBytes)
802
+ ) {
803
+ throw new Error("profile_apply_transaction_staged_profile_mismatch");
804
+ }
805
+ if (
806
+ currentProfilesSha256 !== null
807
+ && currentProfilesSha256 !== receipt.appliedProfilesSha256
808
+ ) {
809
+ throw new Error("profile_apply_transaction_profile_mismatch");
810
+ }
811
+ if (
812
+ currentProfilesSha256 !== null
813
+ && existsSync(stagedProfiles)
814
+ && !exactRegularFileBytes(stagedProfiles, profilesBytes)
815
+ ) {
816
+ throw new Error("profile_apply_transaction_staged_profile_mismatch");
817
+ }
818
+ if (
819
+ receiptExists
820
+ ? !exactRegularFileBytes(receiptAbsolutePath, receiptBytes)
821
+ : !exactRegularFileBytes(stagedReceipt, receiptBytes)
822
+ ) {
823
+ throw new Error("profile_apply_transaction_receipt_mismatch");
824
+ }
825
+ let profilesCommittedByRecovery = false;
826
+ try {
827
+ if (currentProfilesSha256 === null) {
828
+ commitCreateOnlyStagedFile(stagedProfiles, profilesPath, profilesBytes);
829
+ profilesCommittedByRecovery = true;
830
+ } else {
831
+ rmSync(stagedProfiles, { force: true });
832
+ }
833
+ if (receiptExists) {
834
+ rmSync(stagedReceipt, { force: true });
835
+ } else {
836
+ commitCreateOnlyStagedFile(
837
+ stagedReceipt,
838
+ receiptAbsolutePath,
839
+ receiptBytes,
840
+ );
841
+ }
842
+ } catch (error) {
843
+ if (
844
+ profilesCommittedByRecovery
845
+ && profilesFileHash(root) === receipt.appliedProfilesSha256
846
+ ) {
847
+ try {
848
+ linkSync(profilesPath, stagedProfiles);
849
+ } catch (linkError) {
850
+ if (
851
+ linkError?.code !== "EEXIST"
852
+ || !exactRegularFileBytes(stagedProfiles, profilesBytes)
853
+ ) {
854
+ throw linkError;
855
+ }
856
+ }
857
+ rmSync(profilesPath);
858
+ }
859
+ throw error;
860
+ }
861
+ rmSync(transactionRoot, { recursive: true });
862
+ return receipt;
863
+ }
864
+
865
+ export function detectProfilesV2({
866
+ workspaceRoot,
867
+ projectToolResolver,
868
+ } = {}) {
869
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
870
+ const manifest = readWorkspaceJsonBounded(root, "package.json");
871
+ const discovered = discoverWorkspaces(root, manifest);
872
+ const toolBinding = resolveProjectTool(root, manifest, projectToolResolver);
873
+ const rawCandidates = profileCandidates(
874
+ root,
875
+ manifest,
876
+ toolBinding.executable,
877
+ discovered.workspaces,
878
+ toolBinding.packageManager,
879
+ true,
880
+ );
881
+ const candidates = bindCommandsToProjectTool(rawCandidates, toolBinding);
882
+ let inputManifest = [];
883
+ const issueCodes = [
884
+ ...toolBinding.issueCodes,
885
+ ...discovered.warnings,
886
+ ];
887
+ try {
888
+ inputManifest = detectedInputManifest(root, discovered.workspaces, toolBinding);
889
+ } catch (error) {
890
+ issueCodes.push(error instanceof Error ? error.message : String(error));
891
+ }
892
+ const proposedProfiles = proposedProfileDocument(candidates, inputManifest);
893
+ issueCodes.push(...validateProfileDocument(proposedProfiles));
894
+ const qualityIssues = [...new Set(issueCodes)].sort();
895
+ const confidence = qualityIssues.length === 0 && toolBinding.confidence === "high"
896
+ ? "high"
897
+ : "review_required";
898
+ const currentProfilesSha256 = profilesFileHash(root);
899
+ const applyStatus = currentProfilesSha256 !== null
900
+ ? "profiles_already_exists"
901
+ : confidence === "high"
902
+ ? "ready_to_apply"
903
+ : "review_required";
904
+ const body = {
905
+ schemaVersion: "OwlCodaRunKitProfileDetectionV2",
906
+ status: "profiles_detected",
907
+ dryRun: true,
908
+ writesPerformed: 0,
909
+ confidence,
910
+ applyStatus,
911
+ issueCodes: qualityIssues,
912
+ proposedProfiles,
913
+ candidates,
914
+ primaryProfileId: proposedProfiles.profiles.find(profile => profile.primary)?.id ?? null,
915
+ inputManifest,
916
+ toolBinding: {
917
+ status: toolBinding.status,
918
+ confidence: toolBinding.confidence,
919
+ source: toolBinding.source,
920
+ packageManager: toolBinding.packageManager,
921
+ executable: toolBinding.executable,
922
+ argvPrefix: [...toolBinding.argvPrefix],
923
+ version: toolBinding.version,
924
+ executableSha256: toolBinding.executableSha256,
925
+ launcherSha256: toolBinding.launcherSha256,
926
+ },
927
+ currentProfilesSha256,
928
+ detectedScripts: detectedScripts(manifest),
929
+ detectedWorkspaces: discovered.workspaces,
930
+ authorizationGranted: false,
931
+ };
932
+ return {
933
+ ...body,
934
+ detectionSha256: sha256(canonicalJson(body)),
935
+ };
936
+ }
937
+
938
+ function blockedApply(detection, issueCodes, currentProfilesSha256 = null) {
939
+ return {
940
+ schemaVersion: "OwlCodaRunKitProfilesApplyReceiptV1",
941
+ status: "profiles_apply_blocked",
942
+ writesPerformed: 0,
943
+ detectionSha256: detection?.detectionSha256 ?? null,
944
+ currentProfilesSha256,
945
+ issueCodes: [...new Set(issueCodes)].sort(),
946
+ authorizationGranted: false,
947
+ };
948
+ }
949
+
950
+ function verifyDetectionInputs(root, detection) {
951
+ const issues = [];
952
+ const { detectionSha256, ...body } = detection;
953
+ if (
954
+ detection.schemaVersion !== "OwlCodaRunKitProfileDetectionV2"
955
+ || detection.status !== "profiles_detected"
956
+ || !/^[a-f0-9]{64}$/.test(detectionSha256 ?? "")
957
+ || sha256(canonicalJson(body)) !== detectionSha256
958
+ ) {
959
+ issues.push("profile_detection_invalid");
960
+ }
961
+ if (detection.confidence !== "high" || detection.applyStatus !== "ready_to_apply") {
962
+ issues.push("profile_detection_not_ready_to_apply");
963
+ }
964
+ issues.push(...validateProfileDocument(detection.proposedProfiles));
965
+ const executable = regularExecutable(detection.toolBinding?.executable);
966
+ const launcher = regularExecutable(detection.toolBinding?.argvPrefix?.[0]);
967
+ if (
968
+ !executable
969
+ || !launcher
970
+ || sha256(readFileSync(executable)) !== detection.toolBinding.executableSha256
971
+ || sha256(readFileSync(launcher)) !== detection.toolBinding.launcherSha256
972
+ ) {
973
+ issues.push("profile_detection_tool_drift");
974
+ }
975
+ if (!Array.isArray(detection.inputManifest)) {
976
+ issues.push("profile_detection_invalid");
977
+ } else {
978
+ for (const entry of detection.inputManifest) {
979
+ try {
980
+ const actual = sha256(readFileSync(boundedRegularFile(root, entry.path)));
981
+ if (actual !== entry.sha256) issues.push(`profile_detection_input_drift:${entry.path}`);
982
+ } catch {
983
+ issues.push(`profile_detection_input_unavailable:${entry.path}`);
984
+ }
985
+ }
986
+ }
987
+ return [...new Set(issues)].sort();
988
+ }
989
+
990
+ export function applyDetectedProfilesV2({
991
+ workspaceRoot,
992
+ detection,
993
+ } = {}) {
994
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
995
+ let currentProfilesSha256;
996
+ try {
997
+ currentProfilesSha256 = profilesFileHash(root);
998
+ } catch {
999
+ return blockedApply(detection, ["profile_apply_directory_untrusted"]);
1000
+ }
1001
+ const issues = verifyDetectionInputs(root, detection ?? {});
1002
+ if (issues.length > 0) return blockedApply(detection, issues);
1003
+
1004
+ const receiptPath = path.join(
1005
+ ".owlcoda",
1006
+ "runkit",
1007
+ "profile-apply-receipts",
1008
+ `${detection.detectionSha256}.json`,
1009
+ );
1010
+ let runtimeRoot;
1011
+ let receiptRoot;
1012
+ try {
1013
+ runtimeRoot = ensureDirectoryChain(root, ".owlcoda/runkit");
1014
+ receiptRoot = ensureDirectoryChain(
1015
+ root,
1016
+ ".owlcoda/runkit/profile-apply-receipts",
1017
+ );
1018
+ } catch {
1019
+ return blockedApply(detection, ["profile_apply_directory_untrusted"]);
1020
+ }
1021
+ const profilesPath = path.join(runtimeRoot, "profiles.json");
1022
+ const receiptAbsolutePath = path.join(
1023
+ receiptRoot,
1024
+ `${detection.detectionSha256}.json`,
1025
+ );
1026
+ const profilesBytes = `${JSON.stringify(detection.proposedProfiles, null, 2)}\n`;
1027
+ const receipt = {
1028
+ schemaVersion: "OwlCodaRunKitProfilesApplyReceiptV1",
1029
+ status: "profiles_applied",
1030
+ writesPerformed: 2,
1031
+ detectionSha256: detection.detectionSha256,
1032
+ beforeProfilesSha256: null,
1033
+ appliedProfilesSha256: sha256(profilesBytes),
1034
+ receiptPath,
1035
+ issueCodes: [],
1036
+ authorizationGranted: false,
1037
+ };
1038
+ const receiptBytes = `${JSON.stringify(receipt, null, 2)}\n`;
1039
+ const transactionRoot = path.join(
1040
+ runtimeRoot,
1041
+ `.profile-apply-${detection.detectionSha256}.lock`,
1042
+ );
1043
+ if (existsSync(transactionRoot)) {
1044
+ try {
1045
+ return recoverProfileApplyTransaction({
1046
+ root,
1047
+ transactionRoot,
1048
+ profilesPath,
1049
+ profilesBytes,
1050
+ receiptAbsolutePath,
1051
+ receiptBytes,
1052
+ receipt,
1053
+ });
1054
+ } catch {
1055
+ return blockedApply(
1056
+ detection,
1057
+ ["profile_apply_transaction_recovery_invalid"],
1058
+ currentProfilesSha256,
1059
+ );
1060
+ }
1061
+ }
1062
+ if (currentProfilesSha256 !== null) {
1063
+ return blockedApply(detection, ["profiles_already_exists"], currentProfilesSha256);
1064
+ }
1065
+ try {
1066
+ mkdirSync(transactionRoot);
1067
+ } catch (error) {
1068
+ if (error?.code === "EEXIST") {
1069
+ return blockedApply(detection, ["profile_apply_transaction_active"]);
1070
+ }
1071
+ throw error;
1072
+ }
1073
+ let profilesCommitted = false;
1074
+ try {
1075
+ const current = profilesFileHash(root);
1076
+ if (current !== null) {
1077
+ return blockedApply(detection, ["profiles_already_exists"], current);
1078
+ }
1079
+ if (existsSync(receiptAbsolutePath)) {
1080
+ return blockedApply(detection, ["profile_apply_receipt_already_exists"]);
1081
+ }
1082
+ const stagedProfiles = path.join(transactionRoot, "profiles.json");
1083
+ const stagedReceipt = path.join(transactionRoot, "receipt.json");
1084
+ writeFileSync(stagedProfiles, profilesBytes, { flag: "wx" });
1085
+ writeFileSync(stagedReceipt, receiptBytes, { flag: "wx" });
1086
+ commitCreateOnlyStagedFile(stagedProfiles, profilesPath, profilesBytes);
1087
+ profilesCommitted = true;
1088
+ commitCreateOnlyStagedFile(
1089
+ stagedReceipt,
1090
+ receiptAbsolutePath,
1091
+ receiptBytes,
1092
+ );
1093
+ return receipt;
1094
+ } catch {
1095
+ if (
1096
+ profilesCommitted
1097
+ && existsSync(profilesPath)
1098
+ && profilesFileHash(root) === receipt.appliedProfilesSha256
1099
+ ) {
1100
+ rmSync(profilesPath);
1101
+ }
1102
+ return blockedApply(detection, ["profile_apply_transaction_failed"]);
1103
+ } finally {
1104
+ rmSync(transactionRoot, { recursive: true, force: true });
1105
+ }
1106
+ }
1107
+
280
1108
  function exactKeys(value, allowed) {
281
1109
  return value
282
1110
  && typeof value === "object"