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.
- package/CHANGELOG.md +40 -0
- package/README.md +50 -11
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +39 -14
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +10 -0
- package/packages/attest/src/quick.mjs +10 -0
- package/registry-release-policy-v2.json +5 -5
- package/sbom.spdx.json +4 -4
- package/scripts/runkit-contract/core-contract.mjs +6 -1
- package/scripts/runkit-contract/formal-workflow.mjs +1 -1
- package/scripts/runkit-contract/hook-recovery.mjs +496 -0
- package/scripts/runkit-contract/inspect-presentation.mjs +131 -0
- package/scripts/runkit-contract/onboarding-doctor.mjs +179 -4
- package/scripts/runkit-contract/project-cli-resolver.mjs +8 -0
- package/scripts/runkit-contract/registry-adoption.mjs +4 -4
- package/scripts/runkit-contract/runkit-bootstrap.mjs +1 -0
- package/scripts/runkit-contract/runkit-cli.mjs +62 -30
- package/scripts/runkit-contract/schemas/fleet-skill-upgrade-plan-v1.schema.json +1 -1
- package/scripts/runkit-contract/schemas/hook-recovery-v1.schema.json +592 -0
- package/scripts/runkit-contract/schemas/inspect-compact-v2.schema.json +122 -0
- package/scripts/runkit-contract/schemas/team-delivery-status-v3.schema.json +113 -0
- package/scripts/runkit-contract/schemas/team-recommendation-v2.schema.json +120 -0
- package/scripts/runkit-contract/sync-release-identity.mjs +1 -1
- package/scripts/runkit-contract/team-delivery.mjs +75 -0
- 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);
|