owlrunkit 0.18.4 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +102 -4
  3. package/docs/architecture/OWLCODA_RUNKIT_0190_CONSOLIDATED_DAILY_USE_PRODUCT_DECISION_20260812.md +97 -0
  4. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +57 -0
  5. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +28 -4
  6. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  7. package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +57 -0
  8. package/package.json +1 -1
  9. package/packages/attest/README.md +9 -5
  10. package/packages/attest/consumer-surface-v2.json +263 -0
  11. package/packages/attest/distribution-manifest-v1.json +9 -1
  12. package/packages/attest/schemas/quick-verification-receipt-v3.schema.json +244 -0
  13. package/packages/attest/schemas/workspace-snapshot-v2.schema.json +114 -0
  14. package/packages/attest/src/dependency-environment.mjs +128 -0
  15. package/packages/attest/src/index.mjs +1 -0
  16. package/packages/attest/src/quick-receipt-contract.mjs +202 -9
  17. package/packages/attest/src/quick.mjs +40 -4
  18. package/packages/attest/src/workspace-snapshot.mjs +133 -6
  19. package/registry-release-policy-v1.json +5 -5
  20. package/sbom.spdx.json +5 -5
  21. package/scripts/runkit-contract/core-contract.mjs +21 -1
  22. package/scripts/runkit-contract/efficiency-metrics.mjs +559 -0
  23. package/scripts/runkit-contract/evidence-store.mjs +130 -0
  24. package/scripts/runkit-contract/onboarding-bootstrap.mjs +685 -0
  25. package/scripts/runkit-contract/onboarding-doctor.mjs +2 -1
  26. package/scripts/runkit-contract/outcome-presentation.mjs +171 -0
  27. package/scripts/runkit-contract/profile-onboarding.mjs +22 -2
  28. package/scripts/runkit-contract/project-cli-resolver.mjs +23 -5
  29. package/scripts/runkit-contract/quick-evidence-reuse.mjs +590 -0
  30. package/scripts/runkit-contract/quick-isolation.mjs +314 -0
  31. package/scripts/runkit-contract/quick-metrics.mjs +51 -3
  32. package/scripts/runkit-contract/quick-receipt.mjs +31 -1
  33. package/scripts/runkit-contract/quick-verify.mjs +216 -25
  34. package/scripts/runkit-contract/quick-workspace-snapshot.mjs +1 -0
  35. package/scripts/runkit-contract/runkit-bootstrap.mjs +1 -0
  36. package/scripts/runkit-contract/runkit-cli.mjs +330 -18
  37. package/scripts/runkit-contract/runtime-transition.mjs +478 -0
  38. package/scripts/runkit-contract/schemas/efficiency-event-v1.schema.json +56 -0
  39. package/scripts/runkit-contract/schemas/efficiency-metrics-v1.schema.json +220 -0
  40. package/scripts/runkit-contract/schemas/external-runtime-report-v1.schema.json +125 -0
  41. package/scripts/runkit-contract/schemas/onboarding-bootstrap-plan-v1.schema.json +168 -0
  42. package/scripts/runkit-contract/schemas/onboarding-bootstrap-receipt-v1.schema.json +130 -0
  43. package/scripts/runkit-contract/schemas/outcome-summary-v1.schema.json +83 -0
  44. package/scripts/runkit-contract/schemas/quick-evidence-reuse-plan-v1.schema.json +59 -0
  45. package/scripts/runkit-contract/schemas/quick-evidence-reuse-receipt-v1.schema.json +79 -0
  46. package/scripts/runkit-contract/schemas/runtime-transition-receipt-v1.schema.json +185 -0
  47. package/scripts/runkit-contract/schemas/team-project-event-v4.schema.json +142 -0
  48. package/scripts/runkit-contract/team-project.mjs +704 -16
  49. package/scripts/runkit-contract/verification-envelope.mjs +15 -18
@@ -0,0 +1,685 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ existsSync,
4
+ lstatSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ readdirSync,
8
+ realpathSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import path from "node:path";
13
+
14
+ import {
15
+ CORE_VERSION,
16
+ initializeProjectRunKit,
17
+ inspectProjectUpgradeSafety,
18
+ } from "./core-contract.mjs";
19
+ import { inspectInstalledSkillCompatibility } from "./install-codex-skill.mjs";
20
+ import {
21
+ readAdoptionReadiness,
22
+ readLocalInstallBinding,
23
+ readRegistryExact,
24
+ resolveBoundedWorkspaceRoot,
25
+ runDoctor,
26
+ } from "./onboarding-doctor.mjs";
27
+ import {
28
+ detectProfilesV3,
29
+ reconcileProfilesV2,
30
+ validateProfiles,
31
+ } from "./profile-onboarding.mjs";
32
+ import { sha256Canonical } from "./quick-canonical.mjs";
33
+ import { runRegistryAdoption } from "./registry-adoption.mjs";
34
+ import { OFFICIAL_NPM_REGISTRY } from "./registry-adoption-gate.mjs";
35
+ import { writeJsonExclusiveAtomically } from "./provenance-common.mjs";
36
+
37
+ const PACKAGE_NAME = "owlrunkit";
38
+ const EXACT_SPEC = `${PACKAGE_NAME}@${CORE_VERSION}`;
39
+ const RUNTIME_RELATIVE = path.join(".owlcoda", "runkit");
40
+ const TRANSACTION_PATHS = Object.freeze([
41
+ "config.json",
42
+ "profiles.json",
43
+ "config-migration-receipts",
44
+ "profile-apply-receipts",
45
+ "profile-reconcile-receipts",
46
+ "adoption",
47
+ ]);
48
+
49
+ function pathEntryExists(filePath) {
50
+ try {
51
+ lstatSync(filePath);
52
+ return true;
53
+ } catch (error) {
54
+ if (error?.code === "ENOENT") return false;
55
+ throw error;
56
+ }
57
+ }
58
+
59
+ function captureEntry(filePath) {
60
+ if (!pathEntryExists(filePath)) return { kind: "missing" };
61
+ const stat = lstatSync(filePath);
62
+ if (stat.isSymbolicLink()) throw new Error("bootstrap_transaction_symlink_rejected");
63
+ if (stat.isFile()) {
64
+ if (realpathSync(filePath) !== path.resolve(filePath)) {
65
+ throw new Error("bootstrap_transaction_file_alias_rejected");
66
+ }
67
+ return {
68
+ kind: "file",
69
+ bytes: readFileSync(filePath),
70
+ };
71
+ }
72
+ if (!stat.isDirectory() || realpathSync(filePath) !== path.resolve(filePath)) {
73
+ throw new Error("bootstrap_transaction_entry_invalid");
74
+ }
75
+ return {
76
+ kind: "directory",
77
+ entries: readdirSync(filePath)
78
+ .sort()
79
+ .map(name => ({
80
+ name,
81
+ snapshot: captureEntry(path.join(filePath, name)),
82
+ })),
83
+ };
84
+ }
85
+
86
+ function restoreEntry(filePath, snapshot) {
87
+ rmSync(filePath, { recursive: true, force: true });
88
+ if (snapshot.kind === "missing") return;
89
+ mkdirSync(path.dirname(filePath), { recursive: true });
90
+ if (snapshot.kind === "file") {
91
+ writeFileSync(filePath, snapshot.bytes, { flag: "wx" });
92
+ return;
93
+ }
94
+ mkdirSync(filePath);
95
+ for (const entry of snapshot.entries) {
96
+ restoreEntry(path.join(filePath, entry.name), entry.snapshot);
97
+ }
98
+ }
99
+
100
+ function captureTransactionPreimage(root) {
101
+ const owlcodaRoot = path.join(root, ".owlcoda");
102
+ const runtimeRoot = path.join(root, RUNTIME_RELATIVE);
103
+ return {
104
+ owlcodaExisted: pathEntryExists(owlcodaRoot),
105
+ runtimeExisted: pathEntryExists(runtimeRoot),
106
+ executionsExisted: pathEntryExists(path.join(runtimeRoot, "executions")),
107
+ entries: TRANSACTION_PATHS.map(relativePath => ({
108
+ relativePath,
109
+ snapshot: captureEntry(path.join(runtimeRoot, relativePath)),
110
+ })),
111
+ };
112
+ }
113
+
114
+ function rollbackTransaction(root, preimage) {
115
+ const runtimeRoot = path.join(root, RUNTIME_RELATIVE);
116
+ const restoredPaths = [];
117
+ for (const entry of preimage.entries) {
118
+ restoreEntry(path.join(runtimeRoot, entry.relativePath), entry.snapshot);
119
+ restoredPaths.push(path.posix.join(".owlcoda/runkit", entry.relativePath));
120
+ }
121
+ const executionsRoot = path.join(runtimeRoot, "executions");
122
+ if (!preimage.executionsExisted && existsSync(executionsRoot)) {
123
+ if (readdirSync(executionsRoot).length !== 0) {
124
+ throw new Error("bootstrap_rollback_unexpected_execution_state");
125
+ }
126
+ rmSync(executionsRoot, { recursive: true });
127
+ restoredPaths.push(".owlcoda/runkit/executions");
128
+ }
129
+ return restoredPaths.sort();
130
+ }
131
+
132
+ function ensureBootstrapLock(root) {
133
+ const owlcodaRoot = path.join(root, ".owlcoda");
134
+ if (!existsSync(owlcodaRoot)) mkdirSync(owlcodaRoot);
135
+ const owlcodaStat = lstatSync(owlcodaRoot);
136
+ if (
137
+ owlcodaStat.isSymbolicLink()
138
+ || !owlcodaStat.isDirectory()
139
+ || realpathSync(owlcodaRoot) !== owlcodaRoot
140
+ ) {
141
+ throw new Error("bootstrap_control_directory_untrusted");
142
+ }
143
+ const lockPath = path.join(owlcodaRoot, ".runkit-bootstrap.lock");
144
+ try {
145
+ mkdirSync(lockPath);
146
+ } catch (error) {
147
+ if (error?.code === "EEXIST") throw new Error("bootstrap_transaction_active");
148
+ throw error;
149
+ }
150
+ return () => rmSync(lockPath, { recursive: true, force: true });
151
+ }
152
+
153
+ function trustedDirectory(directory, issueCode) {
154
+ const stat = lstatSync(directory);
155
+ if (
156
+ stat.isSymbolicLink()
157
+ || !stat.isDirectory()
158
+ || realpathSync(directory) !== path.resolve(directory)
159
+ ) {
160
+ throw new Error(issueCode);
161
+ }
162
+ }
163
+
164
+ function acquireProjectControlLock(root) {
165
+ const runtimeRoot = path.join(root, RUNTIME_RELATIVE);
166
+ const executionsRoot = path.join(runtimeRoot, "executions");
167
+ const runtimeExisted = existsSync(runtimeRoot);
168
+ const executionsExisted = existsSync(executionsRoot);
169
+ try {
170
+ if (!runtimeExisted) mkdirSync(runtimeRoot);
171
+ trustedDirectory(runtimeRoot, "bootstrap_runtime_directory_untrusted");
172
+ if (!executionsExisted) mkdirSync(executionsRoot);
173
+ trustedDirectory(executionsRoot, "bootstrap_executions_directory_untrusted");
174
+ const lockPath = path.join(runtimeRoot, "control.lock");
175
+ try {
176
+ mkdirSync(lockPath);
177
+ } catch (error) {
178
+ if (error?.code === "EEXIST") {
179
+ throw new Error("bootstrap_project_control_transaction_active");
180
+ }
181
+ throw error;
182
+ }
183
+ return {
184
+ runtimeExisted,
185
+ executionsExisted,
186
+ release() {
187
+ rmSync(lockPath, { recursive: true, force: true });
188
+ },
189
+ };
190
+ } catch (error) {
191
+ if (!executionsExisted && existsSync(executionsRoot)) {
192
+ try {
193
+ if (readdirSync(executionsRoot).length === 0) {
194
+ rmSync(executionsRoot, { recursive: true });
195
+ }
196
+ } catch {
197
+ // The original acquisition failure remains the governing error.
198
+ }
199
+ }
200
+ if (!runtimeExisted && existsSync(runtimeRoot)) {
201
+ try {
202
+ if (readdirSync(runtimeRoot).length === 0) {
203
+ rmSync(runtimeRoot, { recursive: true });
204
+ }
205
+ } catch {
206
+ // The original acquisition failure remains the governing error.
207
+ }
208
+ }
209
+ throw error;
210
+ }
211
+ }
212
+
213
+ function registryMatchesLocal(registry, local) {
214
+ return registry.status === "registry_verified"
215
+ && local.status === "bound"
216
+ && registry.packageName === PACKAGE_NAME
217
+ && registry.version === CORE_VERSION
218
+ && registry.integrity === local.integrity
219
+ && registry.tarballUrl === local.resolved;
220
+ }
221
+
222
+ function profilePlan(root, projectToolResolver) {
223
+ const profilesPath = path.join(root, RUNTIME_RELATIVE, "profiles.json");
224
+ if (!existsSync(profilesPath)) {
225
+ const detection = detectProfilesV3({
226
+ workspaceRoot: root,
227
+ projectToolResolver,
228
+ });
229
+ return {
230
+ status: detection.applyStatus === "ready_to_apply"
231
+ ? "ready"
232
+ : "blocked",
233
+ action: "detect_then_reconcile",
234
+ issueCodes: detection.issueCodes,
235
+ evidenceRef: detection.detectionSha256,
236
+ };
237
+ }
238
+ const reconcile = reconcileProfilesV2({
239
+ workspaceRoot: root,
240
+ projectToolResolver,
241
+ });
242
+ return {
243
+ status: new Set([
244
+ "profiles_already_current",
245
+ "profiles_reconcile_ready",
246
+ ]).has(reconcile.status)
247
+ ? "ready"
248
+ : "blocked",
249
+ action: reconcile.status === "profiles_already_current"
250
+ ? "validate_current"
251
+ : "reconcile",
252
+ issueCodes: reconcile.issueCodes ?? [],
253
+ evidenceRef: reconcile.reconcileSha256 ?? null,
254
+ };
255
+ }
256
+
257
+ async function registryEvidence(registryClient) {
258
+ return registryClient
259
+ ? registryClient.readExact({
260
+ registry: OFFICIAL_NPM_REGISTRY,
261
+ packageName: PACKAGE_NAME,
262
+ version: CORE_VERSION,
263
+ })
264
+ : readRegistryExact({
265
+ registryUrl: OFFICIAL_NPM_REGISTRY,
266
+ packageName: PACKAGE_NAME,
267
+ version: CORE_VERSION,
268
+ });
269
+ }
270
+
271
+ function normalizeSharedSkill(inspectedSkill) {
272
+ const sourceStatus = inspectedSkill.status;
273
+ const status = new Set(["valid", "version_mismatch"]).has(sourceStatus)
274
+ ? sourceStatus
275
+ : "invalid";
276
+ const fallbackIssue = {
277
+ missing: "installed_skill_missing",
278
+ unmanaged: "installed_skill_unmanaged",
279
+ drifted: "installed_skill_drifted",
280
+ }[sourceStatus] ?? "installed_skill_invalid";
281
+ return {
282
+ status,
283
+ blocking: false,
284
+ issueCodes: [...new Set([
285
+ ...(inspectedSkill.issues ?? []),
286
+ ...(status === "invalid" ? [fallbackIssue] : []),
287
+ ])].sort(),
288
+ nextCommand: status === "valid"
289
+ ? null
290
+ : "run the explicit fleet-safe Skill installer after reviewing active executions and leases",
291
+ };
292
+ }
293
+
294
+ export async function planOnboardingBootstrapV1({
295
+ workspaceRoot,
296
+ exactSpec,
297
+ registryClient,
298
+ projectToolResolver,
299
+ skillRoot,
300
+ skillInspector = inspectInstalledSkillCompatibility,
301
+ } = {}) {
302
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
303
+ const issueCodes = [];
304
+ if (exactSpec !== EXACT_SPEC) issueCodes.push("bootstrap_exact_spec_required");
305
+ const local = readLocalInstallBinding({ workspaceRoot: root });
306
+ if (local.status !== "bound") {
307
+ issueCodes.push(...(local.issueCodes ?? ["registry_install_binding_mismatch"]));
308
+ }
309
+ const registry = await registryEvidence(registryClient);
310
+ if (registry.status !== "registry_verified") {
311
+ issueCodes.push(...(registry.issueCodes ?? ["registry_release_not_verified"]));
312
+ } else if (!registryMatchesLocal(registry, local)) {
313
+ issueCodes.push("registry_install_binding_mismatch");
314
+ }
315
+ const configExists = existsSync(path.join(root, RUNTIME_RELATIVE, "config.json"));
316
+ const upgradeSafety = configExists
317
+ ? inspectProjectUpgradeSafety({ workspaceRoot: root })
318
+ : { status: "safe", issues: [] };
319
+ if (upgradeSafety.status !== "safe") {
320
+ issueCodes.push(...(upgradeSafety.issues ?? ["bootstrap_project_upgrade_unsafe"]));
321
+ }
322
+ const profiles = profilePlan(root, projectToolResolver);
323
+ if (profiles.status !== "ready") issueCodes.push(...profiles.issueCodes);
324
+ let adoption = { status: "missing" };
325
+ try {
326
+ adoption = readAdoptionReadiness({ workspaceRoot: root });
327
+ } catch {
328
+ adoption = { status: "missing" };
329
+ }
330
+ let sharedSkill;
331
+ if (skillRoot === undefined) {
332
+ sharedSkill = {
333
+ status: "not_checked",
334
+ blocking: false,
335
+ issueCodes: [],
336
+ nextCommand: "rerun bootstrap with --skill-root <installed-skill-directory>",
337
+ };
338
+ } else {
339
+ const inspectedSkill = await skillInspector({ targetRoot: skillRoot });
340
+ sharedSkill = normalizeSharedSkill(inspectedSkill);
341
+ }
342
+ const steps = [
343
+ { id: "exact_package_binding", action: "verify", writes: 0 },
344
+ { id: "initialize_core", action: configExists ? "verify_or_upgrade" : "initialize", writes: 1 },
345
+ { id: "reconcile_profiles", action: profiles.action, writes: 1 },
346
+ { id: "validate_profiles", action: "validate", writes: 0 },
347
+ { id: "adopt_registry_release", action: adoption.status === "ok" ? "verify_current" : "adopt", writes: 1 },
348
+ { id: "doctor", action: "read_back", writes: 0 },
349
+ ];
350
+ const body = {
351
+ schemaVersion: "OwlCodaRunKitOnboardingBootstrapPlanV1",
352
+ status: issueCodes.length === 0 ? "bootstrap_ready" : "bootstrap_blocked",
353
+ exitCode: issueCodes.length === 0 ? 0 : 2,
354
+ dryRun: true,
355
+ writesPerformed: 0,
356
+ workspaceRoot: root,
357
+ exactSpec: EXACT_SPEC,
358
+ localBinding: local.status === "bound"
359
+ ? {
360
+ status: "bound",
361
+ version: local.version,
362
+ resolved: local.resolved,
363
+ integrity: local.integrity,
364
+ }
365
+ : { status: local.status },
366
+ registry: registry.status === "registry_verified"
367
+ ? {
368
+ status: "verified",
369
+ version: registry.version,
370
+ shasum: registry.shasum,
371
+ integrity: registry.integrity,
372
+ tarballUrl: registry.tarballUrl,
373
+ }
374
+ : {
375
+ status: registry.status,
376
+ issueCodes: registry.issueCodes ?? [],
377
+ },
378
+ upgradeSafety: upgradeSafety.status,
379
+ profiles,
380
+ sharedSkill,
381
+ steps,
382
+ estimatedAdditionalSteps: steps.length,
383
+ estimatedEvidenceObjects: 3,
384
+ rollbackScope: TRANSACTION_PATHS.map(value => path.posix.join(".owlcoda/runkit", value)),
385
+ issueCodes: [...new Set(issueCodes)].sort(),
386
+ nextCommand: issueCodes.length === 0
387
+ ? `npx --no-install owlrunkit bootstrap --workspace ${JSON.stringify(root)} --exact ${EXACT_SPEC} --apply`
388
+ : "repair bootstrap preflight issues and rerun --dry-run",
389
+ authorizationGranted: false,
390
+ };
391
+ return {
392
+ ...body,
393
+ planSha256: sha256Canonical(body),
394
+ };
395
+ }
396
+
397
+ function stepFailure(stepId, result) {
398
+ const issues = result?.issueCodes ?? result?.issues ?? [];
399
+ const suffix = issues.length > 0 ? `:${issues.join(",")}` : "";
400
+ return new Error(`bootstrap_step_failed:${stepId}${suffix}`);
401
+ }
402
+
403
+ function writeBootstrapReceipt(root, receipt) {
404
+ const relativePath = path.posix.join(
405
+ ".owlcoda/runkit/bootstrap-receipts",
406
+ `${receipt.bootstrapId}.json`,
407
+ );
408
+ writeJsonExclusiveAtomically(path.join(root, relativePath), receipt);
409
+ return relativePath;
410
+ }
411
+
412
+ function blockedBootstrapApply(plan, issueCode) {
413
+ return {
414
+ ...plan,
415
+ status: "bootstrap_blocked",
416
+ exitCode: 2,
417
+ writesPerformed: 0,
418
+ issueCodes: [...new Set([...plan.issueCodes, issueCode])].sort(),
419
+ nextCommand: issueCode === "bootstrap_transaction_active"
420
+ ? "wait for the active bootstrap transaction to finish, then rerun --dry-run"
421
+ : "rerun --dry-run against the current project state",
422
+ authorizationGranted: false,
423
+ };
424
+ }
425
+
426
+ export async function applyOnboardingBootstrapV1({
427
+ workspaceRoot,
428
+ exactSpec,
429
+ registryClient,
430
+ projectToolResolver,
431
+ skillRoot,
432
+ skillInspector,
433
+ onAfterStep,
434
+ } = {}) {
435
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
436
+ const plan = await planOnboardingBootstrapV1({
437
+ workspaceRoot: root,
438
+ exactSpec,
439
+ registryClient,
440
+ projectToolResolver,
441
+ skillRoot,
442
+ skillInspector,
443
+ });
444
+ if (plan.status !== "bootstrap_ready") return plan;
445
+
446
+ const bootstrapId = `bootstrap-${randomUUID()}`;
447
+ const owlcodaRoot = path.join(root, ".owlcoda");
448
+ const owlcodaExistedBefore = existsSync(owlcodaRoot);
449
+ let releaseLock = null;
450
+ let projectControl = null;
451
+ try {
452
+ releaseLock = ensureBootstrapLock(root);
453
+ projectControl = acquireProjectControlLock(root);
454
+ } catch (error) {
455
+ releaseLock?.();
456
+ if (
457
+ !owlcodaExistedBefore
458
+ && existsSync(owlcodaRoot)
459
+ && readdirSync(owlcodaRoot).length === 0
460
+ ) {
461
+ rmSync(owlcodaRoot, { recursive: true });
462
+ }
463
+ const issueCode = error instanceof Error ? error.message : String(error);
464
+ return blockedBootstrapApply(plan, issueCode);
465
+ }
466
+ const releaseEarly = () => {
467
+ const acquiredControl = projectControl;
468
+ acquiredControl?.release();
469
+ projectControl = null;
470
+ const runtimeRoot = path.join(root, RUNTIME_RELATIVE);
471
+ const executionsRoot = path.join(runtimeRoot, "executions");
472
+ if (
473
+ !acquiredControl?.executionsExisted
474
+ && existsSync(executionsRoot)
475
+ && readdirSync(executionsRoot).length === 0
476
+ ) {
477
+ rmSync(executionsRoot, { recursive: true });
478
+ }
479
+ if (
480
+ !acquiredControl?.runtimeExisted
481
+ && existsSync(runtimeRoot)
482
+ && readdirSync(runtimeRoot).length === 0
483
+ ) {
484
+ rmSync(runtimeRoot, { recursive: true });
485
+ }
486
+ releaseLock?.();
487
+ releaseLock = null;
488
+ if (
489
+ !owlcodaExistedBefore
490
+ && existsSync(owlcodaRoot)
491
+ && readdirSync(owlcodaRoot).length === 0
492
+ ) {
493
+ rmSync(owlcodaRoot, { recursive: true });
494
+ }
495
+ };
496
+ let lockedPlan;
497
+ try {
498
+ lockedPlan = await planOnboardingBootstrapV1({
499
+ workspaceRoot: root,
500
+ exactSpec,
501
+ registryClient,
502
+ projectToolResolver,
503
+ skillRoot,
504
+ skillInspector,
505
+ });
506
+ } catch (error) {
507
+ releaseEarly();
508
+ throw error;
509
+ }
510
+ if (lockedPlan.planSha256 !== plan.planSha256) {
511
+ releaseEarly();
512
+ return blockedBootstrapApply(lockedPlan, "bootstrap_preflight_drift");
513
+ }
514
+ let preimage;
515
+ try {
516
+ preimage = captureTransactionPreimage(root);
517
+ preimage.executionsExisted = projectControl.executionsExisted;
518
+ } catch (error) {
519
+ releaseEarly();
520
+ return blockedBootstrapApply(
521
+ lockedPlan,
522
+ error instanceof Error ? error.message : String(error),
523
+ );
524
+ }
525
+ let failedStep = null;
526
+ const completedSteps = [];
527
+ const startedAt = new Date().toISOString();
528
+ try {
529
+ const runStep = async (stepId, operation, validStatuses) => {
530
+ failedStep = stepId;
531
+ const result = await operation();
532
+ if (!validStatuses.has(result.status)) throw stepFailure(stepId, result);
533
+ completedSteps.push({ id: stepId, status: result.status });
534
+ await onAfterStep?.(stepId, result);
535
+ return result;
536
+ };
537
+
538
+ await runStep(
539
+ "initialize_core",
540
+ () => initializeProjectRunKit({ workspaceRoot: root }),
541
+ new Set(["initialized", "upgraded"]),
542
+ );
543
+ const reconciled = await runStep(
544
+ "reconcile_profiles",
545
+ () => reconcileProfilesV2({
546
+ workspaceRoot: root,
547
+ apply: true,
548
+ projectToolResolver,
549
+ }),
550
+ new Set(["profiles_already_current", "profiles_reconciled"]),
551
+ );
552
+ await runStep(
553
+ "validate_profiles",
554
+ () => validateProfiles({ workspaceRoot: root }),
555
+ new Set(["valid"]),
556
+ );
557
+ let adoption = readAdoptionReadiness({ workspaceRoot: root });
558
+ if (adoption.status !== "ok") {
559
+ adoption = await runStep(
560
+ "adopt_registry_release",
561
+ () => runRegistryAdoption({
562
+ workspaceRoot: root,
563
+ exactSpec,
564
+ registryClient,
565
+ }),
566
+ new Set(["adopted"]),
567
+ );
568
+ } else {
569
+ completedSteps.push({ id: "adopt_registry_release", status: "already_current" });
570
+ await onAfterStep?.("adopt_registry_release", adoption);
571
+ }
572
+ const doctor = await runStep(
573
+ "doctor",
574
+ () => runDoctor({
575
+ workspaceRoot: root,
576
+ registryUrl: OFFICIAL_NPM_REGISTRY,
577
+ registryClient,
578
+ }),
579
+ new Set(["ready"]),
580
+ );
581
+ failedStep = null;
582
+ const receipt = {
583
+ schemaVersion: "OwlCodaRunKitOnboardingBootstrapReceiptV1",
584
+ status: "bootstrapped",
585
+ exitCode: 0,
586
+ bootstrapId,
587
+ planSha256: plan.planSha256,
588
+ exactSpec: EXACT_SPEC,
589
+ startedAt,
590
+ completedAt: new Date().toISOString(),
591
+ completedSteps,
592
+ profileStatus: reconciled.status,
593
+ adoptionStatus: adoption.status,
594
+ doctorStatus: doctor.status,
595
+ rollbackPerformed: false,
596
+ rollbackOutcome: "not_required",
597
+ authorizationGranted: false,
598
+ };
599
+ const receiptPath = writeBootstrapReceipt(root, receipt);
600
+ return {
601
+ ...receipt,
602
+ receiptPath,
603
+ nextAllowedAction: "plan_project_work",
604
+ };
605
+ } catch (error) {
606
+ let rollbackOutcome = "restored";
607
+ let restoredPaths = [];
608
+ let rollbackIssue = null;
609
+ try {
610
+ restoredPaths = rollbackTransaction(root, preimage);
611
+ } catch (rollbackError) {
612
+ rollbackOutcome = "failed";
613
+ rollbackIssue = rollbackError instanceof Error
614
+ ? rollbackError.message
615
+ : String(rollbackError);
616
+ }
617
+ const receipt = {
618
+ schemaVersion: "OwlCodaRunKitOnboardingBootstrapReceiptV1",
619
+ status: "bootstrap_failed",
620
+ exitCode: 2,
621
+ bootstrapId,
622
+ planSha256: plan.planSha256,
623
+ exactSpec: EXACT_SPEC,
624
+ startedAt,
625
+ completedAt: new Date().toISOString(),
626
+ completedSteps,
627
+ failedStep,
628
+ issueCodes: [
629
+ error instanceof Error ? error.message : String(error),
630
+ ...(rollbackIssue === null ? [] : [rollbackIssue]),
631
+ ],
632
+ rollbackPerformed: true,
633
+ rollbackOutcome,
634
+ restoredPaths,
635
+ authorizationGranted: false,
636
+ };
637
+ let receiptPath = null;
638
+ try {
639
+ receiptPath = writeBootstrapReceipt(root, receipt);
640
+ } catch (receiptError) {
641
+ receipt.issueCodes.push(
642
+ `bootstrap_failure_receipt_write_failed:${receiptError instanceof Error ? receiptError.message : String(receiptError)}`,
643
+ );
644
+ }
645
+ return {
646
+ ...receipt,
647
+ receiptPath,
648
+ nextAllowedAction: rollbackOutcome === "restored"
649
+ ? "repair_failed_step_and_retry_bootstrap"
650
+ : "recover_bootstrap_transaction",
651
+ };
652
+ } finally {
653
+ projectControl?.release();
654
+ releaseLock?.();
655
+ }
656
+ }
657
+
658
+ export function formatOnboardingBootstrapHumanV1(result) {
659
+ if (result.status === "bootstrap_ready") {
660
+ return [
661
+ `Bootstrap ready for ${result.exactSpec}.`,
662
+ `Profiles: ${result.profiles.action}; shared Skill: ${result.sharedSkill.status}.`,
663
+ `Expected steps: ${result.estimatedAdditionalSteps}; evidence objects: ${result.estimatedEvidenceObjects}.`,
664
+ `Next: ${result.nextCommand}`,
665
+ "Git, release, deployment, production, and business authority remain false.",
666
+ "",
667
+ ].join("\n");
668
+ }
669
+ if (result.status === "bootstrapped") {
670
+ return [
671
+ `Bootstrapped ${result.exactSpec}; doctor is ${result.doctorStatus}.`,
672
+ `Receipt: ${result.receiptPath}`,
673
+ "Git, release, deployment, production, and business authority remain false.",
674
+ "",
675
+ ].join("\n");
676
+ }
677
+ const issues = result.issueCodes?.join(", ") || "unknown issue";
678
+ return [
679
+ `Bootstrap blocked: ${issues}`,
680
+ `Rollback: ${result.rollbackOutcome ?? "not_required"}.`,
681
+ ...(result.receiptPath ? [`Receipt: ${result.receiptPath}`] : []),
682
+ "No Git, release, deployment, production, or business authority was granted.",
683
+ "",
684
+ ].join("\n");
685
+ }
@@ -305,7 +305,8 @@ export function readLocalInstallBinding({ workspaceRoot } = {}) {
305
305
  const binding = resolveProjectCli({
306
306
  workspaceRoot,
307
307
  expectedVersion: EXPECTED_VERSION,
308
- requireEntrypoint: false,
308
+ requireEntrypoint: true,
309
+ requireProjectConfig: false,
309
310
  });
310
311
  if (binding.status === "bound") return binding;
311
312
  const issue = binding.issueCodes?.[0];