owlrunkit 0.17.1 → 0.17.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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.2
4
+
5
+ - Treats valid profile launcher portability warnings as non-blocking maintenance,
6
+ allowing an adopted project to finish onboarding.
7
+ - Separates lifecycle, maintenance, and optional-review actions while keeping one
8
+ lifecycle-prioritized top-level next action.
9
+ - Reports trusted unlinked closeouts as non-blocking independent histories rather
10
+ than implying damaged or ambiguous control state.
11
+ - Collapses repeated profile launcher warnings into one machine-readable summary
12
+ while retaining detailed launcher diagnostics under the profile check.
13
+
3
14
  ## 0.17.1
4
15
 
5
16
  - Prevents a completed checkpoint with `nextAction: null` from reviving an
package/README.md CHANGED
@@ -8,7 +8,7 @@ evidence, then close out once.
8
8
  ## Install
9
9
 
10
10
  ```bash
11
- npm install --save-exact owlrunkit@0.17.1
11
+ npm install --save-exact owlrunkit@0.17.2
12
12
  ```
13
13
 
14
14
  The `owlrunkit` bootstrap uses the project's exact locally bound CLI for
@@ -7,9 +7,9 @@ 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.17.1`, bundling Contract `0.2` and
11
- Core `0.17.1` with manifest
12
- `sha256:5376f3736dd17c07598df8b655a6bbceb3b64b44f3e6630e69f966e420d82e26`.
10
+ Bundled release: standalone `owlrunkit@0.17.2`, bundling Contract `0.2` and
11
+ Core `0.17.2` with manifest
12
+ `sha256:67b883b8a763253b873fb6047c7e7e01c81123aa5250a0db5feaabd13cc4d860`.
13
13
  Publication and dist-tag state are mutable registry facts: verify the exact
14
14
  official npm registry provenance before adoption. The immutable rollback
15
15
  baseline for this release is `owlrunkit@0.16.1` with SHA-1
@@ -2,9 +2,9 @@
2
2
  "schemaVersion": "OwlCodaRunKitConfigV2",
3
3
  "core": {
4
4
  "contractVersion": "0.2",
5
- "coreVersion": "0.17.1",
6
- "coreManifestSha256": "sha256:5376f3736dd17c07598df8b655a6bbceb3b64b44f3e6630e69f966e420d82e26",
7
- "coreSourceRef": "artifact:sha256:5376f3736dd17c07598df8b655a6bbceb3b64b44f3e6630e69f966e420d82e26"
5
+ "coreVersion": "0.17.2",
6
+ "coreManifestSha256": "sha256:67b883b8a763253b873fb6047c7e7e01c81123aa5250a0db5feaabd13cc4d860",
7
+ "coreSourceRef": "artifact:sha256:67b883b8a763253b873fb6047c7e7e01c81123aa5250a0db5feaabd13cc4d860"
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.17.1",
3
+ "version": "0.17.2",
4
4
  "description": "Project-owned execution, evidence, verification, and recovery for coding agents.",
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.17.2",
42
+ "coreManifestSha256": "sha256:67b883b8a763253b873fb6047c7e7e01c81123aa5250a0db5feaabd13cc4d860"
43
+ },
39
44
  {
40
45
  "contractVersion": "0.2",
41
46
  "coreVersion": "0.17.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.17.2",
24
+ coreManifestSha256: "sha256:67b883b8a763253b873fb6047c7e7e01c81123aa5250a0db5feaabd13cc4d860",
25
+ },
21
26
  {
22
27
  contractVersion: "0.2",
23
28
  coreVersion: "0.17.1",
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "OwlCodaRunKitRegistryReleasePolicyV1",
3
3
  "status": "release_candidate_unpublished",
4
4
  "packageName": "owlrunkit",
5
- "candidateVersion": "0.17.1",
5
+ "candidateVersion": "0.17.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.17.0",
27
- "shasum": "fa9eb5cd3ad1b168f36fdca10676a7fa8fe2f584",
28
- "integrity": "sha512-ghUnQaCtxA81htdL3LqqMMaQpKnQ9kwW4ELLZ8mo3BTcbLkGJv/QhFa50JtLHdn6n/ti3sBsfQUgKvHVQZQjPA==",
29
- "tarballUrl": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.17.0.tgz"
26
+ "version": "0.17.1",
27
+ "shasum": "819a824d6df7f89fe8c0a68756e20c446fd67936",
28
+ "integrity": "sha512-Oget/g3MHQaWp1gs7VP/mw3DSlSD4a+vDy3a+3z/8AQ715rLvmkc0ikuz/3T/rcIa0Pw8IecBgqrJu+Lpaf/4w==",
29
+ "tarballUrl": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.17.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.17.1",
6
- "documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.17.1",
5
+ "name": "owlrunkit-0.17.2",
6
+ "documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.17.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.17.1",
18
- "downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.17.1.tgz",
17
+ "versionInfo": "0.17.2",
18
+ "downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.17.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.17.1"
27
+ "referenceLocator": "pkg:npm/owlrunkit@0.17.2"
28
28
  }
29
29
  ]
30
30
  }
@@ -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.17.1";
17
+ export const CORE_VERSION = "0.17.2";
18
18
  export const RUNTIME_ROOT = ".owlcoda/runkit";
19
19
 
20
20
  const CORE_FILES = [
@@ -58,6 +58,20 @@ function sameStringSet(left, right) {
58
58
  && sortedLeft.every((value, index) => value === sortedRight[index]);
59
59
  }
60
60
 
61
+ function decisionCounts(closed) {
62
+ const counts = { accepted: 0, blocked: 0, rejected: 0 };
63
+ for (const execution of closed) counts[closedDecision(execution)] += 1;
64
+ return counts;
65
+ }
66
+
67
+ function sameDecisionCounts(left, right) {
68
+ return isRecord(left)
69
+ && isRecord(right)
70
+ && ["accepted", "blocked", "rejected"].every(
71
+ (decision) => left[decision] === right[decision],
72
+ );
73
+ }
74
+
61
75
  export function selectClosedHistoryFocusV1(inspected) {
62
76
  const closed = Array.isArray(inspected?.executions)
63
77
  ? inspected.executions.filter(execution => execution.lifecycle === "closed")
@@ -98,6 +112,22 @@ export function selectClosedHistoryFocusV1(inspected) {
98
112
  };
99
113
  }
100
114
  }
115
+ if (
116
+ historyBound
117
+ && history.status === "multiple_independent_closed_histories"
118
+ && history.lineageVerified === false
119
+ && history.headRunId === null
120
+ && history.blocking === false
121
+ && Array.isArray(history.issues)
122
+ && history.issues.length === 0
123
+ && sameDecisionCounts(history.decisionCounts, decisionCounts(closed))
124
+ ) {
125
+ return {
126
+ status: "multiple_independent_closed_histories",
127
+ selectedExecution: null,
128
+ decision: null,
129
+ };
130
+ }
101
131
 
102
132
  const decisions = new Set(closed.map(closedDecision));
103
133
  if (decisions.size === 1) {
@@ -436,6 +466,25 @@ export function deriveHumanStatusFromInspectV1(inspected) {
436
466
 
437
467
  if (inspected.recovery.state === "no_active_execution") {
438
468
  const history = selectClosedHistoryFocusV1(inspected);
469
+ if (history.status === "multiple_independent_closed_histories") {
470
+ return withMilestones({
471
+ schemaVersion: "OwlCodaRunKitHumanStatusV1",
472
+ status: "projected",
473
+ overall: "closed",
474
+ headline: "Multiple independent closed histories are trusted; no lineage head was selected.",
475
+ stage: "closeout",
476
+ currentExecutionId: null,
477
+ completedSteps: ["multiple_independent_closed_histories"],
478
+ remainingGates: [],
479
+ remainingGateCount: 0,
480
+ control: { status: "healthy", issueCodes: [] },
481
+ nextAllowedAction: inspected.recovery.nextAllowedAction,
482
+ authorizationGranted: false,
483
+ gitAuthorization: false,
484
+ releaseAuthorization: false,
485
+ deployAuthorization: false,
486
+ }, inspected);
487
+ }
439
488
  if (history.status === "ambiguous_history") {
440
489
  return withMilestones(blockedProjection({
441
490
  headline: "Closed execution history has conflicting outcomes without proven chronology.",
@@ -27,10 +27,38 @@ function selectedFocus(inspected) {
27
27
  : null;
28
28
  }
29
29
 
30
- export function buildInspectSummary(inspected) {
30
+ function closeoutDecisionCounts(inspected) {
31
+ const counts = { accepted: 0, blocked: 0, rejected: 0 };
32
+ for (const execution of inspected.executions) {
33
+ if (execution.lifecycle !== "closed") continue;
34
+ if (Object.hasOwn(counts, execution.closeout?.decision)) {
35
+ counts[execution.closeout.decision] += 1;
36
+ }
37
+ }
38
+ return counts;
39
+ }
40
+
41
+ export function buildInspectSummary(
42
+ inspected,
43
+ { maintenanceNextAction = null } = {},
44
+ ) {
31
45
  const humanStatus = deriveHumanStatusFromInspectV1(inspected);
32
46
  const focus = selectedFocus(inspected);
33
47
  const latestClosed = chronologySelectedCloseout(inspected);
48
+ const controlHistory = inspected.controlState?.closedHistory ?? null;
49
+ const closedRunCount = inspected.executions.filter(
50
+ (execution) => execution.lifecycle === "closed",
51
+ ).length;
52
+ const independentHistory = controlHistory?.status
53
+ === "multiple_independent_closed_histories"
54
+ && controlHistory?.blocking === false;
55
+ const selectedHeadCloseout = latestClosed
56
+ ? {
57
+ runId: latestClosed.runId,
58
+ decision: latestClosed.closeout?.decision ?? "invalid",
59
+ trustLevel: latestClosed.recovery?.evidenceTrustLevel ?? "invalid",
60
+ }
61
+ : null;
34
62
  const holders = inspected.executions
35
63
  .flatMap(execution => (execution.recovery?.lease?.activeWorkItemIds ?? []).map(workItemId => ({
36
64
  runId: execution.runId,
@@ -60,13 +88,21 @@ export function buildInspectSummary(inspected) {
60
88
  activeRunIds: [...inspected.recovery.activeRunIds],
61
89
  openCount: inspected.recovery.activeRunIds.length,
62
90
  },
63
- latestIndexedCloseout: latestClosed
64
- ? {
65
- runId: latestClosed.runId,
66
- decision: latestClosed.closeout?.decision ?? "invalid",
67
- trustLevel: latestClosed.recovery?.evidenceTrustLevel ?? "invalid",
68
- }
69
- : null,
91
+ latestIndexedCloseout: selectedHeadCloseout,
92
+ selectedHeadCloseout,
93
+ closedHistory: {
94
+ status: controlHistory?.status ?? "unavailable",
95
+ runCount: closedRunCount,
96
+ blocking: controlHistory?.blocking === true
97
+ || controlHistory?.status === "ambiguous_history",
98
+ selectedHeadRunId: selectedHeadCloseout?.runId ?? null,
99
+ selectionReason: selectedHeadCloseout
100
+ ? "verified_lineage_head"
101
+ : closedRunCount === 0
102
+ ? "no_closed_history"
103
+ : "no_unique_lineage_head",
104
+ decisionCounts: controlHistory?.decisionCounts ?? closeoutDecisionCounts(inspected),
105
+ },
70
106
  source: {
71
107
  status: delivery?.status ?? "none",
72
108
  sourceFingerprint: delivery?.sourceFingerprint ?? verification?.sourceFingerprint ?? null,
@@ -106,6 +142,9 @@ export function buildInspectSummary(inspected) {
106
142
  code: humanStatus.nextAllowedAction,
107
143
  reasons: [...reasons],
108
144
  },
145
+ lifecycleNextAction: inspected.recovery.nextAllowedAction,
146
+ maintenanceNextAction,
147
+ optionalReviewAction: independentHistory ? "inspect_closed_history" : null,
109
148
  nextAllowedAction: humanStatus.nextAllowedAction,
110
149
  authorizationGranted: false,
111
150
  gitAuthorization: false,
@@ -119,9 +158,9 @@ function value(value) {
119
158
  }
120
159
 
121
160
  function summaryLines(summary, humanStatus) {
122
- const latest = summary.latestIndexedCloseout
123
- ? `${summary.latestIndexedCloseout.runId} ${summary.latestIndexedCloseout.decision}`
124
- : "none";
161
+ const selectedHead = summary.selectedHeadCloseout
162
+ ? `${summary.selectedHeadCloseout.runId} ${summary.selectedHeadCloseout.decision}`
163
+ : `none (${summary.closedHistory.selectionReason})`;
125
164
  const visibleHolders = summary.leases.holders.slice(0, 5)
126
165
  .map(holder => `${holder.runId}/${holder.workItemId}`);
127
166
  const holders = visibleHolders.length === 0
@@ -133,7 +172,8 @@ function summaryLines(summary, humanStatus) {
133
172
  `Status: ${humanStatus.overall}`,
134
173
  `Summary: ${humanStatus.headline}`,
135
174
  `Current execution: ${value(summary.currentExecution.selectedRunId)}`,
136
- `Latest indexed closeout: ${latest}`,
175
+ `Closed history: ${summary.closedHistory.status} (${summary.closedHistory.runCount} runs, ${summary.closedHistory.blocking ? "blocking" : "non-blocking"})`,
176
+ `Selected lineage head: ${selectedHead}`,
137
177
  `Source status: ${summary.source.status}`,
138
178
  `Source/data readiness: ${humanStatus.milestones.sourceData.status}`,
139
179
  `Release package: ${humanStatus.milestones.releasePackage.status}`,
@@ -154,6 +194,12 @@ function summaryLines(summary, humanStatus) {
154
194
  `Receipt reuse: ${summary.resourcePreflight.receiptReuse.appliedCount}/${summary.resourcePreflight.receiptReuse.reusableCount}`,
155
195
  `Dominant gap: ${summary.dominantGap.code}`,
156
196
  `Next allowed action: ${summary.nextAllowedAction}`,
197
+ ...(summary.maintenanceNextAction
198
+ ? [`Maintenance: ${summary.maintenanceNextAction}`]
199
+ : []),
200
+ ...(summary.optionalReviewAction
201
+ ? [`Optional review: ${summary.optionalReviewAction}`]
202
+ : []),
157
203
  `Release authorization: ${summary.releaseAuthorization}`,
158
204
  ].map(escapeHumanControls);
159
205
  }
@@ -550,6 +550,7 @@ function profilesCheck(root) {
550
550
  return { status: "invalid" };
551
551
  }
552
552
  const launcherWarnings = [];
553
+ const launcherIssues = [];
553
554
  for (const profile of profiles.profiles) {
554
555
  for (const command of profile?.commands ?? []) {
555
556
  const identity = `${profile?.id ?? "unknown"}:${command?.id ?? "unknown"}`;
@@ -560,19 +561,22 @@ function profilesCheck(root) {
560
561
  try {
561
562
  const stat = lstatSync(command.executable);
562
563
  if (stat.isSymbolicLink()) {
563
- launcherWarnings.push(`profile_launcher_symlink_rejected:${identity}`);
564
+ launcherIssues.push(`profile_launcher_symlink_rejected:${identity}`);
564
565
  } else if (!stat.isFile() || realpathSync(command.executable) !== path.resolve(command.executable)) {
565
- launcherWarnings.push(`profile_launcher_not_regular_file:${identity}`);
566
+ launcherIssues.push(`profile_launcher_not_regular_file:${identity}`);
566
567
  }
567
568
  } catch {
568
- launcherWarnings.push(`profile_launcher_unavailable:${identity}`);
569
+ launcherIssues.push(`profile_launcher_unavailable:${identity}`);
569
570
  }
570
571
  }
571
572
  }
573
+ const uniqueLauncherIssues = [...new Set(launcherIssues)].sort();
572
574
  return {
573
- status: "ok",
575
+ status: uniqueLauncherIssues.length === 0 ? "ok" : "invalid",
574
576
  count: profiles.profiles.length,
575
577
  launcherWarnings: [...new Set(launcherWarnings)].sort(),
578
+ launcherIssues: uniqueLauncherIssues,
579
+ ...(uniqueLauncherIssues.length > 0 ? { issues: uniqueLauncherIssues } : {}),
576
580
  };
577
581
  } catch {
578
582
  return { status: "invalid" };
@@ -585,6 +589,7 @@ export function readProfileLauncherReadiness({ workspaceRoot } = {}) {
585
589
  return {
586
590
  status: profiles.status,
587
591
  launcherWarnings,
592
+ launcherIssues: profiles.launcherIssues ?? [],
588
593
  ready: profiles.status === "ok" && launcherWarnings.length === 0,
589
594
  };
590
595
  }
@@ -703,16 +708,19 @@ export async function runDoctor({
703
708
  issueCodes.push("registry_install_binding_mismatch");
704
709
  }
705
710
  const uniqueIssues = [...new Set(issueCodes)].sort();
711
+ const profileLauncherWarningCount = (profiles.launcherWarnings ?? []).length;
706
712
  const warningCodes = [
707
713
  ...runtimeIsolation.affectedScripts
708
714
  .map(({ script, tool }) => `runkit_runtime_collection_risk:${tool}:${script}`),
709
- ...(profiles.launcherWarnings ?? []),
715
+ ...(profileLauncherWarningCount > 0 ? ["profile_launcher_warnings"] : []),
710
716
  ].sort();
717
+ const lifecycleNextAction = lifecycle.recovery.nextAllowedAction;
718
+ const maintenanceNextAction = profileLauncherWarningCount > 0
719
+ ? "review_profile_launchers"
720
+ : null;
711
721
  const nextAllowedAction = uniqueIssues.length === 0
712
- ? lifecycle.recovery.nextAllowedAction !== "plan_new_execution"
713
- ? lifecycle.recovery.nextAllowedAction
714
- : (profiles.launcherWarnings ?? []).length > 0
715
- ? "review_profile_launchers"
722
+ ? lifecycleNextAction !== "plan_new_execution"
723
+ ? lifecycleNextAction
716
724
  : adoption.status === "ok"
717
725
  ? "plan_new_execution"
718
726
  : "adopt_current_release"
@@ -733,12 +741,22 @@ export async function runDoctor({
733
741
  checks,
734
742
  issueCodes: uniqueIssues,
735
743
  warningCodes,
744
+ profileLauncherWarnings: {
745
+ count: profileLauncherWarningCount,
746
+ blocking: false,
747
+ recommendedAction: maintenanceNextAction,
748
+ },
749
+ lifecycleNextAction,
750
+ maintenanceNextAction,
736
751
  nextAllowedAction,
737
752
  authorizationGranted: false,
738
753
  };
739
754
  }
740
755
 
741
756
  export function formatDoctorHuman(report) {
757
+ const otherWarnings = report.warningCodes.filter(
758
+ (code) => code !== "profile_launcher_warnings",
759
+ );
742
760
  return [
743
761
  `Doctor: ${report.status}`,
744
762
  `Node: ${report.checks.node.status} (${report.checks.node.version})`,
@@ -749,10 +767,16 @@ export function formatDoctorHuman(report) {
749
767
  `Adoption: ${report.checks.adoption.status}`,
750
768
  `Lifecycle: ${report.checks.lifecycle.status}`,
751
769
  ...(report.issueCodes.length > 0 ? [`Issues: ${report.issueCodes.join(", ")}`] : []),
752
- ...(report.warningCodes.length > 0 ? [`Warnings: ${report.warningCodes.join(", ")}`] : []),
770
+ ...(report.profileLauncherWarnings.count > 0
771
+ ? [`Profile launcher warnings: ${report.profileLauncherWarnings.count} (non-blocking)`]
772
+ : []),
773
+ ...(otherWarnings.length > 0 ? [`Warnings: ${otherWarnings.join(", ")}`] : []),
753
774
  `Next: ${report.nextAllowedAction === "adopt_current_release"
754
775
  ? `owlrunkit adopt --workspace . --exact ${PACKAGE_NAME}@${EXPECTED_VERSION}`
755
776
  : report.nextAllowedAction}`,
777
+ ...(report.maintenanceNextAction
778
+ ? [`Maintenance: ${report.maintenanceNextAction}`]
779
+ : []),
756
780
  "",
757
781
  ].join("\n");
758
782
  }
@@ -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.17.1", new Set([
22
+ "sha256:5376f3736dd17c07598df8b655a6bbceb3b64b44f3e6630e69f966e420d82e26",
23
+ ])],
21
24
  ["0.17.0", new Set([
22
25
  "sha256:0c0c52e7f6299bdd3d3ea49005c8ceef0b28831d60af25d77da65a4e4de714c9",
23
26
  ])],
@@ -600,6 +600,10 @@ function closedHistoryProjection(executions, continuationEdges, issues) {
600
600
  }
601
601
  }
602
602
 
603
+ const decisionCounts = { accepted: 0, blocked: 0, rejected: 0 };
604
+ for (const execution of closed) {
605
+ decisionCounts[execution.closeout.decision] += 1;
606
+ }
603
607
  const decisions = new Set(closed.map(execution => execution.closeout.decision));
604
608
  if (decisions.size === 1) {
605
609
  return {
@@ -611,12 +615,25 @@ function closedHistoryProjection(executions, continuationEdges, issues) {
611
615
  issues: [...issues],
612
616
  };
613
617
  }
618
+ if (issues.length === 0) {
619
+ return {
620
+ status: "multiple_independent_closed_histories",
621
+ runIds,
622
+ headRunId: null,
623
+ decision: null,
624
+ decisionCounts,
625
+ lineageVerified: false,
626
+ blocking: false,
627
+ issues: [],
628
+ };
629
+ }
614
630
  return {
615
631
  status: "ambiguous_history",
616
632
  runIds,
617
633
  headRunId: null,
618
634
  decision: null,
619
635
  lineageVerified: false,
636
+ blocking: true,
620
637
  issues: [...issues],
621
638
  };
622
639
  }
@@ -374,7 +374,7 @@ function profilesReadiness(root) {
374
374
  const validation = validateProfiles({ workspaceRoot: root });
375
375
  const launchers = readProfileLauncherReadiness({ workspaceRoot: root });
376
376
  return {
377
- valid: validation.valid === true && launchers.ready,
377
+ valid: validation.valid === true && launchers.status === "ok",
378
378
  launcherWarnings: launchers.launcherWarnings,
379
379
  };
380
380
  } catch {
@@ -389,8 +389,17 @@ function onboardingProjection(root, recovery = null) {
389
389
  : { status: "missing" };
390
390
  const profilesReady = profiles.valid;
391
391
  const adoptionReady = adoption.status === "ok";
392
+ const maintenanceNextAction = profiles.launcherWarnings.length > 0
393
+ ? "review_profile_launchers"
394
+ : null;
392
395
  if (profilesReady && adoptionReady) {
393
396
  return {
397
+ ...(maintenanceNextAction
398
+ ? {
399
+ status: "completed_with_maintenance_warnings",
400
+ maintenanceNextAction,
401
+ }
402
+ : {}),
394
403
  nextAllowedAction: recovery?.nextAllowedAction ?? "plan_new_execution",
395
404
  completed: true,
396
405
  steps: [],
@@ -402,12 +411,7 @@ function onboardingProjection(root, recovery = null) {
402
411
  command: "owlrunkit doctor --workspace .",
403
412
  },
404
413
  ...(!profilesReady
405
- ? profiles.launcherWarnings.length > 0
406
- ? [{
407
- id: "profile_launchers_review",
408
- command: "owlrunkit doctor --workspace .",
409
- }]
410
- : [{
414
+ ? [{
411
415
  id: "profiles_detect",
412
416
  command: "owlrunkit profiles detect --workspace . --dry-run",
413
417
  }]
@@ -421,6 +425,7 @@ function onboardingProjection(root, recovery = null) {
421
425
  ];
422
426
  return {
423
427
  nextAllowedAction: "run_onboarding_diagnostics",
428
+ ...(maintenanceNextAction ? { maintenanceNextAction } : {}),
424
429
  completed: false,
425
430
  steps,
426
431
  };
@@ -1387,7 +1392,10 @@ function inspect(options) {
1387
1392
  if (requestedRunId && !executions.some(execution => execution.runId === requestedRunId)) {
1388
1393
  throw new Error(`Execution was not found: ${requestedRunId}`);
1389
1394
  }
1390
- const summary = buildInspectSummary(result);
1395
+ const onboarding = onboardingProjection(root, recovery);
1396
+ const summary = buildInspectSummary(result, {
1397
+ maintenanceNextAction: onboarding.maintenanceNextAction ?? null,
1398
+ });
1391
1399
  const view = options.history
1392
1400
  ? { mode: "history" }
1393
1401
  : requestedRunId
@@ -1398,7 +1406,7 @@ function inspect(options) {
1398
1406
  execution: executions.find(execution => execution.runId === requestedRunId),
1399
1407
  }
1400
1408
  : { mode: "summary" };
1401
- return { ...result, summary, view, onboarding: onboardingProjection(root, recovery) };
1409
+ return { ...result, summary, view, onboarding };
1402
1410
  }
1403
1411
 
1404
1412
  function closeout(options) {