owlrunkit 0.20.0 → 0.22.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 (50) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +102 -10
  3. package/docs/architecture/OWLCODA_RUNKIT_0201_RELEASE_TRUTH_AND_PROFILE_NORMALIZATION_PATCH_DECISION_20260813.md +95 -0
  4. package/docs/architecture/OWLCODA_RUNKIT_021_TEAM_DELIVERY_PHASE2_PRODUCT_DECISION_20260813.md +249 -0
  5. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +117 -12
  6. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  7. package/package.json +1 -1
  8. package/packages/attest/README.md +7 -1
  9. package/packages/attest/consumer-surface-v3.json +275 -0
  10. package/packages/attest/distribution-manifest-v1.json +24 -0
  11. package/packages/attest/schemas/canonical-candidate-identity-v1.schema.json +112 -0
  12. package/packages/attest/schemas/foreign-workspace-filesystem-identity-v1.schema.json +32 -0
  13. package/packages/attest/schemas/quick-verification-receipt-v4.schema.json +226 -0
  14. package/packages/attest/src/candidate-identity.mjs +454 -0
  15. package/packages/attest/src/index.mjs +11 -0
  16. package/packages/attest/src/quick-receipt-contract.mjs +126 -5
  17. package/packages/attest/src/quick.mjs +53 -8
  18. package/{registry-release-policy-v1.json → registry-release-policy-v2.json} +8 -9
  19. package/sbom.spdx.json +4 -4
  20. package/scripts/runkit-contract/core-contract.mjs +19 -1
  21. package/scripts/runkit-contract/fleet-skill-plan.mjs +223 -0
  22. package/scripts/runkit-contract/formal-workflow.mjs +3 -1
  23. package/scripts/runkit-contract/hook-recovery.mjs +496 -0
  24. package/scripts/runkit-contract/inspect-presentation.mjs +187 -0
  25. package/scripts/runkit-contract/install-codex-skill.mjs +1 -0
  26. package/scripts/runkit-contract/onboarding-bootstrap.mjs +15 -0
  27. package/scripts/runkit-contract/onboarding-doctor.mjs +378 -9
  28. package/scripts/runkit-contract/profile-onboarding.mjs +6 -0
  29. package/scripts/runkit-contract/project-cli-resolver.mjs +17 -1
  30. package/scripts/runkit-contract/quick-metrics.mjs +9 -5
  31. package/scripts/runkit-contract/quick-verify.mjs +108 -24
  32. package/scripts/runkit-contract/registry-adoption-gate.mjs +12 -3
  33. package/scripts/runkit-contract/registry-adoption.mjs +10 -5
  34. package/scripts/runkit-contract/runkit-bootstrap.mjs +1 -0
  35. package/scripts/runkit-contract/runkit-cli.mjs +332 -30
  36. package/scripts/runkit-contract/schemas/delivery-lifecycle-v1.schema.json +91 -0
  37. package/scripts/runkit-contract/schemas/external-gate-v1.schema.json +68 -0
  38. package/scripts/runkit-contract/schemas/fleet-skill-upgrade-plan-v1.schema.json +140 -0
  39. package/scripts/runkit-contract/schemas/hook-recovery-v1.schema.json +592 -0
  40. package/scripts/runkit-contract/schemas/inspect-compact-v1.schema.json +93 -0
  41. package/scripts/runkit-contract/schemas/inspect-compact-v2.schema.json +122 -0
  42. package/scripts/runkit-contract/schemas/team-delivery-status-v2.schema.json +219 -0
  43. package/scripts/runkit-contract/schemas/team-delivery-status-v3.schema.json +113 -0
  44. package/scripts/runkit-contract/schemas/team-project-event-v6.schema.json +82 -0
  45. package/scripts/runkit-contract/schemas/team-project-event-v7.schema.json +80 -0
  46. package/scripts/runkit-contract/schemas/team-project-status-v4.schema.json +208 -0
  47. package/scripts/runkit-contract/schemas/team-recommendation-v2.schema.json +120 -0
  48. package/scripts/runkit-contract/sync-release-identity.mjs +87 -0
  49. package/scripts/runkit-contract/team-delivery.mjs +203 -3
  50. package/scripts/runkit-contract/team-project.mjs +1032 -11
@@ -0,0 +1,496 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ lstatSync,
4
+ readFileSync,
5
+ realpathSync,
6
+ } from "node:fs";
7
+ import path from "node:path";
8
+
9
+ import {
10
+ RUNTIME_ROOT,
11
+ currentCoreIdentity,
12
+ validateExecutionPin,
13
+ validateProjectConfigV2,
14
+ } from "./core-contract.mjs";
15
+ import { inspectProjectControlState } from "./project-control-state.mjs";
16
+ import { readTeamProjectHookSourceV1 } from "./team-project.mjs";
17
+
18
+ const MAX_OUTPUT_BYTES = 2_000;
19
+ const MAX_CONTROL_BYTES = 1_048_576;
20
+ const MAX_PROJECT_ID_BYTES = 96;
21
+ const MAX_ACTOR_ID_BYTES = 96;
22
+ const MAX_GAP_ID_BYTES = 96;
23
+ const MAX_OBJECTIVE_PREVIEW_BYTES = 180;
24
+ const MAX_HEADLINE_BYTES = 120;
25
+ const MAX_GAP_REASON_BYTES = 120;
26
+ const MAX_NEXT_ACTION_BYTES = 120;
27
+ const MAX_WARNING_CODES = 4;
28
+ const MAX_WARNING_CODE_BYTES = 64;
29
+
30
+ const AUTHORITY_BOUNDARY = Object.freeze({
31
+ agentDispatch: false,
32
+ git: false,
33
+ release: false,
34
+ deployment: false,
35
+ production: false,
36
+ business: false,
37
+ automation: false,
38
+ money: false,
39
+ });
40
+
41
+ function sha256Ref(value) {
42
+ return "sha256:" + createHash("sha256").update(JSON.stringify(value)).digest("hex");
43
+ }
44
+
45
+ function compareCodeUnits(left, right) {
46
+ return left < right ? -1 : left > right ? 1 : 0;
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 withinRoot(root, candidate) {
60
+ const relative = path.relative(root, candidate);
61
+ return relative === ""
62
+ || (
63
+ relative !== ".."
64
+ && !relative.startsWith(".." + path.sep)
65
+ && !path.isAbsolute(relative)
66
+ );
67
+ }
68
+
69
+ function canonicalWorkspaceRoot(workspaceRoot) {
70
+ if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
71
+ throw new Error("workspace_required");
72
+ }
73
+ const requested = path.resolve(workspaceRoot);
74
+ const stat = lstatSync(requested);
75
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
76
+ throw new Error("workspace_redirected");
77
+ }
78
+ const real = realpathSync(requested);
79
+ return real;
80
+ }
81
+
82
+ function readRegularJsonBounded(root, filePath, label) {
83
+ const stat = lstatSync(filePath);
84
+ if (
85
+ stat.isSymbolicLink()
86
+ || !stat.isFile()
87
+ || stat.size > MAX_CONTROL_BYTES
88
+ ) {
89
+ throw new Error(label + "_invalid");
90
+ }
91
+ const real = realpathSync(filePath);
92
+ if (real !== path.resolve(filePath) || !withinRoot(root, real)) {
93
+ throw new Error(label + "_redirected");
94
+ }
95
+ return JSON.parse(readFileSync(real, "utf8"));
96
+ }
97
+
98
+ function unsafeCodePoint(codePoint) {
99
+ return codePoint <= 0x1f
100
+ || (codePoint >= 0x7f && codePoint <= 0x9f)
101
+ || (codePoint >= 0xd800 && codePoint <= 0xdfff)
102
+ || codePoint === 0x2028
103
+ || codePoint === 0x2029;
104
+ }
105
+
106
+ function escapedCodePoint(character) {
107
+ const codePoint = character.codePointAt(0);
108
+ if (!unsafeCodePoint(codePoint)) return character;
109
+ if (codePoint <= 0xffff) {
110
+ return "\\u" + codePoint.toString(16).padStart(4, "0");
111
+ }
112
+ return "\\u{" + codePoint.toString(16) + "}";
113
+ }
114
+
115
+ function boundedUntrustedText(value, maxBytes) {
116
+ if (value === null || value === undefined) {
117
+ return { value: null, truncated: false };
118
+ }
119
+ let rendered = "";
120
+ let renderedBytes = 0;
121
+ let truncated = false;
122
+ for (const character of String(value)) {
123
+ const token = escapedCodePoint(character);
124
+ const tokenBytes = Buffer.byteLength(token);
125
+ if (renderedBytes + tokenBytes + 3 > maxBytes) {
126
+ truncated = true;
127
+ break;
128
+ }
129
+ rendered += token;
130
+ renderedBytes += tokenBytes;
131
+ }
132
+ if (truncated) rendered += "…";
133
+ return { value: rendered, truncated };
134
+ }
135
+
136
+ function exactBoundedIdentity(value, maxBytes) {
137
+ if (value === null || value === undefined) return null;
138
+ const text = String(value);
139
+ return Buffer.byteLength(text) <= maxBytes ? text : null;
140
+ }
141
+
142
+ function safeWarningCode(value) {
143
+ if (
144
+ typeof value !== "string"
145
+ || !/^[a-z0-9][a-z0-9._:-]*$/u.test(value)
146
+ || Buffer.byteLength(value) > MAX_WARNING_CODE_BYTES
147
+ ) {
148
+ return "unclassified_warning";
149
+ }
150
+ return value;
151
+ }
152
+
153
+ function warningSummary(entries) {
154
+ const groups = new Map();
155
+ let count = 0;
156
+ let blockingCount = 0;
157
+ for (const entry of entries) {
158
+ const increment = Number.isSafeInteger(entry.count) && entry.count > 0
159
+ ? entry.count
160
+ : 1;
161
+ count += increment;
162
+ if (entry.blocking === true) blockingCount += increment;
163
+ const code = safeWarningCode(entry.code);
164
+ const current = groups.get(code) ?? { code, count: 0, blocking: false };
165
+ current.count += increment;
166
+ current.blocking ||= entry.blocking === true;
167
+ groups.set(code, current);
168
+ }
169
+ const ordered = [...groups.values()].sort((left, right) => (
170
+ compareCodeUnits(left.code, right.code)
171
+ ));
172
+ return {
173
+ count,
174
+ blockingCount,
175
+ codes: ordered.slice(0, MAX_WARNING_CODES).map(group => group.code),
176
+ truncated: ordered.length > MAX_WARNING_CODES,
177
+ };
178
+ }
179
+
180
+ function executionProjection(control) {
181
+ return {
182
+ state: control?.recovery?.state ?? "invalid_control_truth",
183
+ openCount: control?.activeRunIds?.length ?? 0,
184
+ activeLeaseCount: control?.activeLeaseIds?.length ?? 0,
185
+ };
186
+ }
187
+
188
+ function noProjectGap() {
189
+ return {
190
+ kind: "no_project_driver",
191
+ id: null,
192
+ reason: "No Project Driver is initialized.",
193
+ truncated: false,
194
+ };
195
+ }
196
+
197
+ function invalidGap() {
198
+ return {
199
+ kind: "invalid_control_truth",
200
+ id: null,
201
+ reason: "RunKit control truth must be repaired before recovery.",
202
+ truncated: false,
203
+ };
204
+ }
205
+
206
+ function projectGap(status) {
207
+ const kind = safeWarningCode(status.dominantGap?.kind ?? "none");
208
+ const id = exactBoundedIdentity(status.dominantGap?.id, MAX_GAP_ID_BYTES);
209
+ const reason = boundedUntrustedText(
210
+ status.dominantGap?.reason,
211
+ MAX_GAP_REASON_BYTES,
212
+ );
213
+ return {
214
+ kind,
215
+ id,
216
+ reason: reason.value,
217
+ truncated: reason.truncated
218
+ || (status.dominantGap?.id !== null
219
+ && status.dominantGap?.id !== undefined
220
+ && id === null),
221
+ };
222
+ }
223
+
224
+ function baseProjection({
225
+ status,
226
+ exitCode,
227
+ execution,
228
+ warnings,
229
+ project = null,
230
+ nextAction = null,
231
+ }) {
232
+ const objective = project
233
+ ? boundedUntrustedText(project.status.objective, MAX_OBJECTIVE_PREVIEW_BYTES)
234
+ : { value: null, truncated: false };
235
+ const headline = project
236
+ ? boundedUntrustedText(project.status.headline, MAX_HEADLINE_BYTES)
237
+ : { value: null, truncated: false };
238
+ const next = boundedUntrustedText(nextAction, MAX_NEXT_ACTION_BYTES);
239
+ const projectedGap = project ? projectGap(project.status) : noProjectGap();
240
+ return {
241
+ schemaVersion: "OwlCodaRunKitHookRecoveryV1",
242
+ status,
243
+ exitCode,
244
+ projectId: project
245
+ ? exactBoundedIdentity(project.status.projectId, MAX_PROJECT_ID_BYTES)
246
+ : null,
247
+ projectTruthHash: project?.status.projectTruthHash ?? null,
248
+ objectiveDigest: project ? sha256Ref(project.status.objective) : null,
249
+ objectivePreview: objective.value,
250
+ objectiveTruncated: objective.truncated,
251
+ projectTextTrust: "untrusted_data",
252
+ overall: project?.status.overall ?? null,
253
+ progress: {
254
+ completedWorkItems: project?.status.counts?.completed ?? null,
255
+ totalWorkItems: project?.status.counts?.total ?? null,
256
+ },
257
+ headline: headline.value,
258
+ dominantGap: status === "invalid_control_truth"
259
+ ? invalidGap()
260
+ : projectedGap,
261
+ nextActorId: project
262
+ ? exactBoundedIdentity(project.status.nextActorId, MAX_ACTOR_ID_BYTES)
263
+ : null,
264
+ nextAction: next.value,
265
+ deliveryDisposition: project?.status.deliveryDisposition ?? null,
266
+ execution,
267
+ warnings,
268
+ latestProjectFactAt: project?.latestProjectFactAt ?? null,
269
+ textTruncated: objective.truncated
270
+ || headline.truncated
271
+ || projectedGap.truncated
272
+ || next.truncated,
273
+ readOnly: true,
274
+ writesPerformed: false,
275
+ authorityBoundary: { ...AUTHORITY_BOUNDARY },
276
+ authorizationGranted: false,
277
+ };
278
+ }
279
+
280
+ function outputBytes(value) {
281
+ return Buffer.byteLength(JSON.stringify(value) + "\n");
282
+ }
283
+
284
+ function enforceOutputBound(value) {
285
+ if (outputBytes(value) <= MAX_OUTPUT_BYTES) return value;
286
+ const fallback = {
287
+ ...value,
288
+ status: "invalid_control_truth",
289
+ exitCode: 2,
290
+ projectId: null,
291
+ objectivePreview: null,
292
+ objectiveTruncated: true,
293
+ headline: "Hook recovery projection exceeded its byte bound.",
294
+ dominantGap: invalidGap(),
295
+ nextActorId: null,
296
+ nextAction: "inspect_project_truth_manually",
297
+ warnings: warningSummary([{
298
+ code: "projection_byte_limit_exceeded",
299
+ blocking: true,
300
+ }]),
301
+ textTruncated: true,
302
+ };
303
+ if (outputBytes(fallback) > MAX_OUTPUT_BYTES) {
304
+ throw new Error("hook_recovery_byte_bound_invariant_failed");
305
+ }
306
+ return fallback;
307
+ }
308
+
309
+ function invalidProjection({
310
+ execution = executionProjection(null),
311
+ warningCodes,
312
+ nextAction,
313
+ project = null,
314
+ }) {
315
+ return enforceOutputBound(baseProjection({
316
+ status: "invalid_control_truth",
317
+ exitCode: 2,
318
+ execution,
319
+ warnings: warningSummary(warningCodes.map(code => ({
320
+ code,
321
+ blocking: true,
322
+ }))),
323
+ project,
324
+ nextAction,
325
+ }));
326
+ }
327
+
328
+ function inspectCoreBinding(root) {
329
+ try {
330
+ const configPath = path.join(root, RUNTIME_ROOT, "config.json");
331
+ if (!pathEntryExists(configPath)) {
332
+ return { valid: false, code: "core_config_missing" };
333
+ }
334
+ const config = readRegularJsonBounded(root, configPath, "core_config");
335
+ const configGate = validateProjectConfigV2(config);
336
+ if (!configGate.valid) {
337
+ return { valid: false, code: "core_config_invalid" };
338
+ }
339
+ const pinGate = validateExecutionPin({
340
+ expected: config.core,
341
+ actual: currentCoreIdentity(),
342
+ });
343
+ return pinGate.status === "valid"
344
+ ? { valid: true }
345
+ : { valid: false, code: "core_version_mismatch" };
346
+ } catch {
347
+ return { valid: false, code: "core_config_invalid" };
348
+ }
349
+ }
350
+
351
+ function readProjectSource(root) {
352
+ try {
353
+ const projectRoot = path.join(root, RUNTIME_ROOT, "project");
354
+ if (!pathEntryExists(projectRoot)) {
355
+ return { state: "absent", project: null };
356
+ }
357
+ const projectStat = lstatSync(projectRoot);
358
+ const projectReal = realpathSync(projectRoot);
359
+ if (
360
+ projectStat.isSymbolicLink()
361
+ || !projectStat.isDirectory()
362
+ || projectReal !== path.resolve(projectRoot)
363
+ || !withinRoot(root, projectReal)
364
+ ) {
365
+ return { state: "invalid", project: null };
366
+ }
367
+ const definitionPath = path.join(projectRoot, "definition.json");
368
+ if (!pathEntryExists(definitionPath)) {
369
+ return { state: "invalid", project: null };
370
+ }
371
+ return {
372
+ state: "valid",
373
+ project: readTeamProjectHookSourceV1({ workspaceRoot: root }),
374
+ };
375
+ } catch {
376
+ return { state: "invalid", project: null };
377
+ }
378
+ }
379
+
380
+ export function buildHookRecoveryV1({ workspaceRoot } = {}) {
381
+ let root;
382
+ try {
383
+ root = canonicalWorkspaceRoot(workspaceRoot);
384
+ } catch {
385
+ return invalidProjection({
386
+ warningCodes: ["workspace_invalid"],
387
+ nextAction: "repair_workspace_binding",
388
+ });
389
+ }
390
+
391
+ const core = inspectCoreBinding(root);
392
+ let control;
393
+ try {
394
+ control = inspectProjectControlState({
395
+ workspaceRoot: root,
396
+ currentCore: currentCoreIdentity(),
397
+ });
398
+ } catch {
399
+ control = null;
400
+ }
401
+ const execution = executionProjection(control);
402
+ const projectSource = readProjectSource(root);
403
+ const project = projectSource.project;
404
+
405
+ if (!core.valid) {
406
+ return invalidProjection({
407
+ execution,
408
+ warningCodes: [core.code],
409
+ nextAction: "repair_core_binding",
410
+ project,
411
+ });
412
+ }
413
+ if (!control || control.status === "invalid") {
414
+ return invalidProjection({
415
+ execution,
416
+ warningCodes: ["control_truth_invalid"],
417
+ nextAction: "repair_execution_artifacts",
418
+ project,
419
+ });
420
+ }
421
+ if (control.activeRunIds.length > 1) {
422
+ return invalidProjection({
423
+ execution,
424
+ warningCodes: ["multiple_active_executions"],
425
+ nextAction: "select_active_execution",
426
+ project,
427
+ });
428
+ }
429
+ if (control.closedHistory?.blocking === true) {
430
+ return invalidProjection({
431
+ execution,
432
+ warningCodes: ["ambiguous_closed_history"],
433
+ nextAction: "repair_execution_lineage",
434
+ project,
435
+ });
436
+ }
437
+ if (projectSource.state === "invalid") {
438
+ return invalidProjection({
439
+ execution,
440
+ warningCodes: ["project_truth_invalid"],
441
+ nextAction: "repair_project_truth",
442
+ });
443
+ }
444
+ if (projectSource.state === "absent") {
445
+ return enforceOutputBound(baseProjection({
446
+ status: "no_project_driver",
447
+ exitCode: 0,
448
+ execution,
449
+ warnings: warningSummary([]),
450
+ nextAction: control.activeRunIds.length === 1
451
+ ? "inspect_active_execution"
452
+ : null,
453
+ }));
454
+ }
455
+
456
+ const identityIssues = [];
457
+ if (exactBoundedIdentity(project.status.projectId, MAX_PROJECT_ID_BYTES) === null) {
458
+ identityIssues.push("project_id_exceeds_hook_bound");
459
+ }
460
+ if (
461
+ project.status.nextActorId !== null
462
+ && exactBoundedIdentity(project.status.nextActorId, MAX_ACTOR_ID_BYTES) === null
463
+ ) {
464
+ identityIssues.push("next_actor_id_exceeds_hook_bound");
465
+ }
466
+ if (
467
+ project.status.dominantGap?.id !== null
468
+ && exactBoundedIdentity(project.status.dominantGap.id, MAX_GAP_ID_BYTES) === null
469
+ ) {
470
+ identityIssues.push("dominant_gap_id_exceeds_hook_bound");
471
+ }
472
+ if (identityIssues.length > 0) {
473
+ return invalidProjection({
474
+ execution,
475
+ warningCodes: identityIssues,
476
+ nextAction: "inspect_project_truth_manually",
477
+ project,
478
+ });
479
+ }
480
+
481
+ const warningEntries = (project.status.warnings ?? []).map(warning => ({
482
+ code: warning.code,
483
+ count: 1,
484
+ blocking: warning.blocking === true,
485
+ }));
486
+ return enforceOutputBound(baseProjection({
487
+ status: "hook_recovery_ready",
488
+ exitCode: 0,
489
+ execution,
490
+ warnings: warningSummary(warningEntries),
491
+ project,
492
+ nextAction: project.status.nextAction,
493
+ }));
494
+ }
495
+
496
+ export const HOOK_RECOVERY_MAX_OUTPUT_BYTES = MAX_OUTPUT_BYTES;
@@ -176,6 +176,193 @@ export function buildInspectSummary(
176
176
  };
177
177
  }
178
178
 
179
+ function compactWarningSummary(projectDriver) {
180
+ const byCode = new Map();
181
+ for (const warning of projectDriver?.warnings ?? []) {
182
+ const current = byCode.get(warning.code) ?? {
183
+ code: warning.code,
184
+ count: 0,
185
+ blocking: warning.blocking === true,
186
+ };
187
+ current.count += 1;
188
+ current.blocking ||= warning.blocking === true;
189
+ byCode.set(warning.code, current);
190
+ }
191
+ return {
192
+ count: [...byCode.values()].reduce((total, group) => total + group.count, 0),
193
+ groups: [...byCode.values()].sort((left, right) => compareCodeUnits(left.code, right.code)),
194
+ };
195
+ }
196
+
197
+ function warningBucket(groups) {
198
+ const ordered = [...groups].sort((left, right) => compareCodeUnits(left.code, right.code));
199
+ return {
200
+ count: ordered.reduce((total, group) => total + group.count, 0),
201
+ groups: ordered,
202
+ };
203
+ }
204
+
205
+ function projectWarningBuckets(projectDriver) {
206
+ const actionable = new Map();
207
+ const advisories = new Map();
208
+ for (const warning of projectDriver?.warnings ?? []) {
209
+ const advisory = warning.code === "no_bound_verification_receipt";
210
+ const target = advisory ? advisories : actionable;
211
+ const current = target.get(warning.code) ?? {
212
+ code: warning.code,
213
+ count: 0,
214
+ blocking: advisory ? false : warning.blocking === true,
215
+ recommendedAction: advisory
216
+ ? "no_retroactive_action_required"
217
+ : warning.recommendedAction ?? "review_project_warning",
218
+ };
219
+ current.count += 1;
220
+ current.blocking ||= warning.blocking === true;
221
+ target.set(warning.code, current);
222
+ }
223
+ return {
224
+ projectWarnings: warningBucket(actionable.values()),
225
+ advisorySummary: warningBucket(advisories.values()),
226
+ };
227
+ }
228
+
229
+ function maintenanceWarningBuckets(localMaintenance) {
230
+ const maintenance = [];
231
+ const launcherCount = localMaintenance?.profileLaunchers?.launcherWarnings?.length ?? 0;
232
+ if (launcherCount > 0) {
233
+ maintenance.push({
234
+ code: "profile_launcher_warnings",
235
+ count: launcherCount,
236
+ blocking: false,
237
+ recommendedAction: "review_profile_launchers",
238
+ });
239
+ }
240
+ for (const affected of localMaintenance?.runtimeIsolation?.affectedScripts ?? []) {
241
+ maintenance.push({
242
+ code: `runkit_runtime_collection_risk:${affected.tool}:${affected.script}`,
243
+ count: 1,
244
+ blocking: false,
245
+ recommendedAction: affected.tool === "eslint"
246
+ && affected.ignoreFile.startsWith("eslint.config.")
247
+ ? "add_runkit_to_eslint_flat_config_global_ignores"
248
+ : `exclude_runkit_runtime_in_${affected.ignoreFile}`,
249
+ });
250
+ }
251
+ const skill = (localMaintenance?.skill?.warningCodes ?? []).map(code => ({
252
+ code,
253
+ count: 1,
254
+ blocking: false,
255
+ recommendedAction: code === "shared_skill_version_drift"
256
+ ? "reload_package_bound_skill"
257
+ : "use_package_bound_skill",
258
+ }));
259
+ return {
260
+ maintenanceWarnings: warningBucket(maintenance),
261
+ skillDriftWarnings: warningBucket(skill),
262
+ };
263
+ }
264
+
265
+ export function buildInspectCompactV1(inspected) {
266
+ const projectDriver = inspected.projectDriver ?? null;
267
+ const summary = inspected.summary;
268
+ return {
269
+ schemaVersion: "OwlCodaRunKitInspectCompactV1",
270
+ status: "inspected_compact",
271
+ exitCode: inspected.exitCode,
272
+ projectTruthHash: projectDriver?.projectTruthHash ?? null,
273
+ progress: {
274
+ overall: projectDriver?.overall ?? null,
275
+ completedWorkItems: projectDriver?.counts?.completed ?? null,
276
+ totalWorkItems: projectDriver?.counts?.total ?? null,
277
+ },
278
+ dominantGap: {
279
+ code: summary.dominantGap.code,
280
+ reason: summary.dominantGap.reasons[0] ?? null,
281
+ },
282
+ ownerAgentId: projectDriver?.nextActorId
283
+ ?? projectDriver?.dominantGap?.agentId
284
+ ?? null,
285
+ nextAction: summary.recommendedNextAction ?? summary.nextAllowedAction,
286
+ warningSummary: compactWarningSummary(projectDriver),
287
+ execution: {
288
+ state: summary.currentExecution.state,
289
+ openCount: summary.currentExecution.openCount,
290
+ activeLeaseCount: summary.leases.activeCount,
291
+ },
292
+ authorityBoundary: {
293
+ git: false,
294
+ release: false,
295
+ deployment: false,
296
+ production: false,
297
+ business: false,
298
+ },
299
+ authorizationGranted: false,
300
+ };
301
+ }
302
+
303
+ export function buildInspectCompactV2(inspected) {
304
+ const projectDriver = inspected.projectDriver ?? null;
305
+ const summary = inspected.summary;
306
+ const { projectWarnings, advisorySummary } = projectWarningBuckets(projectDriver);
307
+ const { maintenanceWarnings, skillDriftWarnings } = maintenanceWarningBuckets(
308
+ inspected.localMaintenance ?? null,
309
+ );
310
+ const warningSummary = warningBucket([
311
+ ...projectWarnings.groups,
312
+ ...maintenanceWarnings.groups,
313
+ ...skillDriftWarnings.groups,
314
+ ]);
315
+ const lifecycleNextAction = summary.lifecycleNextAction ?? summary.nextAllowedAction;
316
+ const projectNextAction = projectDriver?.nextAction ?? null;
317
+ const maintenanceNextAction = skillDriftWarnings.groups.some(
318
+ group => group.code === "shared_skill_version_drift",
319
+ )
320
+ ? "reload_package_bound_skill"
321
+ : maintenanceWarnings.groups.length > 0
322
+ ? maintenanceWarnings.groups[0].recommendedAction
323
+ : null;
324
+ return {
325
+ schemaVersion: "OwlCodaRunKitInspectCompactV2",
326
+ status: "inspected_compact",
327
+ exitCode: inspected.exitCode,
328
+ projectTruthHash: projectDriver?.projectTruthHash ?? null,
329
+ progress: {
330
+ overall: projectDriver?.overall ?? null,
331
+ completedWorkItems: projectDriver?.counts?.completed ?? null,
332
+ totalWorkItems: projectDriver?.counts?.total ?? null,
333
+ },
334
+ dominantGap: {
335
+ code: summary.dominantGap.code,
336
+ reason: summary.dominantGap.reasons[0] ?? null,
337
+ },
338
+ ownerAgentId: projectDriver?.nextActorId
339
+ ?? projectDriver?.dominantGap?.agentId
340
+ ?? null,
341
+ nextAction: projectNextAction ?? lifecycleNextAction ?? maintenanceNextAction,
342
+ lifecycleNextAction,
343
+ projectNextAction,
344
+ maintenanceNextAction,
345
+ projectWarnings,
346
+ maintenanceWarnings,
347
+ skillDriftWarnings,
348
+ advisorySummary,
349
+ warningSummary,
350
+ execution: {
351
+ state: summary.currentExecution.state,
352
+ openCount: summary.currentExecution.openCount,
353
+ activeLeaseCount: summary.leases.activeCount,
354
+ },
355
+ authorityBoundary: {
356
+ git: false,
357
+ release: false,
358
+ deployment: false,
359
+ production: false,
360
+ business: false,
361
+ },
362
+ authorizationGranted: false,
363
+ };
364
+ }
365
+
179
366
  function value(value) {
180
367
  if (value === null || value === undefined || value === "") return "none";
181
368
  return String(value);
@@ -786,6 +786,7 @@ export async function runCli(argv = process.argv.slice(2)) {
786
786
  workspaceRoots: options.workspace ?? null,
787
787
  fleetManifestPath: options["fleet-manifest"] ?? null,
788
788
  fleetRoots: options["fleet-root"] ?? null,
789
+ fleetRegistryPath: options.registry ?? null,
789
790
  });
790
791
  return { exitCode: 0, ...output };
791
792
  }