owlrunkit 0.18.1 → 0.18.2

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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.2
4
+
5
+ - Makes `project checkpoint --help` action-specific and returns a complete,
6
+ shell-safe correction command when a measured checkpoint omits
7
+ `--completed-units`.
8
+ - Keeps symlinked workspace roots fail-closed while reporting the canonical
9
+ `--workspace` path, including the macOS `/tmp` to `/private/tmp` case.
10
+ - Returns a bounded stdout/stderr `outputSummary` at the top level of Quick
11
+ results without changing the receipt or weakening full-output binding.
12
+ - Retries one transient registry transport failure while retaining the existing
13
+ per-attempt timeout, and distinguishes DNS, connection, timeout, HTTP,
14
+ authentication, and missing-version failures.
15
+ - Aligns the packaged README with five-mode routing and existing profile
16
+ reconciliation instead of treating long-running, release, or production
17
+ labels as automatic Formal triggers.
18
+ - Adds action-specific `quick-verify --help`, lists canonical risk-category
19
+ values in mode help and validation errors, and marks formatting-only profile
20
+ reconcile plans with `normalizationOnly: true`.
21
+
3
22
  ## 0.18.1
4
23
 
5
24
  - Adds read-only `mode recommend` and `mode status` commands with five
package/README.md CHANGED
@@ -11,6 +11,10 @@ continuity and assurance independently. It keeps ordinary development light
11
11
  while preserving mandatory Formal routing for material safety and authority
12
12
  facts.
13
13
 
14
+ RunKit 0.18.2 is a focused dogfood patch for clearer CLI help, actionable
15
+ checkpoint and canonical-path errors, bounded Quick output summaries, typed
16
+ registry diagnostics, and explicit profile normalization previews.
17
+
14
18
  The 0.18 Project Driver is a bounded surface for new projects and controlled
15
19
  dogfood. Its product name does not claim to be a general long-running,
16
20
  multi-phase, or enterprise control plane. Version 0.18 adds one bounded
@@ -25,7 +29,7 @@ separate authority boundaries.
25
29
  ## Install
26
30
 
27
31
  ```bash
28
- npm install --save-exact owlrunkit@0.18.1
32
+ npm install --save-exact owlrunkit@0.18.2
29
33
  ```
30
34
 
31
35
  The `owlrunkit` bootstrap uses the project's exact locally bound CLI for
@@ -112,9 +116,10 @@ Normal users supply the one definition JSON to `init`, then use typed flags and
112
116
  values. They do not need to author request, event, receipt, or hash JSON. The
113
117
  legacy `--request <event.json>` path remains available for existing automation
114
118
  and compatibility. Multiple Agents do not make project coordination Formal by
115
- themselves. This flow does not make project coordination Formal by itself; the
116
- assurance lane follows the underlying high-risk integration,
117
- release, production, migration, destructive, or explicit Formal-acceptance boundary.
119
+ themselves. Project Driver continuity does not make project coordination Formal
120
+ by itself. The assurance lane follows actual writer and authority count,
121
+ rollback certainty, permissions, security, funds, irreversibility, schema/data
122
+ migration, and explicit acceptance contracts—not environment labels alone.
118
123
 
119
124
  `project verification --defer` is a small deferred-verification ledger, not a
120
125
  test scheduler. It records stable check IDs, why they were intentionally not
@@ -166,30 +171,52 @@ high-confidence candidates:
166
171
  ```bash
167
172
  owlrunkit profiles detect --workspace . --dry-run
168
173
  owlrunkit profiles detect --workspace . --apply
174
+ owlrunkit profiles reconcile --workspace . --dry-run
175
+ owlrunkit profiles reconcile --workspace . --apply
169
176
  owlrunkit profiles validate --workspace .
170
177
  ```
171
178
 
172
- `--apply` is create-only. It refuses ambiguous or low-confidence candidates,
173
- source drift, symlinks, and launchers that do not resolve to exact local files.
174
- It directly supports lock-bound npm plus exact project-installed pnpm, Yarn,
175
- and Bun launchers. A PATH-only package manager remains review-required. It
176
- never silently replaces an existing profile set.
179
+ `profiles detect --apply` is create-only. `profiles reconcile` previews or
180
+ atomically updates an existing profile set and leaves a migration receipt. Both
181
+ paths refuse ambiguous or low-confidence candidates, source drift, symlinks,
182
+ and launchers that do not resolve to exact local files. Detection directly
183
+ supports lock-bound npm plus exact project-installed pnpm, Yarn, and Bun
184
+ launchers. A PATH-only package manager remains review-required. A dry-run that
185
+ only canonicalizes JSON formatting reports `normalizationOnly: true` instead of
186
+ showing unexplained hash drift with an empty update list.
187
+
188
+ ## Choose auto, off, light, managed, or formal
189
+
190
+ Start with the human-first read-only recommendation:
191
+
192
+ ```bash
193
+ owlrunkit mode recommend --workspace .
194
+ ```
195
+
196
+ - **off / No RunKit** uses neither continuity nor assurance for analysis,
197
+ documentation, and disposable exploration that needs no reusable evidence.
198
+ - **light / Quick Verification** runs one exact low-risk, source-bound command.
199
+ - **managed** adds Project Driver continuity; each WorkItem may still use no
200
+ assurance, Quick, or Formal according to its own facts.
201
+ - **formal / Formal Delivery** is forced by multiple writers, permission or
202
+ security boundaries, funds, irreversible work, uncertain rollback,
203
+ schema/data migration, or an explicit Formal acceptance contract.
204
+ - **auto** derives the preset from supplied facts and is the default.
177
205
 
178
- ## Choose No RunKit, Quick, or Formal
206
+ Production, release, deployment, long duration, and interruption recovery do
207
+ not force Formal by themselves. Long or interrupted work primarily selects
208
+ Project Driver continuity. Risk containment permits a fresh lower-assurance
209
+ recommendation; escalation is not sticky.
179
210
 
180
- Route explicit risk facts before starting work:
211
+ Machine integrations may route an explicit risk-facts document directly:
181
212
 
182
213
  ```bash
183
214
  owlrunkit assurance route --workspace . \
184
215
  --request ./scripts/runkit-contract/templates/assurance-request.json
185
216
  ```
186
217
 
187
- - **No RunKit** is for work that needs neither a reusable receipt nor controlled
188
- writes.
189
- - **Quick Verification** is for one low-risk, source-bound command that needs a
190
- reusable receipt but no writer lease or Formal acceptance.
191
- - **Formal Delivery** is for multi-writer, long-running, migration, integration,
192
- release, production, funds, destructive, or formally accepted work.
218
+ `mode recommend --help` lists every accepted `--risk-category`; an invalid
219
+ category reports both the rejected value and the canonical allowed list.
193
220
 
194
221
  The router fails closed to Formal when required risk facts are incomplete.
195
222
  Quick receipts cannot become Formal acceptance, and Formal acceptance does not
@@ -201,6 +228,11 @@ Run Quick with:
201
228
  owlrunkit quick-verify --workspace . -- npm test
202
229
  ```
203
230
 
231
+ The top-level Quick result includes a bounded `outputSummary` with the last
232
+ meaningful stdout/stderr lines, so common summaries such as `155 passed` are
233
+ visible without opening `stdout.log`. The receipt still binds the complete
234
+ stdout and stderr artifacts; the summary is presentation only.
235
+
204
236
  ## Formal Delivery in three commands
205
237
 
206
238
  Given a Goal Contract and the paths this work item may write, the happy path is:
@@ -1,6 +1,6 @@
1
1
  # OwlCoda RunKit 使用模式选择:Sierac MES Dogfood 产品决议
2
2
 
3
- 状态:**0.18.1 本地候选,未发布、未安装、未激活**
3
+ 状态:**0.18.1 已发布;本文主体保留发布前产品决议,文末追加 0.18.2 dogfood 补丁候选**
4
4
  日期:2026-08-11
5
5
 
6
6
  ## 已验证现场事实
@@ -125,8 +125,9 @@ global gate 仍正确失效。
125
125
 
126
126
  ## 不能声称的内容
127
127
 
128
- - 这份文档和当前 worktree 不是已发布的 0.18.1,也不是 npm、Core、Skill 或任何
129
- 项目的升级完成证明。
128
+ - 本文主体形成时记录的是 0.18.1 发布前候选;0.18.1 后续发布事实以
129
+ `docs/PRODUCT-TRUTH.md` 和独立 release receipt 为准。本文本身不证明 npm、Core、
130
+ Skill 或任何项目已升级。
130
131
  - `mode recommend` 根据调用者提供的结构化事实路由;它不会自动发现所有业务、
131
132
  权限、资金或迁移风险。
132
133
  - RunKit 降低进度、证据和完成状态幻觉,不直接解决 Sierac MES 业务问答幻觉。
@@ -136,7 +137,30 @@ global gate 仍正确失效。
136
137
 
137
138
  ## 版本建议
138
139
 
139
- 本组变更适合形成 `owlrunkit@0.18.1` 小版本候选:它保持 0.18 的 Project Driver
140
+ 本组变更当时适合形成 `owlrunkit@0.18.1` 小版本候选:它保持 0.18 的 Project Driver
140
141
  和回执合同,新增只读 CLI,并修复 dogfood 验证出的行为缺陷。候选只有在聚焦测试、
141
142
  RunKit contract/package gate、Core 身份同步、pack 内容检查和安装 smoke 都通过后
142
143
  才具备发布评审资格;发布、项目升级与运行时激活仍是之后的独立动作。
144
+
145
+ ## 0.18.2 dogfood 补丁追加
146
+
147
+ 0.18.1 发布后,Sierac MES 与 OwlChen 的项目级使用继续证明 `managed + quick` 是普通
148
+ 跨会话源码 Wave 的合适默认,Formal 只应由真实高风险事实触发。OwlChen 已读回项目
149
+ 本地 0.18.1 的 doctor、profiles、Formal、Quick、metrics 与模式选择器主路径;进程级
150
+ 共享 Skill 因另一项目存在 active execution/lease 而停止升级,这是正确的安全边界,
151
+ 不是缺陷。
152
+
153
+ 这些后续 dogfood 暴露的缺口适合形成 0.18.2 patch,而不是新增平台能力:
154
+
155
+ - symlink workspace 拒绝时直接给出 canonical `--workspace`,包括 macOS
156
+ `/tmp` 到 `/private/tmp`;
157
+ - `project checkpoint --help` 和 measured WorkItem 缺参错误给出完整命令;
158
+ - Quick 顶层返回有界 stdout/stderr 摘要,同时完整日志继续由回执绑定;
159
+ - doctor 对 registry transport 做一次重试、每次保留原有超时上限,并区分 DNS、连接、
160
+ 超时、HTTP、认证和版本缺失;
161
+ - `quick-verify --help` 可用,risk category 的帮助与错误列出同一组规范值;
162
+ - profile 纯格式规范化计划显式返回 `normalizationOnly: true`;
163
+ - package README 与五模式、profile reconcile 的已实现真值一致。
164
+
165
+ 当前这些内容只是本地 `owlrunkit@0.18.2` 候选。它不代表 commit、tag、npm 发布、
166
+ 全局 Skill 更新、OwlChen/OwlFootball 迁移或任何业务授权已经发生。
@@ -7,10 +7,10 @@ description: Run receipt-backed, project-owned work with OwlCoda RunKit. Use whe
7
7
 
8
8
  Use the bundled deterministic Core to coordinate work through project artifacts rather than chat memory. Require Node.js 20 or later.
9
9
 
10
- Bundled release: standalone `owlrunkit@0.18.1`, bundling Contract `0.2` and
11
- Core `0.18.1` with manifest
12
- `sha256:349b4157d3d089462b172de1619ccd169b5c1669b9322e2580ac6ec4f8616b07`.
13
- The published `owlrunkit@0.18.0` package is the trusted prior for rollback;
10
+ Bundled release: standalone `owlrunkit@0.18.2`, bundling Contract `0.2` and
11
+ Core `0.18.2` with manifest
12
+ `sha256:abe17ff794b4019d8d476f09eb5d4105bae1070b1df0aab53e6c94c7ec8b65a6`.
13
+ The published `owlrunkit@0.18.1` package is the trusted prior for rollback;
14
14
  verify exact official npm registry provenance before adoption. The immutable
15
15
  rollback baseline for the prior release is `owlrunkit@0.16.1` with SHA-1
16
16
  `1bc119d24879bdd8959af16b364af55ca9d61ea9` and canonical npm tarball URL
@@ -2,9 +2,9 @@
2
2
  "schemaVersion": "OwlCodaRunKitConfigV2",
3
3
  "core": {
4
4
  "contractVersion": "0.2",
5
- "coreVersion": "0.18.1",
6
- "coreManifestSha256": "sha256:349b4157d3d089462b172de1619ccd169b5c1669b9322e2580ac6ec4f8616b07",
7
- "coreSourceRef": "artifact:sha256:349b4157d3d089462b172de1619ccd169b5c1669b9322e2580ac6ec4f8616b07"
5
+ "coreVersion": "0.18.2",
6
+ "coreManifestSha256": "sha256:abe17ff794b4019d8d476f09eb5d4105bae1070b1df0aab53e6c94c7ec8b65a6",
7
+ "coreSourceRef": "artifact:sha256:abe17ff794b4019d8d476f09eb5d4105bae1070b1df0aab53e6c94c7ec8b65a6"
8
8
  },
9
9
  "authorizationPolicy": "external_explicit_authority_required"
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "owlrunkit",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "Project Driver for cross-Agent project continuity and progression; execution, evidence, verification, and recovery are the underlying trust substrate.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "yeemio <owlclaw@163.com>",
@@ -36,6 +36,11 @@
36
36
  "sha256": "sha256:3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986"
37
37
  },
38
38
  "supportedProducerCoreIdentities": [
39
+ {
40
+ "contractVersion": "0.2",
41
+ "coreVersion": "0.18.2",
42
+ "coreManifestSha256": "sha256:abe17ff794b4019d8d476f09eb5d4105bae1070b1df0aab53e6c94c7ec8b65a6"
43
+ },
39
44
  {
40
45
  "contractVersion": "0.2",
41
46
  "coreVersion": "0.18.1",
@@ -18,6 +18,11 @@ import { captureWorkspaceSnapshot } from "./workspace-snapshot.mjs";
18
18
  export { validQuickReceiptShape } from "./quick-receipt-contract.mjs";
19
19
 
20
20
  export const SUPPORTED_QUICK_CORE_IDENTITIES = Object.freeze([
21
+ {
22
+ contractVersion: "0.2",
23
+ coreVersion: "0.18.2",
24
+ coreManifestSha256: "sha256:abe17ff794b4019d8d476f09eb5d4105bae1070b1df0aab53e6c94c7ec8b65a6",
25
+ },
21
26
  {
22
27
  contractVersion: "0.2",
23
28
  coreVersion: "0.18.1",
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "OwlCodaRunKitRegistryReleasePolicyV1",
3
3
  "status": "release_candidate_unpublished",
4
4
  "packageName": "owlrunkit",
5
- "candidateVersion": "0.18.1",
5
+ "candidateVersion": "0.18.2",
6
6
  "registry": "https://registry.npmjs.org",
7
7
  "registryRequiredBeforeAdoption": true,
8
8
  "exactVersionRequired": true,
@@ -23,10 +23,10 @@
23
23
  ],
24
24
  "currentPublishedRelease": {
25
25
  "packageName": "owlrunkit",
26
- "version": "0.18.0",
27
- "shasum": "ab2d9c9ed81f9dcccd4c1affa519cfca1cc32032",
28
- "integrity": "sha512-asi4CKofzjzQ9gGxnv6/NDCM6rZcMMqpwd4uRStfUUwDetFLYHEPDMBv1JGvYotdUwwoODxwdJ3KMGXQSYorgA==",
29
- "tarballUrl": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.18.0.tgz"
26
+ "version": "0.18.1",
27
+ "shasum": "6d35c15f90ed775c583356754b831856d1a3c0a6",
28
+ "integrity": "sha512-07wWnSfY6RUavcb+ZpUbZ1S0nBjN7/8VIFHyP6blOB+PIrDnnST/8f7CoLgADYpfwjZiF5gR9U6XFwXa0kgVrg==",
29
+ "tarballUrl": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.18.1.tgz"
30
30
  },
31
31
  "registryRelease": null,
32
32
  "registryEvidenceMustBeExternal": true,
package/sbom.spdx.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "owlrunkit-0.18.1",
6
- "documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.18.1",
5
+ "name": "owlrunkit-0.18.2",
6
+ "documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.18.2",
7
7
  "creationInfo": {
8
8
  "created": "2026-07-29T00:00:00Z",
9
9
  "creators": [
@@ -14,8 +14,8 @@
14
14
  {
15
15
  "name": "owlrunkit",
16
16
  "SPDXID": "SPDXRef-Package-OwlRunKit",
17
- "versionInfo": "0.18.1",
18
- "downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.18.1.tgz",
17
+ "versionInfo": "0.18.2",
18
+ "downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.18.2.tgz",
19
19
  "filesAnalyzed": false,
20
20
  "licenseConcluded": "GPL-3.0-or-later",
21
21
  "licenseDeclared": "GPL-3.0-or-later",
@@ -24,7 +24,7 @@
24
24
  {
25
25
  "referenceCategory": "PACKAGE-MANAGER",
26
26
  "referenceType": "purl",
27
- "referenceLocator": "pkg:npm/owlrunkit@0.18.1"
27
+ "referenceLocator": "pkg:npm/owlrunkit@0.18.2"
28
28
  }
29
29
  ]
30
30
  }
@@ -14,7 +14,7 @@ const BOOLEAN_FACTS = [
14
14
  "schemaOrDataMigration",
15
15
  ];
16
16
 
17
- const RISK_CATEGORIES = new Set([
17
+ export const RUNKIT_RISK_CATEGORIES = Object.freeze([
18
18
  "backtest",
19
19
  "deployment",
20
20
  "destructive",
@@ -26,6 +26,8 @@ const RISK_CATEGORIES = new Set([
26
26
  "release",
27
27
  ]);
28
28
 
29
+ const RISK_CATEGORIES = new Set(RUNKIT_RISK_CATEGORIES);
30
+
29
31
  const MATERIAL_RISK_CATEGORIES = new Set([
30
32
  "destructive",
31
33
  "foreign_write",
@@ -14,7 +14,7 @@ import { fileURLToPath } from "node:url";
14
14
  import { parseProjectControlState } from "./project-control-parser.mjs";
15
15
 
16
16
  export const CONTRACT_VERSION = "0.2";
17
- export const CORE_VERSION = "0.18.1";
17
+ export const CORE_VERSION = "0.18.2";
18
18
  export const RUNTIME_ROOT = ".owlcoda/runkit";
19
19
 
20
20
  const CORE_FILES = [
@@ -190,11 +190,16 @@ function parseJsonStrict(text) {
190
190
  export function resolveBoundedWorkspaceRoot(workspaceRoot) {
191
191
  const requested = path.resolve(workspaceRoot);
192
192
  const stat = lstatSync(requested);
193
- if (
194
- stat.isSymbolicLink()
195
- || !stat.isDirectory()
196
- || realpathSync(requested) !== requested
197
- ) {
193
+ const resolved = realpathSync(requested);
194
+ if ((stat.isSymbolicLink() || resolved !== requested)
195
+ && lstatSync(resolved).isDirectory()) {
196
+ const quoted = `'${resolved.replaceAll("'", `'"'"'`)}'`;
197
+ throw new Error(
198
+ "workspace root must be a real directory without symlink ancestors. "
199
+ + `Retry with --workspace ${quoted}.`,
200
+ );
201
+ }
202
+ if (!stat.isDirectory()) {
198
203
  throw new Error("workspace root must be a real directory without symlink ancestors");
199
204
  }
200
205
  return requested;
@@ -317,28 +322,51 @@ export function readLocalInstallBinding({ workspaceRoot } = {}) {
317
322
  };
318
323
  }
319
324
 
325
+ function registryTransportFailure(error) {
326
+ const code = error?.cause?.code ?? error?.code ?? null;
327
+ if (error?.name === "TimeoutError"
328
+ || error?.name === "AbortError"
329
+ || code === "ETIMEDOUT") {
330
+ return { issueCode: "registry_timeout", retryable: true };
331
+ }
332
+ if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
333
+ return { issueCode: "registry_dns_failed", retryable: true };
334
+ }
335
+ if (code === "ERR_INVALID_URL") {
336
+ return { issueCode: "registry_url_invalid", retryable: false };
337
+ }
338
+ return { issueCode: "registry_connection_failed", retryable: true };
339
+ }
340
+
320
341
  export async function readRegistryExact({
321
342
  registryUrl,
322
343
  packageName = PACKAGE_NAME,
323
344
  version = EXPECTED_VERSION,
324
345
  timeoutMs = 1_500,
346
+ fetchImpl = fetch,
325
347
  } = {}) {
326
348
  const base = registryUrl.replace(/\/+$/, "");
327
349
  const url = `${base}/${encodeURIComponent(packageName)}/${encodeURIComponent(version)}`;
328
350
  let response;
329
- try {
330
- response = await fetch(url, {
331
- headers: {
332
- accept: "application/json",
333
- },
334
- redirect: "manual",
335
- signal: AbortSignal.timeout(timeoutMs),
336
- });
337
- } catch {
338
- return {
339
- status: "unavailable",
340
- issueCodes: ["registry_dns_or_timeout"],
341
- };
351
+ for (let attempt = 0; attempt < 2; attempt += 1) {
352
+ try {
353
+ response = await fetchImpl(url, {
354
+ headers: {
355
+ accept: "application/json",
356
+ },
357
+ redirect: "manual",
358
+ signal: AbortSignal.timeout(timeoutMs),
359
+ });
360
+ break;
361
+ } catch (error) {
362
+ const failure = registryTransportFailure(error);
363
+ if (!failure.retryable || attempt === 1) {
364
+ return {
365
+ status: "unavailable",
366
+ issueCodes: [failure.issueCode],
367
+ };
368
+ }
369
+ }
342
370
  }
343
371
  if (response.status === 401 || response.status === 403) {
344
372
  return {
@@ -1511,10 +1511,14 @@ export function reconcileProfilesV2({
1511
1511
  }
1512
1512
  const proposedProfilesBytes = `${JSON.stringify(merged.proposedProfiles, null, 2)}\n`;
1513
1513
  const proposedProfilesSha256 = sha256(proposedProfilesBytes);
1514
+ const normalizationOnly = proposedProfilesSha256 !== currentProfilesSha256
1515
+ && merged.addedProfileIds.length === 0
1516
+ && merged.updatedProfiles.length === 0;
1514
1517
  const planBody = {
1515
1518
  detectionSha256: detection.detectionSha256,
1516
1519
  currentProfilesSha256,
1517
1520
  proposedProfilesSha256,
1521
+ normalizationOnly,
1518
1522
  addedProfileIds: merged.addedProfileIds,
1519
1523
  updatedProfiles: merged.updatedProfiles,
1520
1524
  };
@@ -18,6 +18,9 @@ const OFFICIAL_CLI_ENTRYPOINT =
18
18
  const EXACT_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/;
19
19
  const SHA256_REF = /^sha256:[a-f0-9]{64}$/;
20
20
  const TRUSTED_PRIOR_CORE_IDENTITIES = new Map([
21
+ ["0.18.1", new Set([
22
+ "sha256:349b4157d3d089462b172de1619ccd169b5c1669b9322e2580ac6ec4f8616b07",
23
+ ])],
21
24
  ["0.18.0", new Set([
22
25
  "sha256:72c481dbafe06a9c56cd940d74a3fe109a82b73a3c43d0bb54923ea6c584c04e",
23
26
  ])],
@@ -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,40 @@ 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 summarizeOutputBytes(bytes) {
101
+ const start = Math.max(0, bytes.byteLength - OUTPUT_SUMMARY_BYTES);
102
+ let text = bytes.subarray(start).toString("utf8");
103
+ if (start > 0) {
104
+ const firstLineBreak = text.indexOf("\n");
105
+ if (firstLineBreak >= 0) text = text.slice(firstLineBreak + 1);
106
+ }
107
+ const lines = text
108
+ .split(/\r?\n/u)
109
+ .map(cleanOutputLine)
110
+ .filter(Boolean);
111
+ return {
112
+ lines: lines.slice(-OUTPUT_SUMMARY_LINES),
113
+ truncated: start > 0 || lines.length > OUTPUT_SUMMARY_LINES,
114
+ };
115
+ }
116
+
90
117
  function outputArtifact(workspaceRoot, filePath) {
91
118
  const bytes = readFileSync(filePath);
92
119
  return {
93
- path: path.relative(realpathSync(workspaceRoot), filePath).split(path.sep).join("/"),
94
- sha256: sha256Bytes(bytes),
95
- sizeBytes: bytes.byteLength,
120
+ artifact: {
121
+ path: path.relative(realpathSync(workspaceRoot), filePath).split(path.sep).join("/"),
122
+ sha256: sha256Bytes(bytes),
123
+ sizeBytes: bytes.byteLength,
124
+ },
125
+ summary: summarizeOutputBytes(bytes),
96
126
  };
97
127
  }
98
128
 
@@ -119,11 +149,13 @@ function validateQuickReceiptStore(workspaceRoot, store) {
119
149
 
120
150
  export function persistQuickReceipt({ workspaceRoot, store, receipt }) {
121
151
  validateQuickReceiptStore(workspaceRoot, store);
152
+ const stdout = outputArtifact(workspaceRoot, store.stdoutPath);
153
+ const stderr = outputArtifact(workspaceRoot, store.stderrPath);
122
154
  const complete = {
123
155
  ...receipt,
124
156
  outputArtifacts: {
125
- stdout: outputArtifact(workspaceRoot, store.stdoutPath),
126
- stderr: outputArtifact(workspaceRoot, store.stderrPath),
157
+ stdout: stdout.artifact,
158
+ stderr: stderr.artifact,
127
159
  },
128
160
  };
129
161
  atomicWriteJson(store.receiptPath, complete);
@@ -131,6 +163,12 @@ export function persistQuickReceipt({ workspaceRoot, store, receipt }) {
131
163
  receipt: complete,
132
164
  receiptPath: store.receiptPath,
133
165
  receiptSha256: sha256Bytes(readFileSync(store.receiptPath)),
166
+ outputSummary: {
167
+ stdoutTail: stdout.summary.lines,
168
+ stderrTail: stderr.summary.lines,
169
+ stdoutTruncated: stdout.summary.truncated,
170
+ stderrTruncated: stderr.summary.truncated,
171
+ },
134
172
  };
135
173
  }
136
174
 
@@ -160,6 +160,8 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
160
160
  };
161
161
  }
162
162
 
163
+ const { outputSummary } = persisted;
164
+
163
165
  if (sourceMutated) {
164
166
  const attestCommand = quickAttestCommand(root, persisted.receiptPath);
165
167
  return {
@@ -171,6 +173,7 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
171
173
  commandExitCode: execution.exitCode,
172
174
  mutationDecision: "invalidated_by_command_source_mutation",
173
175
  issueCodes,
176
+ outputSummary,
174
177
  attestCommand,
175
178
  issues: [],
176
179
  nextAllowedAction: "restore_source_or_run_a_new_quick_verification",
@@ -188,6 +191,7 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
188
191
  commandExitCode: execution.exitCode,
189
192
  mutationDecision: "source_unchanged",
190
193
  issueCodes,
194
+ outputSummary,
191
195
  attestCommand,
192
196
  issues: execution.launchError ? [execution.launchError] : [],
193
197
  nextAllowedAction: "inspect_output_and_rerun",
@@ -204,6 +208,7 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
204
208
  commandExitCode: 0,
205
209
  mutationDecision: "source_unchanged",
206
210
  issueCodes,
211
+ outputSummary,
207
212
  attestCommand,
208
213
  issues: [],
209
214
  nextAllowedAction: attestCommand,
@@ -100,7 +100,10 @@ import {
100
100
  replaceFleetRegistry,
101
101
  rollbackFleetRegistry,
102
102
  } from "./fleet-discovery.mjs";
103
- import { routeRunKitAssuranceV1 } from "./assurance-router.mjs";
103
+ import {
104
+ RUNKIT_RISK_CATEGORIES,
105
+ routeRunKitAssuranceV1,
106
+ } from "./assurance-router.mjs";
104
107
  import {
105
108
  formatUsageModeHumanV1,
106
109
  recommendUsageModeV1,
@@ -244,7 +247,7 @@ read-only and grant no Git, release, deployment, production, or business authori
244
247
  [--irreversible] [--rollback-uncertain]
245
248
  [--permission-sensitive] [--security-sensitive] [--funds-at-risk]
246
249
  [--schema-or-data-migration] [--formal-acceptance]
247
- [--risk-category <category>] [--json]
250
+ [--risk-category <${RUNKIT_RISK_CATEGORIES.join("|")}>] [--json]
248
251
 
249
252
  Returns one user mode plus the independent continuity and assurance selections.
250
253
  Persisted mode preferences are not part of this release.
@@ -259,6 +262,13 @@ Reads the current mode preference source without creating project state.
259
262
 
260
263
  Attests one Quick Receipt against its bound materials and current workspace.
261
264
  It never grants repository or business authority.
265
+ `,
266
+ "quick-verify": `Usage:
267
+ owlrunkit quick-verify --workspace <directory> [--json] -- <executable> [args...]
268
+
269
+ Runs one exact command, saves source-bound stdout/stderr and a Quick receipt,
270
+ and returns a bounded output summary. Supply the exact command after --.
271
+ Quick evidence is not Formal acceptance and grants no repository or business authority.
262
272
  `,
263
273
  fleet: `Usage:
264
274
  owlrunkit fleet register-root --fleet-root <directory>
@@ -437,6 +447,18 @@ action without granting any authority.
437
447
  Project status is derived from durable work items, Agent assignments,
438
448
  dependencies, decisions, checkpoints, integration gates, and evidence refs.
439
449
  No self-reported percentage is accepted.
450
+ `,
451
+ "project checkpoint": `Usage:
452
+ owlrunkit project checkpoint --workspace <directory> --checkpoint-id <id> --at <ISO-UTC>
453
+ --assignment-id <current> --work-item <id>
454
+ --state <active|waiting_dependency|waiting_decision|verifying|ready_to_integrate|completed|failed>
455
+ --summary <text> [--completed-units <nonnegative-int>]
456
+ [--evidence <ref> ...] [--blocker <ref> ...] [--decision <id> ...]
457
+ [--next <text>] [--source-fingerprint <sha256>] [--json]
458
+
459
+ Measured WorkItems require --completed-units on every checkpoint. A completed
460
+ checkpoint must use the WorkItem's declared total. Example:
461
+ 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
440
462
  `,
441
463
  "project status": `Usage:
442
464
  owlrunkit project status --workspace <directory> [--json]
@@ -465,6 +487,25 @@ function withHumanOutput(result, humanOutput) {
465
487
  return result;
466
488
  }
467
489
 
490
+ function shellCommandToken(value) {
491
+ const text = String(value);
492
+ return /^[A-Za-z0-9_@%+=:,./-]+$/u.test(text)
493
+ ? text
494
+ : `'${text.replaceAll("'", `'"'"'`)}'`;
495
+ }
496
+
497
+ function correctedCheckpointCommand(argv, completedUnits) {
498
+ const corrected = [...argv];
499
+ const jsonIndex = corrected.indexOf("--json");
500
+ corrected.splice(
501
+ jsonIndex === -1 ? corrected.length : jsonIndex,
502
+ 0,
503
+ "--completed-units",
504
+ String(completedUnits),
505
+ );
506
+ return `owlrunkit ${corrected.map(shellCommandToken).join(" ")}`;
507
+ }
508
+
468
509
  function formatTeamProjectAssignmentHuman(result) {
469
510
  const disposition = result.resumed ? "Resumed" : "Assigned";
470
511
  const nextAction = result.nextAction === null
@@ -3073,24 +3114,49 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
3073
3114
  event: requestedEvent,
3074
3115
  });
3075
3116
  }
3117
+ const checkpointId = requireOption(options, "checkpoint-id");
3118
+ const occurredAt = requireOption(options, "at");
3119
+ const assignmentId = requireOption(options, "assignment-id");
3120
+ const workItemId = requireOption(options, "work-item");
3121
+ const state = requireOption(options, "state");
3122
+ const summary = requireOption(options, "summary");
3076
3123
  const completedUnits = options["completed-units"] === undefined
3077
3124
  ? undefined
3078
3125
  : parseCanonicalNonnegativeSafeInteger(options["completed-units"], "completed-units");
3079
- const checkpointed = checkpointTeamProjectV1({
3080
- workspaceRoot: root,
3081
- checkpointId: requireOption(options, "checkpoint-id"),
3082
- occurredAt: requireOption(options, "at"),
3083
- assignmentId: requireOption(options, "assignment-id"),
3084
- workItemId: requireOption(options, "work-item"),
3085
- state: requireOption(options, "state"),
3086
- summary: requireOption(options, "summary"),
3087
- completedUnits,
3088
- evidenceRefs: options.evidence ?? [],
3089
- blockerRefs: options.blocker ?? [],
3090
- decisionRefs: options.decision ?? [],
3091
- nextAction: options.next ?? null,
3092
- sourceFingerprint: options["source-fingerprint"],
3093
- });
3126
+ let checkpointed;
3127
+ try {
3128
+ checkpointed = checkpointTeamProjectV1({
3129
+ workspaceRoot: root,
3130
+ checkpointId,
3131
+ occurredAt,
3132
+ assignmentId,
3133
+ workItemId,
3134
+ state,
3135
+ summary,
3136
+ completedUnits,
3137
+ evidenceRefs: options.evidence ?? [],
3138
+ blockerRefs: options.blocker ?? [],
3139
+ decisionRefs: options.decision ?? [],
3140
+ nextAction: options.next ?? null,
3141
+ sourceFingerprint: options["source-fingerprint"],
3142
+ });
3143
+ } catch (error) {
3144
+ if (error instanceof Error
3145
+ && error.message === "Measured work item checkpoint requires completedUnits.") {
3146
+ const projectStatus = readTeamProjectStatusV1({ workspaceRoot: root });
3147
+ const workItem = projectStatus.workItems.find((row) => row.workItemId === workItemId);
3148
+ const suggestedUnits = state === "completed"
3149
+ ? workItem?.progress.total
3150
+ : workItem?.progress.completed;
3151
+ if (Number.isSafeInteger(suggestedUnits)) {
3152
+ throw new Error(
3153
+ "Measured work item checkpoint requires --completed-units. "
3154
+ + `Corrected command: ${correctedCheckpointCommand(argv, suggestedUnits)}`,
3155
+ );
3156
+ }
3157
+ }
3158
+ throw error;
3159
+ }
3094
3160
  return options.json === true
3095
3161
  ? checkpointed
3096
3162
  : withHumanOutput(checkpointed, formatTeamProjectCheckpointHuman(checkpointed));
@@ -16,6 +16,7 @@
16
16
  "detectionSha256": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/hash" }] },
17
17
  "currentProfilesSha256": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/hash" }] },
18
18
  "proposedProfilesSha256": { "$ref": "#/$defs/hash" },
19
+ "normalizationOnly": { "type": "boolean" },
19
20
  "addedProfileIds": { "$ref": "#/$defs/strings" },
20
21
  "updatedProfiles": { "type": "array", "items": { "$ref": "#/$defs/update" } },
21
22
  "proposedProfiles": { "$ref": "#/$defs/profiles" },
@@ -81,6 +82,7 @@
81
82
  "detectionSha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
82
83
  "currentProfilesSha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
83
84
  "proposedProfilesSha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
85
+ "normalizationOnly": false,
84
86
  "addedProfileIds": [],
85
87
  "updatedProfiles": [{
86
88
  "profileId": "node-quality",
@@ -1,3 +1,5 @@
1
+ import { RUNKIT_RISK_CATEGORIES } from "./assurance-router.mjs";
2
+
1
3
  const USER_MODES = new Set(["auto", "off", "light", "managed", "formal"]);
2
4
 
3
5
  const BOOLEAN_FACTS = [
@@ -24,17 +26,7 @@ const MATERIAL_RISK_CATEGORIES = new Map([
24
26
  ["migration", "schema_or_data_migration"],
25
27
  ]);
26
28
 
27
- const KNOWN_RISK_CATEGORIES = new Set([
28
- "backtest",
29
- "deployment",
30
- "destructive",
31
- "foreign_write",
32
- "funds",
33
- "integration",
34
- "migration",
35
- "production",
36
- "release",
37
- ]);
29
+ const KNOWN_RISK_CATEGORIES = new Set(RUNKIT_RISK_CATEGORIES);
38
30
 
39
31
  const AUTHORITY_BOUNDARY = Object.freeze({
40
32
  authorizationGranted: false,
@@ -65,8 +57,14 @@ function requireFacts(input) {
65
57
  || !input.riskCategories.every((value) => typeof value === "string")) {
66
58
  throw new Error("riskCategories must be an array of strings");
67
59
  }
68
- if (input.riskCategories.some((value) => !KNOWN_RISK_CATEGORIES.has(value))) {
69
- throw new Error("riskCategories contains an unknown category");
60
+ const unknownCategories = uniqueSorted(
61
+ input.riskCategories.filter((value) => !KNOWN_RISK_CATEGORIES.has(value)),
62
+ );
63
+ if (unknownCategories.length > 0) {
64
+ throw new Error(
65
+ `riskCategories contains an unknown category: ${unknownCategories.join(", ")}. `
66
+ + `Allowed: ${RUNKIT_RISK_CATEGORIES.join(", ")}`,
67
+ );
70
68
  }
71
69
  }
72
70