owlrunkit 0.18.4 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +109 -4
- package/docs/architecture/OWLCODA_RUNKIT_0190_CONSOLIDATED_DAILY_USE_PRODUCT_DECISION_20260812.md +100 -0
- package/docs/architecture/OWLCODA_RUNKIT_0191_PROFILE_RECONCILE_PATCH_PRODUCT_DECISION_20260813.md +30 -0
- package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +57 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +28 -4
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +57 -0
- package/package.json +1 -1
- package/packages/attest/README.md +9 -5
- package/packages/attest/consumer-surface-v2.json +263 -0
- package/packages/attest/distribution-manifest-v1.json +14 -1
- package/packages/attest/schemas/quick-verification-receipt-v3.schema.json +244 -0
- package/packages/attest/schemas/workspace-snapshot-v2.schema.json +114 -0
- package/packages/attest/src/dependency-environment.mjs +128 -0
- package/packages/attest/src/index.mjs +1 -0
- package/packages/attest/src/quick-receipt-contract.mjs +202 -9
- package/packages/attest/src/quick.mjs +45 -4
- package/packages/attest/src/workspace-snapshot.mjs +133 -6
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/core-contract.mjs +21 -1
- package/scripts/runkit-contract/efficiency-metrics.mjs +559 -0
- package/scripts/runkit-contract/evidence-store.mjs +130 -0
- package/scripts/runkit-contract/onboarding-bootstrap.mjs +685 -0
- package/scripts/runkit-contract/onboarding-doctor.mjs +2 -1
- package/scripts/runkit-contract/outcome-presentation.mjs +171 -0
- package/scripts/runkit-contract/profile-onboarding.mjs +74 -35
- package/scripts/runkit-contract/project-cli-resolver.mjs +26 -5
- package/scripts/runkit-contract/quick-evidence-reuse.mjs +590 -0
- package/scripts/runkit-contract/quick-isolation.mjs +314 -0
- package/scripts/runkit-contract/quick-metrics.mjs +51 -3
- package/scripts/runkit-contract/quick-receipt.mjs +31 -1
- package/scripts/runkit-contract/quick-verify.mjs +216 -25
- package/scripts/runkit-contract/quick-workspace-snapshot.mjs +1 -0
- package/scripts/runkit-contract/runkit-bootstrap.mjs +1 -0
- package/scripts/runkit-contract/runkit-cli.mjs +330 -18
- package/scripts/runkit-contract/runtime-transition.mjs +478 -0
- package/scripts/runkit-contract/schemas/efficiency-event-v1.schema.json +56 -0
- package/scripts/runkit-contract/schemas/efficiency-metrics-v1.schema.json +220 -0
- package/scripts/runkit-contract/schemas/external-runtime-report-v1.schema.json +125 -0
- package/scripts/runkit-contract/schemas/onboarding-bootstrap-plan-v1.schema.json +168 -0
- package/scripts/runkit-contract/schemas/onboarding-bootstrap-receipt-v1.schema.json +130 -0
- package/scripts/runkit-contract/schemas/outcome-summary-v1.schema.json +83 -0
- package/scripts/runkit-contract/schemas/quick-evidence-reuse-plan-v1.schema.json +59 -0
- package/scripts/runkit-contract/schemas/quick-evidence-reuse-receipt-v1.schema.json +79 -0
- package/scripts/runkit-contract/schemas/runtime-transition-receipt-v1.schema.json +185 -0
- package/scripts/runkit-contract/schemas/team-project-event-v4.schema.json +142 -0
- package/scripts/runkit-contract/team-project.mjs +704 -16
- 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:
|
|
308
|
+
requireEntrypoint: true,
|
|
309
|
+
requireProjectConfig: false,
|
|
309
310
|
});
|
|
310
311
|
if (binding.status === "bound") return binding;
|
|
311
312
|
const issue = binding.issueCodes?.[0];
|