owlrunkit 0.21.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 (26) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +50 -11
  3. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +39 -14
  4. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  5. package/package.json +1 -1
  6. package/packages/attest/distribution-manifest-v1.json +10 -0
  7. package/packages/attest/src/quick.mjs +10 -0
  8. package/registry-release-policy-v2.json +5 -5
  9. package/sbom.spdx.json +4 -4
  10. package/scripts/runkit-contract/core-contract.mjs +6 -1
  11. package/scripts/runkit-contract/formal-workflow.mjs +1 -1
  12. package/scripts/runkit-contract/hook-recovery.mjs +496 -0
  13. package/scripts/runkit-contract/inspect-presentation.mjs +131 -0
  14. package/scripts/runkit-contract/onboarding-doctor.mjs +179 -4
  15. package/scripts/runkit-contract/project-cli-resolver.mjs +8 -0
  16. package/scripts/runkit-contract/registry-adoption.mjs +4 -4
  17. package/scripts/runkit-contract/runkit-bootstrap.mjs +1 -0
  18. package/scripts/runkit-contract/runkit-cli.mjs +62 -30
  19. package/scripts/runkit-contract/schemas/fleet-skill-upgrade-plan-v1.schema.json +1 -1
  20. package/scripts/runkit-contract/schemas/hook-recovery-v1.schema.json +592 -0
  21. package/scripts/runkit-contract/schemas/inspect-compact-v2.schema.json +122 -0
  22. package/scripts/runkit-contract/schemas/team-delivery-status-v3.schema.json +113 -0
  23. package/scripts/runkit-contract/schemas/team-recommendation-v2.schema.json +120 -0
  24. package/scripts/runkit-contract/sync-release-identity.mjs +1 -1
  25. package/scripts/runkit-contract/team-delivery.mjs +75 -0
  26. package/scripts/runkit-contract/team-project.mjs +62 -2
@@ -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;
@@ -194,6 +194,74 @@ function compactWarningSummary(projectDriver) {
194
194
  };
195
195
  }
196
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
+
197
265
  export function buildInspectCompactV1(inspected) {
198
266
  const projectDriver = inspected.projectDriver ?? null;
199
267
  const summary = inspected.summary;
@@ -232,6 +300,69 @@ export function buildInspectCompactV1(inspected) {
232
300
  };
233
301
  }
234
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
+
235
366
  function value(value) {
236
367
  if (value === null || value === undefined || value === "") return "none";
237
368
  return String(value);