owlrunkit 0.18.1 → 0.18.3
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 +85 -19
- package/docs/architecture/OWLCODA_RUNKIT_018_PROJECT_DRIVER_PRODUCT_DEFINITION.md +16 -5
- package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +78 -4
- package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +7 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +28 -9
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.1.md +160 -0
- package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +545 -0
- package/package.json +1 -1
- package/packages/attest/README.md +4 -3
- package/packages/attest/consumer-surface-v1.json +1 -0
- package/packages/attest/distribution-manifest-v1.json +12 -1
- package/packages/attest/schemas/quick-verification-receipt-v2.schema.json +232 -0
- package/packages/attest/src/index.mjs +4 -1
- package/packages/attest/src/quick-receipt-contract.mjs +13 -3
- package/packages/attest/src/quick.mjs +21 -2
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/assurance-router.mjs +3 -1
- package/scripts/runkit-contract/core-contract.mjs +2 -1
- package/scripts/runkit-contract/onboarding-doctor.mjs +46 -18
- package/scripts/runkit-contract/profile-onboarding.mjs +4 -0
- package/scripts/runkit-contract/project-cli-resolver.mjs +3 -0
- package/scripts/runkit-contract/quick-metrics.mjs +3 -3
- package/scripts/runkit-contract/quick-receipt.mjs +110 -5
- package/scripts/runkit-contract/quick-verify.mjs +93 -11
- package/scripts/runkit-contract/runkit-cli.mjs +172 -24
- package/scripts/runkit-contract/schemas/profile-reconcile-plan-v1.schema.json +2 -0
- package/scripts/runkit-contract/schemas/team-project-event-v2.schema.json +128 -0
- package/scripts/runkit-contract/team-project.mjs +310 -45
- package/scripts/runkit-contract/usage-mode.mjs +11 -13
|
@@ -16,6 +16,8 @@ import path from "node:path";
|
|
|
16
16
|
import { sha256Bytes } from "./quick-canonical.mjs";
|
|
17
17
|
|
|
18
18
|
const QUICK_ROOT = ".owlcoda/runkit/quick";
|
|
19
|
+
const OUTPUT_SUMMARY_BYTES = 16 * 1024;
|
|
20
|
+
const OUTPUT_SUMMARY_LINES = 3;
|
|
19
21
|
|
|
20
22
|
function withinRoot(root, candidate) {
|
|
21
23
|
const remainder = path.relative(root, candidate);
|
|
@@ -87,12 +89,56 @@ export function closeQuickOutputFiles(store) {
|
|
|
87
89
|
closeSync(store.stderrFd);
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
function cleanOutputLine(line) {
|
|
93
|
+
const withoutAnsi = line.replace(/\u001b\[[0-?]*[ -/]*[@-~]/gu, "");
|
|
94
|
+
return [...withoutAnsi]
|
|
95
|
+
.filter((character) => character === "\t" || character.codePointAt(0) >= 32)
|
|
96
|
+
.join("")
|
|
97
|
+
.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cleanOutputLines(text) {
|
|
101
|
+
return text
|
|
102
|
+
.split(/\r?\n/u)
|
|
103
|
+
.map(cleanOutputLine)
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function summarizeOutputBytes(bytes) {
|
|
108
|
+
if (bytes.byteLength <= OUTPUT_SUMMARY_BYTES) {
|
|
109
|
+
const lines = cleanOutputLines(bytes.toString("utf8"));
|
|
110
|
+
return {
|
|
111
|
+
headLines: lines.slice(0, OUTPUT_SUMMARY_LINES),
|
|
112
|
+
tailLines: lines.slice(-OUTPUT_SUMMARY_LINES),
|
|
113
|
+
truncated: lines.length > OUTPUT_SUMMARY_LINES * 2,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const windowBytes = Math.floor(OUTPUT_SUMMARY_BYTES / 2);
|
|
118
|
+
let headText = bytes.subarray(0, windowBytes).toString("utf8");
|
|
119
|
+
const lastHeadBreak = headText.lastIndexOf("\n");
|
|
120
|
+
if (lastHeadBreak >= 0) headText = headText.slice(0, lastHeadBreak + 1);
|
|
121
|
+
|
|
122
|
+
let tailText = bytes.subarray(bytes.byteLength - windowBytes).toString("utf8");
|
|
123
|
+
const firstTailBreak = tailText.indexOf("\n");
|
|
124
|
+
if (firstTailBreak >= 0) tailText = tailText.slice(firstTailBreak + 1);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
headLines: cleanOutputLines(headText).slice(0, OUTPUT_SUMMARY_LINES),
|
|
128
|
+
tailLines: cleanOutputLines(tailText).slice(-OUTPUT_SUMMARY_LINES),
|
|
129
|
+
truncated: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
90
133
|
function outputArtifact(workspaceRoot, filePath) {
|
|
91
134
|
const bytes = readFileSync(filePath);
|
|
92
135
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
136
|
+
artifact: {
|
|
137
|
+
path: path.relative(realpathSync(workspaceRoot), filePath).split(path.sep).join("/"),
|
|
138
|
+
sha256: sha256Bytes(bytes),
|
|
139
|
+
sizeBytes: bytes.byteLength,
|
|
140
|
+
},
|
|
141
|
+
summary: summarizeOutputBytes(bytes),
|
|
96
142
|
};
|
|
97
143
|
}
|
|
98
144
|
|
|
@@ -115,15 +161,66 @@ function validateQuickReceiptStore(workspaceRoot, store) {
|
|
|
115
161
|
assertStorePath(root, store.receiptRoot, store.receiptRootIdentity, "directory");
|
|
116
162
|
assertStorePath(root, store.stdoutPath, store.stdoutIdentity, "file");
|
|
117
163
|
assertStorePath(root, store.stderrPath, store.stderrIdentity, "file");
|
|
164
|
+
if (store.stdinPath !== undefined) {
|
|
165
|
+
assertStorePath(root, store.stdinPath, store.stdinIdentity, "file");
|
|
166
|
+
const bytes = readFileSync(store.stdinPath);
|
|
167
|
+
if (
|
|
168
|
+
bytes.byteLength !== store.stdinArtifact.sizeBytes
|
|
169
|
+
|| sha256Bytes(bytes) !== store.stdinArtifact.sha256
|
|
170
|
+
) {
|
|
171
|
+
throw new Error(`Quick receipt store stdin material changed: ${store.stdinPath}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function persistQuickInputArtifact({ workspaceRoot, store, bytes }) {
|
|
177
|
+
if (!Buffer.isBuffer(bytes)) throw new Error("Quick stdin material must be bytes.");
|
|
178
|
+
validateQuickReceiptStore(workspaceRoot, store);
|
|
179
|
+
const stdinPath = path.join(store.receiptRoot, "stdin.bin");
|
|
180
|
+
writeFileSync(stdinPath, bytes, { flag: "wx", mode: 0o600 });
|
|
181
|
+
const stat = lstatSync(stdinPath);
|
|
182
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
183
|
+
throw new Error(`Quick receipt store stdin material is not trusted: ${stdinPath}`);
|
|
184
|
+
}
|
|
185
|
+
const root = realpathSync(workspaceRoot);
|
|
186
|
+
const resolved = realpathSync(stdinPath);
|
|
187
|
+
if (!withinRoot(root, resolved)) {
|
|
188
|
+
throw new Error(`Quick receipt store stdin material escapes the workspace: ${stdinPath}`);
|
|
189
|
+
}
|
|
190
|
+
store.stdinPath = stdinPath;
|
|
191
|
+
store.stdinIdentity = { dev: stat.dev, ino: stat.ino };
|
|
192
|
+
store.stdinArtifact = {
|
|
193
|
+
path: path.relative(root, resolved).split(path.sep).join("/"),
|
|
194
|
+
sha256: sha256Bytes(bytes),
|
|
195
|
+
sizeBytes: bytes.byteLength,
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
artifact: store.stdinArtifact,
|
|
199
|
+
inputPath: stdinPath,
|
|
200
|
+
};
|
|
118
201
|
}
|
|
119
202
|
|
|
120
203
|
export function persistQuickReceipt({ workspaceRoot, store, receipt }) {
|
|
121
204
|
validateQuickReceiptStore(workspaceRoot, store);
|
|
205
|
+
const receiptStdin = receipt.inputArtifacts?.stdin;
|
|
206
|
+
if (
|
|
207
|
+
(store.stdinArtifact === undefined) !== (receiptStdin === undefined)
|
|
208
|
+
|| store.stdinArtifact !== undefined
|
|
209
|
+
&& (
|
|
210
|
+
receiptStdin.path !== store.stdinArtifact.path
|
|
211
|
+
|| receiptStdin.sha256 !== store.stdinArtifact.sha256
|
|
212
|
+
|| receiptStdin.sizeBytes !== store.stdinArtifact.sizeBytes
|
|
213
|
+
)
|
|
214
|
+
) {
|
|
215
|
+
throw new Error("Quick receipt stdin binding does not match the persisted material.");
|
|
216
|
+
}
|
|
217
|
+
const stdout = outputArtifact(workspaceRoot, store.stdoutPath);
|
|
218
|
+
const stderr = outputArtifact(workspaceRoot, store.stderrPath);
|
|
122
219
|
const complete = {
|
|
123
220
|
...receipt,
|
|
124
221
|
outputArtifacts: {
|
|
125
|
-
stdout:
|
|
126
|
-
stderr:
|
|
222
|
+
stdout: stdout.artifact,
|
|
223
|
+
stderr: stderr.artifact,
|
|
127
224
|
},
|
|
128
225
|
};
|
|
129
226
|
atomicWriteJson(store.receiptPath, complete);
|
|
@@ -131,6 +228,14 @@ export function persistQuickReceipt({ workspaceRoot, store, receipt }) {
|
|
|
131
228
|
receipt: complete,
|
|
132
229
|
receiptPath: store.receiptPath,
|
|
133
230
|
receiptSha256: sha256Bytes(readFileSync(store.receiptPath)),
|
|
231
|
+
outputSummary: {
|
|
232
|
+
stdoutHead: stdout.summary.headLines,
|
|
233
|
+
stdoutTail: stdout.summary.tailLines,
|
|
234
|
+
stderrHead: stderr.summary.headLines,
|
|
235
|
+
stderrTail: stderr.summary.tailLines,
|
|
236
|
+
stdoutTruncated: stdout.summary.truncated,
|
|
237
|
+
stderrTruncated: stderr.summary.truncated,
|
|
238
|
+
},
|
|
134
239
|
};
|
|
135
240
|
}
|
|
136
241
|
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
constants,
|
|
5
|
+
openSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
3
9
|
|
|
10
|
+
import { readFileBytesBounded } from "../../packages/attest/src/formal.mjs";
|
|
4
11
|
import { currentCoreIdentity } from "./core-contract.mjs";
|
|
5
12
|
import {
|
|
6
13
|
closeQuickOutputFiles,
|
|
7
14
|
createQuickReceiptStore,
|
|
15
|
+
persistQuickInputArtifact,
|
|
8
16
|
persistQuickReceipt,
|
|
9
17
|
} from "./quick-receipt.mjs";
|
|
10
18
|
import { captureWorkspaceSnapshot } from "./quick-workspace-snapshot.mjs";
|
|
@@ -17,15 +25,31 @@ function quickAttestCommand(workspaceRoot, receiptPath) {
|
|
|
17
25
|
return `owlrunkit quick-attest --workspace ${shellQuote(workspaceRoot)} --receipt ${shellQuote(receiptPath)}`;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
|
-
function runExactCommand({ executable, argv, cwd, stdoutFd, stderrFd }) {
|
|
28
|
+
function runExactCommand({ executable, argv, cwd, stdinPath, stdoutFd, stderrFd }) {
|
|
21
29
|
return new Promise((resolve) => {
|
|
22
30
|
let settled = false;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
let stdinFd;
|
|
32
|
+
let child;
|
|
33
|
+
try {
|
|
34
|
+
stdinFd = stdinPath === undefined
|
|
35
|
+
? undefined
|
|
36
|
+
: openSync(stdinPath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
37
|
+
child = spawn(executable, argv, {
|
|
38
|
+
cwd,
|
|
39
|
+
shell: false,
|
|
40
|
+
stdio: [stdinFd ?? "ignore", stdoutFd, stderrFd],
|
|
41
|
+
env: process.env,
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (stdinFd !== undefined) closeSync(stdinFd);
|
|
45
|
+
resolve({
|
|
46
|
+
exitCode: null,
|
|
47
|
+
signal: null,
|
|
48
|
+
launchError: error instanceof Error ? error.message : String(error),
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (stdinFd !== undefined) closeSync(stdinFd);
|
|
29
53
|
child.once("error", (error) => {
|
|
30
54
|
if (settled) return;
|
|
31
55
|
settled = true;
|
|
@@ -39,7 +63,13 @@ function runExactCommand({ executable, argv, cwd, stdoutFd, stderrFd }) {
|
|
|
39
63
|
});
|
|
40
64
|
}
|
|
41
65
|
|
|
42
|
-
|
|
66
|
+
function inputArtifactResult(stdinArtifact) {
|
|
67
|
+
return stdinArtifact === null
|
|
68
|
+
? {}
|
|
69
|
+
: { inputArtifacts: { stdin: stdinArtifact } };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function runQuickVerification({ workspaceRoot, commandArgv, stdinFile }) {
|
|
43
73
|
if (!Array.isArray(commandArgv) || commandArgv.length === 0 || !commandArgv.every((value) => typeof value === "string")) {
|
|
44
74
|
return {
|
|
45
75
|
status: "quick_verification_input_invalid",
|
|
@@ -60,12 +90,49 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
60
90
|
};
|
|
61
91
|
}
|
|
62
92
|
|
|
93
|
+
let stdinBytes;
|
|
94
|
+
if (stdinFile !== undefined) {
|
|
95
|
+
if (typeof stdinFile !== "string" || stdinFile.length === 0) {
|
|
96
|
+
return {
|
|
97
|
+
status: "quick_verification_input_invalid",
|
|
98
|
+
exitCode: 3,
|
|
99
|
+
issues: ["--stdin-file requires a non-empty regular file path."],
|
|
100
|
+
authorizationGranted: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const selected = path.isAbsolute(stdinFile)
|
|
105
|
+
? stdinFile
|
|
106
|
+
: path.resolve(root, stdinFile);
|
|
107
|
+
stdinBytes = readFileBytesBounded(selected).bytes;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return {
|
|
110
|
+
status: "quick_verification_input_invalid",
|
|
111
|
+
exitCode: 3,
|
|
112
|
+
issues: [`Quick stdin file is invalid: ${error instanceof Error ? error.message : String(error)}`],
|
|
113
|
+
authorizationGranted: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
63
118
|
let workspaceBefore;
|
|
64
119
|
let store;
|
|
120
|
+
let stdinArtifact = null;
|
|
121
|
+
let persistedStdinPath;
|
|
65
122
|
try {
|
|
66
123
|
workspaceBefore = captureWorkspaceSnapshot(root);
|
|
67
124
|
store = createQuickReceiptStore(root);
|
|
125
|
+
if (stdinBytes !== undefined) {
|
|
126
|
+
const persistedInput = persistQuickInputArtifact({
|
|
127
|
+
workspaceRoot: root,
|
|
128
|
+
store,
|
|
129
|
+
bytes: stdinBytes,
|
|
130
|
+
});
|
|
131
|
+
stdinArtifact = persistedInput.artifact;
|
|
132
|
+
persistedStdinPath = persistedInput.inputPath;
|
|
133
|
+
}
|
|
68
134
|
} catch (error) {
|
|
135
|
+
if (store !== undefined) closeQuickOutputFiles(store);
|
|
69
136
|
return {
|
|
70
137
|
status: "quick_verification_input_invalid",
|
|
71
138
|
exitCode: 3,
|
|
@@ -80,6 +147,7 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
80
147
|
executable,
|
|
81
148
|
argv,
|
|
82
149
|
cwd: root,
|
|
150
|
+
stdinPath: persistedStdinPath,
|
|
83
151
|
stdoutFd: store.stdoutFd,
|
|
84
152
|
stderrFd: store.stderrFd,
|
|
85
153
|
});
|
|
@@ -101,7 +169,7 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
101
169
|
}
|
|
102
170
|
|
|
103
171
|
const sourceMutated = workspaceBefore.sourceFingerprint !== workspaceAfter.sourceFingerprint;
|
|
104
|
-
const issueCodes = [
|
|
172
|
+
const issueCodes = [];
|
|
105
173
|
if (sourceMutated) issueCodes.push("source_mutated_during_verification");
|
|
106
174
|
let persisted;
|
|
107
175
|
try {
|
|
@@ -109,7 +177,9 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
109
177
|
workspaceRoot: root,
|
|
110
178
|
store,
|
|
111
179
|
receipt: {
|
|
112
|
-
schemaVersion:
|
|
180
|
+
schemaVersion: stdinArtifact === null
|
|
181
|
+
? "OwlCodaQuickVerificationReceiptV1"
|
|
182
|
+
: "OwlCodaQuickVerificationReceiptV2",
|
|
113
183
|
receiptId: store.receiptId,
|
|
114
184
|
assurance: "captured_verification",
|
|
115
185
|
authorizationGranted: false,
|
|
@@ -124,6 +194,9 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
124
194
|
argv,
|
|
125
195
|
cwd: root,
|
|
126
196
|
},
|
|
197
|
+
...(stdinArtifact === null
|
|
198
|
+
? {}
|
|
199
|
+
: { inputArtifacts: { stdin: stdinArtifact } }),
|
|
127
200
|
verificationContext: {
|
|
128
201
|
platform: process.platform,
|
|
129
202
|
architecture: process.arch,
|
|
@@ -154,12 +227,15 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
154
227
|
? "invalidated_by_command_source_mutation"
|
|
155
228
|
: "source_unchanged",
|
|
156
229
|
issueCodes: [...issueCodes, "quick_receipt_store_invalid"],
|
|
230
|
+
...inputArtifactResult(stdinArtifact),
|
|
157
231
|
issues: [error instanceof Error ? error.message : String(error)],
|
|
158
232
|
nextAllowedAction: "repair_quick_receipt_store_and_rerun",
|
|
159
233
|
authorizationGranted: false,
|
|
160
234
|
};
|
|
161
235
|
}
|
|
162
236
|
|
|
237
|
+
const { outputSummary } = persisted;
|
|
238
|
+
|
|
163
239
|
if (sourceMutated) {
|
|
164
240
|
const attestCommand = quickAttestCommand(root, persisted.receiptPath);
|
|
165
241
|
return {
|
|
@@ -171,6 +247,8 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
171
247
|
commandExitCode: execution.exitCode,
|
|
172
248
|
mutationDecision: "invalidated_by_command_source_mutation",
|
|
173
249
|
issueCodes,
|
|
250
|
+
...inputArtifactResult(stdinArtifact),
|
|
251
|
+
outputSummary,
|
|
174
252
|
attestCommand,
|
|
175
253
|
issues: [],
|
|
176
254
|
nextAllowedAction: "restore_source_or_run_a_new_quick_verification",
|
|
@@ -188,6 +266,8 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
188
266
|
commandExitCode: execution.exitCode,
|
|
189
267
|
mutationDecision: "source_unchanged",
|
|
190
268
|
issueCodes,
|
|
269
|
+
...inputArtifactResult(stdinArtifact),
|
|
270
|
+
outputSummary,
|
|
191
271
|
attestCommand,
|
|
192
272
|
issues: execution.launchError ? [execution.launchError] : [],
|
|
193
273
|
nextAllowedAction: "inspect_output_and_rerun",
|
|
@@ -204,6 +284,8 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
204
284
|
commandExitCode: 0,
|
|
205
285
|
mutationDecision: "source_unchanged",
|
|
206
286
|
issueCodes,
|
|
287
|
+
...inputArtifactResult(stdinArtifact),
|
|
288
|
+
outputSummary,
|
|
207
289
|
attestCommand,
|
|
208
290
|
issues: [],
|
|
209
291
|
nextAllowedAction: attestCommand,
|
|
@@ -100,7 +100,10 @@ import {
|
|
|
100
100
|
replaceFleetRegistry,
|
|
101
101
|
rollbackFleetRegistry,
|
|
102
102
|
} from "./fleet-discovery.mjs";
|
|
103
|
-
import {
|
|
103
|
+
import {
|
|
104
|
+
RUNKIT_RISK_CATEGORIES,
|
|
105
|
+
routeRunKitAssuranceV1,
|
|
106
|
+
} from "./assurance-router.mjs";
|
|
104
107
|
import {
|
|
105
108
|
formatUsageModeHumanV1,
|
|
106
109
|
recommendUsageModeV1,
|
|
@@ -155,6 +158,7 @@ import {
|
|
|
155
158
|
integrateTeamProjectV1,
|
|
156
159
|
openTeamProjectDecisionV1,
|
|
157
160
|
readTeamProjectStatusV1,
|
|
161
|
+
rejectAndReturnTeamProjectV1,
|
|
158
162
|
resolveTeamProjectDecisionV1,
|
|
159
163
|
} from "./team-project.mjs";
|
|
160
164
|
import {
|
|
@@ -197,7 +201,7 @@ Fleet and status
|
|
|
197
201
|
owlrunkit fleet discover; owlrunkit status --workspace .
|
|
198
202
|
|
|
199
203
|
Agent team projects
|
|
200
|
-
owlrunkit project <init|assign|checkpoint|handoff|decision|verification|capture|status|takeover|successor> [options]
|
|
204
|
+
owlrunkit project <init|assign|checkpoint|handoff|reject-and-return|decision|verification|capture|status|takeover|successor> [options]
|
|
201
205
|
Durable Agent work, dependencies, decisions, gates, progress, and handoffs.
|
|
202
206
|
Core successor
|
|
203
207
|
owlrunkit core-successor <plan|apply|resume> --workspace . [...]
|
|
@@ -244,7 +248,7 @@ read-only and grant no Git, release, deployment, production, or business authori
|
|
|
244
248
|
[--irreversible] [--rollback-uncertain]
|
|
245
249
|
[--permission-sensitive] [--security-sensitive] [--funds-at-risk]
|
|
246
250
|
[--schema-or-data-migration] [--formal-acceptance]
|
|
247
|
-
[--risk-category
|
|
251
|
+
[--risk-category <${RUNKIT_RISK_CATEGORIES.join("|")}>] [--json]
|
|
248
252
|
|
|
249
253
|
Returns one user mode plus the independent continuity and assurance selections.
|
|
250
254
|
Persisted mode preferences are not part of this release.
|
|
@@ -259,6 +263,22 @@ Reads the current mode preference source without creating project state.
|
|
|
259
263
|
|
|
260
264
|
Attests one Quick Receipt against its bound materials and current workspace.
|
|
261
265
|
It never grants repository or business authority.
|
|
266
|
+
`,
|
|
267
|
+
"quick-verify": `Usage:
|
|
268
|
+
owlrunkit quick-verify --workspace <directory> [--stdin-file <file>] [--attest] [--json]
|
|
269
|
+
-- <executable> [args...]
|
|
270
|
+
|
|
271
|
+
Runs one exact command, saves source-bound stdout/stderr and a Quick receipt,
|
|
272
|
+
and returns a bounded head/tail output summary. --stdin-file copies and hash-binds
|
|
273
|
+
up to 1 MiB of exact stdin bytes in the receipt store. --attest immediately
|
|
274
|
+
attests the saved receipt without merging Quick and attestation semantics.
|
|
275
|
+
Quick evidence is not Formal acceptance and grants no repository or business authority.
|
|
276
|
+
`,
|
|
277
|
+
"quick-metrics": `Usage:
|
|
278
|
+
owlrunkit quick-metrics --workspace <directory> --local [--verbose] [--json]
|
|
279
|
+
|
|
280
|
+
Returns aggregate local-only Quick counts with no telemetry or network access.
|
|
281
|
+
--verbose additionally lists the receipt paths used for the aggregation.
|
|
262
282
|
`,
|
|
263
283
|
fleet: `Usage:
|
|
264
284
|
owlrunkit fleet register-root --fleet-root <directory>
|
|
@@ -409,6 +429,9 @@ action without granting any authority.
|
|
|
409
429
|
owlrunkit project handoff --workspace <directory> --handoff-id <id> --at <ISO-UTC>
|
|
410
430
|
--assignment-id <current> --work-item <id> --from-agent <id> --to-agent <id>
|
|
411
431
|
--summary <text> --next <text> --evidence <ref> [--evidence <ref> ...] [--json]
|
|
432
|
+
owlrunkit project reject-and-return --workspace <directory> --rework-id <id> --at <ISO-UTC>
|
|
433
|
+
--work-item <failed-id> --reviewer-agent <id> --to-agent <id>
|
|
434
|
+
--reason <text> --next <text> --evidence <ref> [--evidence <ref> ...] [--json]
|
|
412
435
|
owlrunkit project decision --workspace <directory> --open --decision-id <id> --at <ISO-UTC>
|
|
413
436
|
--title <text> --question <text> --owner-agent <id>
|
|
414
437
|
[--blocking-work-item <id> ...] --option <text> [--option <text> ...] [--json]
|
|
@@ -437,6 +460,27 @@ action without granting any authority.
|
|
|
437
460
|
Project status is derived from durable work items, Agent assignments,
|
|
438
461
|
dependencies, decisions, checkpoints, integration gates, and evidence refs.
|
|
439
462
|
No self-reported percentage is accepted.
|
|
463
|
+
`,
|
|
464
|
+
"project checkpoint": `Usage:
|
|
465
|
+
owlrunkit project checkpoint --workspace <directory> --checkpoint-id <id> --at <ISO-UTC>
|
|
466
|
+
--assignment-id <current> --work-item <id>
|
|
467
|
+
--state <active|waiting_dependency|waiting_decision|verifying|ready_to_integrate|completed|failed>
|
|
468
|
+
--summary <text> [--completed-units <nonnegative-int>]
|
|
469
|
+
[--evidence <ref> ...] [--blocker <ref> ...] [--decision <id> ...]
|
|
470
|
+
[--next <text>] [--source-fingerprint <sha256>] [--json]
|
|
471
|
+
|
|
472
|
+
Measured WorkItems require --completed-units on every checkpoint. A completed
|
|
473
|
+
checkpoint must use the WorkItem's declared total. Example:
|
|
474
|
+
owlrunkit project checkpoint --workspace . --checkpoint-id checkpoint-x --at 2026-08-11T00:00:00.000Z --assignment-id assign-x --work-item x --state completed --summary 'Completed x.' --completed-units 5 --evidence receipt:quick
|
|
475
|
+
`,
|
|
476
|
+
"project reject-and-return": `Usage:
|
|
477
|
+
owlrunkit project reject-and-return --workspace <directory> --rework-id <id> --at <ISO-UTC>
|
|
478
|
+
--work-item <failed-id> --reviewer-agent <id> --to-agent <id>
|
|
479
|
+
--reason <text> --next <text> --evidence <ref> [--evidence <ref> ...] [--json]
|
|
480
|
+
|
|
481
|
+
Atomically records the rejection and opens a new assigned rework attempt. The
|
|
482
|
+
failed attempt remains immutable and evidence-linked. This action does not
|
|
483
|
+
grant Git, release, deployment, production, or business authority.
|
|
440
484
|
`,
|
|
441
485
|
"project status": `Usage:
|
|
442
486
|
owlrunkit project status --workspace <directory> [--json]
|
|
@@ -465,6 +509,25 @@ function withHumanOutput(result, humanOutput) {
|
|
|
465
509
|
return result;
|
|
466
510
|
}
|
|
467
511
|
|
|
512
|
+
function shellCommandToken(value) {
|
|
513
|
+
const text = String(value);
|
|
514
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/u.test(text)
|
|
515
|
+
? text
|
|
516
|
+
: `'${text.replaceAll("'", `'"'"'`)}'`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function correctedCheckpointCommand(argv, completedUnits) {
|
|
520
|
+
const corrected = [...argv];
|
|
521
|
+
const jsonIndex = corrected.indexOf("--json");
|
|
522
|
+
corrected.splice(
|
|
523
|
+
jsonIndex === -1 ? corrected.length : jsonIndex,
|
|
524
|
+
0,
|
|
525
|
+
"--completed-units",
|
|
526
|
+
String(completedUnits),
|
|
527
|
+
);
|
|
528
|
+
return `owlrunkit ${corrected.map(shellCommandToken).join(" ")}`;
|
|
529
|
+
}
|
|
530
|
+
|
|
468
531
|
function formatTeamProjectAssignmentHuman(result) {
|
|
469
532
|
const disposition = result.resumed ? "Resumed" : "Assigned";
|
|
470
533
|
const nextAction = result.nextAction === null
|
|
@@ -495,6 +558,20 @@ function formatTeamProjectHandoffHuman(result) {
|
|
|
495
558
|
].join("\n");
|
|
496
559
|
}
|
|
497
560
|
|
|
561
|
+
function formatTeamProjectReworkHuman(result) {
|
|
562
|
+
const disposition = result.resumed ? "Resumed" : "Returned";
|
|
563
|
+
const nextAction = result.nextAction === null
|
|
564
|
+
? "none"
|
|
565
|
+
: String(result.nextAction).replace(/\s+/gu, " ").trim();
|
|
566
|
+
return [
|
|
567
|
+
`${disposition} failed WorkItem ${result.workItemId} to Agent ${result.toAgentId} for rework attempt ${result.reworkAttempt}.`,
|
|
568
|
+
`Failed checkpoint preserved: ${result.failedCheckpointEventId}`,
|
|
569
|
+
`Next: ${nextAction}`,
|
|
570
|
+
`Rework assignment: ${result.assignmentId}`,
|
|
571
|
+
"",
|
|
572
|
+
].join("\n");
|
|
573
|
+
}
|
|
574
|
+
|
|
498
575
|
function formatTeamProjectDecisionHuman(result) {
|
|
499
576
|
const disposition = result.resumed
|
|
500
577
|
? "Resumed"
|
|
@@ -1882,6 +1959,8 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
1882
1959
|
? ["owned-path"]
|
|
1883
1960
|
: command === "project" && action === "handoff"
|
|
1884
1961
|
? ["evidence"]
|
|
1962
|
+
: command === "project" && action === "reject-and-return"
|
|
1963
|
+
? ["evidence"]
|
|
1885
1964
|
: command === "project" && action === "integrate"
|
|
1886
1965
|
? ["evidence"]
|
|
1887
1966
|
: command === "project" && action === "verification"
|
|
@@ -1919,7 +1998,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
1919
1998
|
? ["json", "open", "resolve"]
|
|
1920
1999
|
: command === "project" && action === "verification"
|
|
1921
2000
|
? ["json", "defer", "close"]
|
|
1922
|
-
: command === "project" && new Set(["assign", "handoff", "checkpoint", "integrate", "status", "takeover", "successor"]).has(action)
|
|
2001
|
+
: command === "project" && new Set(["assign", "handoff", "reject-and-return", "checkpoint", "integrate", "status", "takeover", "successor"]).has(action)
|
|
1923
2002
|
? ["json"]
|
|
1924
2003
|
: command === "doctor" || command === "adopt" || command === "status"
|
|
1925
2004
|
? ["json"]
|
|
@@ -1933,8 +2012,12 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
1933
2012
|
? ["adopt-orphan-success-receipts"]
|
|
1934
2013
|
: command === "deployment" && action === "execute"
|
|
1935
2014
|
? ["resume"]
|
|
1936
|
-
:
|
|
1937
|
-
? ["json", "
|
|
2015
|
+
: command === "quick-verify"
|
|
2016
|
+
? ["json", "attest"]
|
|
2017
|
+
: command === "quick-attest"
|
|
2018
|
+
? ["json"]
|
|
2019
|
+
: command === "quick-metrics"
|
|
2020
|
+
? ["json", "local", "verbose"]
|
|
1938
2021
|
: [],
|
|
1939
2022
|
});
|
|
1940
2023
|
if (command === "init") return await initialize(options);
|
|
@@ -2770,6 +2853,10 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
2770
2853
|
"workspace", "request", "handoff-id", "at", "assignment-id", "work-item",
|
|
2771
2854
|
"from-agent", "to-agent", "summary", "next", "evidence", "json",
|
|
2772
2855
|
],
|
|
2856
|
+
"reject-and-return": [
|
|
2857
|
+
"workspace", "rework-id", "at", "work-item", "reviewer-agent",
|
|
2858
|
+
"to-agent", "reason", "next", "evidence", "json",
|
|
2859
|
+
],
|
|
2773
2860
|
decision: [
|
|
2774
2861
|
"workspace", "request", "open", "resolve", "decision-id", "at", "title",
|
|
2775
2862
|
"question", "owner-agent", "blocking-work-item", "option", "resolution",
|
|
@@ -2877,6 +2964,22 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
2877
2964
|
? handedOff
|
|
2878
2965
|
: withHumanOutput(handedOff, formatTeamProjectHandoffHuman(handedOff));
|
|
2879
2966
|
}
|
|
2967
|
+
if (action === "reject-and-return") {
|
|
2968
|
+
const returned = rejectAndReturnTeamProjectV1({
|
|
2969
|
+
workspaceRoot: root,
|
|
2970
|
+
reworkId: requireOption(options, "rework-id"),
|
|
2971
|
+
occurredAt: requireOption(options, "at"),
|
|
2972
|
+
workItemId: requireOption(options, "work-item"),
|
|
2973
|
+
reviewerAgentId: requireOption(options, "reviewer-agent"),
|
|
2974
|
+
toAgentId: requireOption(options, "to-agent"),
|
|
2975
|
+
reason: requireOption(options, "reason"),
|
|
2976
|
+
nextAction: requireOption(options, "next"),
|
|
2977
|
+
evidenceRefs: options.evidence ?? [],
|
|
2978
|
+
});
|
|
2979
|
+
return options.json === true
|
|
2980
|
+
? returned
|
|
2981
|
+
: withHumanOutput(returned, formatTeamProjectReworkHuman(returned));
|
|
2982
|
+
}
|
|
2880
2983
|
if (action === "decision") {
|
|
2881
2984
|
const directDecisionFields = [
|
|
2882
2985
|
"open",
|
|
@@ -3073,24 +3176,49 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
3073
3176
|
event: requestedEvent,
|
|
3074
3177
|
});
|
|
3075
3178
|
}
|
|
3179
|
+
const checkpointId = requireOption(options, "checkpoint-id");
|
|
3180
|
+
const occurredAt = requireOption(options, "at");
|
|
3181
|
+
const assignmentId = requireOption(options, "assignment-id");
|
|
3182
|
+
const workItemId = requireOption(options, "work-item");
|
|
3183
|
+
const state = requireOption(options, "state");
|
|
3184
|
+
const summary = requireOption(options, "summary");
|
|
3076
3185
|
const completedUnits = options["completed-units"] === undefined
|
|
3077
3186
|
? undefined
|
|
3078
3187
|
: parseCanonicalNonnegativeSafeInteger(options["completed-units"], "completed-units");
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3188
|
+
let checkpointed;
|
|
3189
|
+
try {
|
|
3190
|
+
checkpointed = checkpointTeamProjectV1({
|
|
3191
|
+
workspaceRoot: root,
|
|
3192
|
+
checkpointId,
|
|
3193
|
+
occurredAt,
|
|
3194
|
+
assignmentId,
|
|
3195
|
+
workItemId,
|
|
3196
|
+
state,
|
|
3197
|
+
summary,
|
|
3198
|
+
completedUnits,
|
|
3199
|
+
evidenceRefs: options.evidence ?? [],
|
|
3200
|
+
blockerRefs: options.blocker ?? [],
|
|
3201
|
+
decisionRefs: options.decision ?? [],
|
|
3202
|
+
nextAction: options.next ?? null,
|
|
3203
|
+
sourceFingerprint: options["source-fingerprint"],
|
|
3204
|
+
});
|
|
3205
|
+
} catch (error) {
|
|
3206
|
+
if (error instanceof Error
|
|
3207
|
+
&& error.message === "Measured work item checkpoint requires completedUnits.") {
|
|
3208
|
+
const projectStatus = readTeamProjectStatusV1({ workspaceRoot: root });
|
|
3209
|
+
const workItem = projectStatus.workItems.find((row) => row.workItemId === workItemId);
|
|
3210
|
+
const suggestedUnits = state === "completed"
|
|
3211
|
+
? workItem?.progress.total
|
|
3212
|
+
: workItem?.progress.completed;
|
|
3213
|
+
if (Number.isSafeInteger(suggestedUnits)) {
|
|
3214
|
+
throw new Error(
|
|
3215
|
+
"Measured work item checkpoint requires --completed-units. "
|
|
3216
|
+
+ `Corrected command: ${correctedCheckpointCommand(argv, suggestedUnits)}`,
|
|
3217
|
+
);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
throw error;
|
|
3221
|
+
}
|
|
3094
3222
|
return options.json === true
|
|
3095
3223
|
? checkpointed
|
|
3096
3224
|
: withHumanOutput(checkpointed, formatTeamProjectCheckpointHuman(checkpointed));
|
|
@@ -3162,7 +3290,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
3162
3290
|
? successor
|
|
3163
3291
|
: withHumanOutput(successor, formatTeamProjectSuccessorHumanV1(successor));
|
|
3164
3292
|
}
|
|
3165
|
-
throw new Error("Usage: owlrunkit project <init|assign|checkpoint|handoff|decision|verification|integrate|capture|status|takeover|successor> [options]");
|
|
3293
|
+
throw new Error("Usage: owlrunkit project <init|assign|checkpoint|handoff|reject-and-return|decision|verification|integrate|capture|status|takeover|successor> [options]");
|
|
3166
3294
|
}
|
|
3167
3295
|
if (command === "profiles") {
|
|
3168
3296
|
const root = requireOption(options, "workspace");
|
|
@@ -3248,10 +3376,28 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
3248
3376
|
if (commandArgv.length === 0) {
|
|
3249
3377
|
throw new Error("Usage: runkit-cli.mjs quick-verify --workspace <path> -- <executable> [args...]");
|
|
3250
3378
|
}
|
|
3251
|
-
|
|
3379
|
+
const verification = await runQuickVerification({
|
|
3252
3380
|
workspaceRoot: workspace(options),
|
|
3253
3381
|
commandArgv,
|
|
3382
|
+
stdinFile: options["stdin-file"],
|
|
3383
|
+
});
|
|
3384
|
+
if (options.attest !== true || typeof verification.receiptPath !== "string") {
|
|
3385
|
+
return verification;
|
|
3386
|
+
}
|
|
3387
|
+
const details = attestQuickReceiptDetails({
|
|
3388
|
+
receiptPath: verification.receiptPath,
|
|
3389
|
+
workspaceRoot: workspace(options),
|
|
3254
3390
|
});
|
|
3391
|
+
return {
|
|
3392
|
+
...verification,
|
|
3393
|
+
exitCode: details.exitCode === 0 ? verification.exitCode : details.exitCode,
|
|
3394
|
+
attestationDecision: details.attestation.decision,
|
|
3395
|
+
attestation: details.attestation,
|
|
3396
|
+
nextAllowedAction: details.attestation.decision === "GO"
|
|
3397
|
+
? "consume_attestation"
|
|
3398
|
+
: "inspect_attestation_issues",
|
|
3399
|
+
authorizationGranted: false,
|
|
3400
|
+
};
|
|
3255
3401
|
}
|
|
3256
3402
|
if (command === "quick-attest") {
|
|
3257
3403
|
const details = attestQuickReceiptDetails({
|
|
@@ -3276,7 +3422,9 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
3276
3422
|
if (options.local !== true) {
|
|
3277
3423
|
throw new Error("Quick metrics requires --local; network telemetry is not available.");
|
|
3278
3424
|
}
|
|
3279
|
-
const local = readLocalQuickMetrics(workspace(options)
|
|
3425
|
+
const local = readLocalQuickMetrics(workspace(options), {
|
|
3426
|
+
verbose: options.verbose === true,
|
|
3427
|
+
});
|
|
3280
3428
|
return {
|
|
3281
3429
|
...local,
|
|
3282
3430
|
metrics: local.quick,
|