owlrunkit 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.18.0";
18
18
  export const RUNTIME_ROOT = ".owlcoda/runkit";
19
19
 
20
20
  const CORE_FILES = [
@@ -66,6 +66,7 @@ const CORE_FILES = [
66
66
  "source-candidate.mjs",
67
67
  "source-fingerprint.mjs",
68
68
  "sync-release-identity.mjs",
69
+ "team-project-successor.mjs",
69
70
  "team-project.mjs",
70
71
  "verification-envelope.mjs",
71
72
  "verification-envelope-check.mjs",
@@ -122,6 +123,7 @@ const CORE_FILES = [
122
123
  "schemas/verification-context-v1.schema.json",
123
124
  "schemas/team-project-definition-v1.schema.json",
124
125
  "schemas/team-project-event-v1.schema.json",
126
+ "schemas/team-project-successor-receipt-v1.schema.json",
125
127
  "schemas/team-project-status-v1.schema.json",
126
128
  "schemas/verification-envelope-v1.schema.json",
127
129
  "schemas/verification-finalize-attempt-v1.schema.json",
@@ -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,12 @@ 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.2", new Set([
22
+ "sha256:67b883b8a763253b873fb6047c7e7e01c81123aa5250a0db5feaabd13cc4d860",
23
+ ])],
24
+ ["0.17.1", new Set([
25
+ "sha256:5376f3736dd17c07598df8b655a6bbceb3b64b44f3e6630e69f966e420d82e26",
26
+ ])],
21
27
  ["0.17.0", new Set([
22
28
  "sha256:0c0c52e7f6299bdd3d3ea49005c8ceef0b28831d60af25d77da65a4e4de714c9",
23
29
  ])],
@@ -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
  }