owlrunkit 0.18.3 → 0.19.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +223 -52
  3. package/docs/architecture/OWLCODA_RUNKIT_0184_EXTERNAL_DELIVERY_INTAKE_PRODUCT_DECISION_20260812.md +209 -0
  4. package/docs/architecture/OWLCODA_RUNKIT_0190_CONSOLIDATED_DAILY_USE_PRODUCT_DECISION_20260812.md +97 -0
  5. package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +8 -7
  6. package/docs/architecture/OWLCODA_RUN_KIT_CONTRACT_V0_2.md +57 -0
  7. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +133 -77
  8. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  9. package/integrations/codex/skills/owlcoda-runkit/references/contract-v0.2.md +57 -0
  10. package/package.json +1 -1
  11. package/packages/attest/README.md +9 -5
  12. package/packages/attest/consumer-surface-v2.json +263 -0
  13. package/packages/attest/distribution-manifest-v1.json +14 -1
  14. package/packages/attest/schemas/quick-verification-receipt-v3.schema.json +244 -0
  15. package/packages/attest/schemas/workspace-snapshot-v2.schema.json +114 -0
  16. package/packages/attest/src/dependency-environment.mjs +128 -0
  17. package/packages/attest/src/index.mjs +1 -0
  18. package/packages/attest/src/quick-receipt-contract.mjs +205 -12
  19. package/packages/attest/src/quick.mjs +45 -4
  20. package/packages/attest/src/workspace-snapshot.mjs +143 -7
  21. package/registry-release-policy-v1.json +5 -5
  22. package/sbom.spdx.json +5 -5
  23. package/scripts/runkit-contract/assurance-router.mjs +25 -6
  24. package/scripts/runkit-contract/core-contract.mjs +29 -1
  25. package/scripts/runkit-contract/efficiency-metrics.mjs +559 -0
  26. package/scripts/runkit-contract/evidence-store.mjs +130 -0
  27. package/scripts/runkit-contract/onboarding-bootstrap.mjs +685 -0
  28. package/scripts/runkit-contract/onboarding-doctor.mjs +3 -2
  29. package/scripts/runkit-contract/outcome-presentation.mjs +171 -0
  30. package/scripts/runkit-contract/profile-onboarding.mjs +285 -17
  31. package/scripts/runkit-contract/project-cli-resolver.mjs +29 -5
  32. package/scripts/runkit-contract/project-external-delivery.mjs +707 -0
  33. package/scripts/runkit-contract/quick-evidence-reuse.mjs +590 -0
  34. package/scripts/runkit-contract/quick-isolation.mjs +314 -0
  35. package/scripts/runkit-contract/quick-metrics.mjs +51 -3
  36. package/scripts/runkit-contract/quick-receipt.mjs +31 -1
  37. package/scripts/runkit-contract/quick-verify.mjs +217 -26
  38. package/scripts/runkit-contract/quick-workspace-snapshot.mjs +1 -0
  39. package/scripts/runkit-contract/runkit-bootstrap.mjs +7 -1
  40. package/scripts/runkit-contract/runkit-cli.mjs +602 -152
  41. package/scripts/runkit-contract/runtime-transition.mjs +478 -0
  42. package/scripts/runkit-contract/schemas/assurance-route-v2.schema.json +26 -0
  43. package/scripts/runkit-contract/schemas/efficiency-event-v1.schema.json +56 -0
  44. package/scripts/runkit-contract/schemas/efficiency-metrics-v1.schema.json +220 -0
  45. package/scripts/runkit-contract/schemas/external-runtime-report-v1.schema.json +125 -0
  46. package/scripts/runkit-contract/schemas/onboarding-bootstrap-plan-v1.schema.json +168 -0
  47. package/scripts/runkit-contract/schemas/onboarding-bootstrap-receipt-v1.schema.json +130 -0
  48. package/scripts/runkit-contract/schemas/outcome-summary-v1.schema.json +83 -0
  49. package/scripts/runkit-contract/schemas/profile-detection-v3.schema.json +170 -0
  50. package/scripts/runkit-contract/schemas/quick-evidence-reuse-plan-v1.schema.json +59 -0
  51. package/scripts/runkit-contract/schemas/quick-evidence-reuse-receipt-v1.schema.json +79 -0
  52. package/scripts/runkit-contract/schemas/runtime-transition-receipt-v1.schema.json +185 -0
  53. package/scripts/runkit-contract/schemas/team-project-event-v3.schema.json +146 -0
  54. package/scripts/runkit-contract/schemas/team-project-event-v4.schema.json +142 -0
  55. package/scripts/runkit-contract/schemas/team-project-status-v2.schema.json +85 -0
  56. package/scripts/runkit-contract/schemas/usage-mode-recommendation-v2.schema.json +136 -0
  57. package/scripts/runkit-contract/team-project.mjs +1172 -22
  58. package/scripts/runkit-contract/usage-mode.mjs +28 -7
  59. package/scripts/runkit-contract/verification-envelope.mjs +15 -18
@@ -0,0 +1,171 @@
1
+ const RUNTIME_LEVEL_BY_DECISION = Object.freeze({
2
+ external_transition_passed: "reported_passed",
3
+ external_transition_failed_recovered: "reported_failed_recovered",
4
+ external_transition_failed_unrecovered: "reported_failed_unrecovered",
5
+ external_transition_indeterminate: "reported_indeterminate",
6
+ external_transition_invalidated: "reported_indeterminate",
7
+ });
8
+
9
+ function verificationLevel(result) {
10
+ const status = result.quickVerificationStatus ?? result.status;
11
+ if (status === "quick_verification_passed") return "passed";
12
+ if (status === "quick_verification_failed") return "failed";
13
+ if (status === "quick_verification_source_mutated") return "invalidated";
14
+ return "not_proven";
15
+ }
16
+
17
+ function workspaceLevel(result) {
18
+ if (result.mutationDecision === "source_unchanged") return "unchanged";
19
+ if (typeof result.mutationDecision === "string") return "changed";
20
+ return "not_proven";
21
+ }
22
+
23
+ function candidateLevel(result) {
24
+ if (result.attestationDecision === "NO_GO") return "not_proven";
25
+ const verification = verificationLevel(result);
26
+ if (verification === "invalidated" || workspaceLevel(result) === "changed") {
27
+ return "invalidated";
28
+ }
29
+ return typeof result.receiptPath === "string" && workspaceLevel(result) === "unchanged"
30
+ ? "bound"
31
+ : "not_proven";
32
+ }
33
+
34
+ function runtimeLevel(result) {
35
+ const transition = result.runtimeTransition;
36
+ if (!transition || transition.status === "runtime_transition_invalid") return "not_proven";
37
+ return RUNTIME_LEVEL_BY_DECISION[transition.transitionDecision] ?? "reported_indeterminate";
38
+ }
39
+
40
+ function resultDecision(result) {
41
+ if (candidateLevel(result) === "invalidated") return "invalidated";
42
+ if (result.attestationDecision === "NO_GO"
43
+ || [
44
+ "quick_verification_control_invalid",
45
+ "quick_verification_input_invalid",
46
+ "quick_verification_runtime_truth_invalid",
47
+ ].includes(result.status)) {
48
+ return "indeterminate";
49
+ }
50
+ const verification = verificationLevel(result);
51
+ if (verification === "passed") return "pass";
52
+ if (verification === "failed") return "fail";
53
+ return "indeterminate";
54
+ }
55
+
56
+ function headline(result, acceptanceLevels) {
57
+ const decision = resultDecision(result);
58
+ if (decision === "invalidated") {
59
+ return "The verification candidate was invalidated by a workspace or execution-environment change.";
60
+ }
61
+ if (decision === "fail") {
62
+ return "The exact verification command failed for the bound source candidate.";
63
+ }
64
+ if (decision === "indeterminate") {
65
+ return "RunKit did not establish a reusable verification result.";
66
+ }
67
+ if (acceptanceLevels.deploymentRuntime === "reported_failed_recovered") {
68
+ return "Verification passed, and the exact command reported a failed external transition with successful rollback.";
69
+ }
70
+ if (acceptanceLevels.deploymentRuntime === "reported_failed_unrecovered") {
71
+ return "Verification passed, but the exact command reported an unrecovered external transition failure.";
72
+ }
73
+ if (acceptanceLevels.deploymentRuntime === "reported_passed") {
74
+ return "Verification passed, and the exact command reported a successful external transition.";
75
+ }
76
+ return "The exact verification command passed for the bound source candidate.";
77
+ }
78
+
79
+ export function projectQuickOutcomeV1(result) {
80
+ const acceptanceLevels = {
81
+ candidateIdentity: candidateLevel(result),
82
+ workspaceAttestation: workspaceLevel(result),
83
+ verificationCommand: verificationLevel(result),
84
+ productExperience: "not_proven",
85
+ deploymentRuntime: runtimeLevel(result),
86
+ productionBusiness: "not_proven",
87
+ };
88
+ const proven = [];
89
+ if (acceptanceLevels.candidateIdentity === "bound") {
90
+ proven.push("Quick evidence binds the exact command to the captured source candidate.");
91
+ }
92
+ if (acceptanceLevels.workspaceAttestation === "unchanged") {
93
+ proven.push("The bound workspace state remained unchanged during the command.");
94
+ }
95
+ if (acceptanceLevels.verificationCommand === "passed") {
96
+ proven.push("The exact verification command exited successfully.");
97
+ } else if (acceptanceLevels.verificationCommand === "failed") {
98
+ proven.push("The exact verification command failure was preserved.");
99
+ }
100
+ if (result.attestationDecision === "GO") {
101
+ proven.push("The Quick receipt attestation decision is GO.");
102
+ }
103
+ if (acceptanceLevels.deploymentRuntime !== "not_proven") {
104
+ proven.push("The exact command supplied a bounded external-runtime transition report.");
105
+ }
106
+ const notProven = [
107
+ ...(result.attestationDecision === "NO_GO"
108
+ ? ["Quick attestation did not establish a reusable candidate binding."]
109
+ : []),
110
+ "Product or UX acceptance was not performed by Quick Verification.",
111
+ ...(acceptanceLevels.deploymentRuntime === "not_proven"
112
+ ? ["Deployment or runtime activation was not proven."]
113
+ : ["RunKit did not independently observe the external runtime beyond the exact command report."]),
114
+ "Production or business effectiveness was not proven.",
115
+ "No Git, release, deployment, production, or business authority was granted.",
116
+ ];
117
+ return {
118
+ schemaVersion: "OwlCodaRunKitOutcomeSummaryV1",
119
+ result: resultDecision(result),
120
+ headline: headline(result, acceptanceLevels),
121
+ proofLevel: typeof result.receiptPath === "string"
122
+ ? "captured_verification"
123
+ : "no_reusable_evidence",
124
+ acceptanceLevels,
125
+ proven,
126
+ notProven,
127
+ nextAction: typeof result.nextAllowedAction === "string"
128
+ ? result.nextAllowedAction
129
+ : "inspect_result",
130
+ authorizationGranted: false,
131
+ };
132
+ }
133
+
134
+ function display(value) {
135
+ return value.replaceAll("_", " ");
136
+ }
137
+
138
+ function humanNextAction(nextAction) {
139
+ if (nextAction.includes(" quick-attest ")) return "Attest this Quick result.";
140
+ const friendly = {
141
+ consume_attestation: "Use the attested verification evidence at the next named gate.",
142
+ inspect_attestation_issues: "Inspect the attestation issues before relying on this result.",
143
+ inspect_output_and_rerun: "Inspect the failed command output, repair the cause, and rerun.",
144
+ restore_source_or_run_a_new_quick_verification: "Restore or freeze the intended candidate, then run a new verification.",
145
+ repair_runtime_report_and_run_a_new_quick_verification: "Repair the runtime report and run a new verification.",
146
+ };
147
+ return friendly[nextAction] ?? nextAction.replaceAll("_", " ");
148
+ }
149
+
150
+ export function formatQuickOutcomeHumanV1(outcome) {
151
+ const label = {
152
+ pass: "PASS",
153
+ fail: "FAIL",
154
+ invalidated: "INVALIDATED",
155
+ indeterminate: "INDETERMINATE",
156
+ }[outcome.result];
157
+ const levels = outcome.acceptanceLevels;
158
+ return [
159
+ `Result: ${label} — ${outcome.headline}`,
160
+ `Proved: ${outcome.proven.length > 0 ? outcome.proven.join(" ") : "No reusable verification claim."}`,
161
+ "Acceptance: "
162
+ + `Candidate: ${display(levels.candidateIdentity)}; `
163
+ + `workspace: ${display(levels.workspaceAttestation)}; `
164
+ + `verification: ${display(levels.verificationCommand)}; `
165
+ + "Product/UX: not proven; "
166
+ + `Deployment/runtime: ${display(levels.deploymentRuntime)}; `
167
+ + "Production/business: not proven.",
168
+ `Next: ${humanNextAction(outcome.nextAction)}`,
169
+ "Authority: no Git, release, deployment, production, or business authority granted.",
170
+ ].join("\n");
171
+ }
@@ -1,3 +1,4 @@
1
+ import { execFileSync } from "node:child_process";
1
2
  import { createHash } from "node:crypto";
2
3
  import {
3
4
  existsSync,
@@ -765,7 +766,7 @@ function bindCommandsToProjectTool(profiles, toolBinding) {
765
766
  }));
766
767
  }
767
768
 
768
- function proposedProfileDocument(candidates, inputManifest) {
769
+ function proposedProfileDocument(candidates, inputManifest, detectedSurfacePaths = []) {
769
770
  const actionable = candidates.filter(profile => profile.commands.length > 0);
770
771
  const selectedPrimary = actionable.find(profile => profile.id === "node-quality")
771
772
  ?? actionable[0]
@@ -779,13 +780,162 @@ function proposedProfileDocument(candidates, inputManifest) {
779
780
  ...profile,
780
781
  paths: [...new Set([
781
782
  ...profile.paths,
782
- ...(profile.id === selectedPrimary?.id ? rootSurfacePaths : []),
783
+ ...(profile.id === selectedPrimary?.id
784
+ ? [...rootSurfacePaths, ...detectedSurfacePaths]
785
+ : []),
783
786
  ])].sort(),
784
787
  primary: profile.id === selectedPrimary?.id,
785
788
  })),
786
789
  };
787
790
  }
788
791
 
792
+ function visibleGitChangedPaths(root) {
793
+ try {
794
+ const gitRoot = realpathSync(execFileSync(
795
+ "git",
796
+ ["rev-parse", "--show-toplevel"],
797
+ {
798
+ cwd: root,
799
+ encoding: "utf8",
800
+ stdio: ["ignore", "pipe", "pipe"],
801
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
802
+ },
803
+ ).trim());
804
+ const workspacePrefix = path.relative(gitRoot, root).split(path.sep).join("/");
805
+ if (workspacePrefix.startsWith("../") || workspacePrefix === "..") {
806
+ return { paths: [], issueCodes: ["profile_git_root_unusable"] };
807
+ }
808
+ const normalizeGitPath = (filePath) => {
809
+ const normalized = filePath.split(path.sep).join("/");
810
+ if (!workspacePrefix) return normalized;
811
+ const prefix = `${workspacePrefix}/`;
812
+ return normalized.startsWith(prefix) ? normalized.slice(prefix.length) : null;
813
+ };
814
+ const raw = execFileSync(
815
+ "git",
816
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", "."],
817
+ {
818
+ cwd: root,
819
+ encoding: "utf8",
820
+ maxBuffer: 64 * 1024 * 1024,
821
+ stdio: ["ignore", "pipe", "pipe"],
822
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
823
+ },
824
+ );
825
+ const fields = raw.split("\0");
826
+ const paths = [];
827
+ for (let index = 0; index < fields.length; index += 1) {
828
+ const field = fields[index];
829
+ if (!field) continue;
830
+ const status = field.slice(0, 2);
831
+ const currentPath = normalizeGitPath(field.slice(3));
832
+ let priorPath = null;
833
+ if (status.includes("R") || status.includes("C")) {
834
+ const prior = fields[index + 1];
835
+ if (!prior) return { paths: [], issueCodes: ["profile_git_status_unusable"] };
836
+ priorPath = normalizeGitPath(prior);
837
+ index += 1;
838
+ }
839
+ if (currentPath === null || (status.includes("R") && priorPath === null)) {
840
+ return { paths: [], issueCodes: ["profile_git_path_outside_workspace"] };
841
+ }
842
+ const changedPaths = status.includes("R") ? [currentPath, priorPath] : [currentPath];
843
+ for (const filePath of changedPaths) {
844
+ if (!filePath
845
+ || filePath === ".owlcoda/runkit"
846
+ || filePath.startsWith(".owlcoda/runkit/")) continue;
847
+ try {
848
+ safeRelativePath(filePath, "profile changed path");
849
+ paths.push(filePath);
850
+ } catch {
851
+ return { paths: [], issueCodes: ["profile_changed_path_unusable"] };
852
+ }
853
+ }
854
+ }
855
+ return { paths: [...new Set(paths)].sort(), issueCodes: [] };
856
+ } catch {
857
+ return { paths: [], issueCodes: ["profile_git_status_unavailable"] };
858
+ }
859
+ }
860
+
861
+ function sourceCandidatePath(filePath) {
862
+ const segments = filePath.split("/");
863
+ const nonSourceSegments = new Set([
864
+ ".cache",
865
+ ".mypy_cache",
866
+ ".pytest_cache",
867
+ ".ruff_cache",
868
+ ".turbo",
869
+ ".vite",
870
+ ".vitest",
871
+ "__pycache__",
872
+ "node_modules",
873
+ ]);
874
+ if (segments.some(segment => nonSourceSegments.has(segment))) return false;
875
+ if ([".github", ".owlcoda", "docs"].includes(segments[0])) return false;
876
+ return !/\.(?:md|mdx|rst|txt)$/iu.test(filePath);
877
+ }
878
+
879
+ function shellQuote(value) {
880
+ return `'${String(value).replaceAll("'", `'"'"'`)}'`;
881
+ }
882
+
883
+ function profileCoveragePlan({ root, proposedProfiles, currentProfilesSha256 }) {
884
+ const changed = visibleGitChangedPaths(root);
885
+ const impact = resolveProfileImpactDetailed({
886
+ changedPaths: changed.paths,
887
+ profiles: proposedProfiles.profiles,
888
+ });
889
+ const uncoveredChangedPaths = [...impact.uncoveredPaths];
890
+ const uncoveredSourcePaths = uncoveredChangedPaths.filter(sourceCandidatePath);
891
+ const selectedPrimary = proposedProfiles.profiles.find(profile => profile.primary) ?? null;
892
+ const primaryBlockers = [];
893
+ if (selectedPrimary === null) primaryBlockers.push("actionable_primary_profile_missing");
894
+ if (uncoveredSourcePaths.length > 0) {
895
+ primaryBlockers.push(
896
+ `candidate_paths_do_not_cover_changed_source:${uncoveredSourcePaths.join(",")}`,
897
+ );
898
+ }
899
+ const minimalSuggestedProfile = selectedPrimary && uncoveredSourcePaths.length > 0
900
+ ? {
901
+ ...structuredClone(selectedPrimary),
902
+ paths: [...new Set([
903
+ ...selectedPrimary.paths,
904
+ ...uncoveredSourcePaths,
905
+ ])].sort(),
906
+ primary: true,
907
+ }
908
+ : null;
909
+ const repairedProfiles = minimalSuggestedProfile
910
+ ? {
911
+ ...structuredClone(proposedProfiles),
912
+ profiles: proposedProfiles.profiles.map(profile => (
913
+ profile.id === minimalSuggestedProfile.id
914
+ ? structuredClone(minimalSuggestedProfile)
915
+ : structuredClone(profile)
916
+ )),
917
+ }
918
+ : proposedProfiles;
919
+ const command = currentProfilesSha256 === null ? "detect" : "reconcile";
920
+ const workspace = shellQuote(root);
921
+ return {
922
+ status: primaryBlockers.length > 0 ? "profiles_insufficient" : "profiles_detected",
923
+ proposedProfiles: repairedProfiles,
924
+ changedPaths: changed.paths,
925
+ uncoveredChangedPaths,
926
+ uncoveredSourcePaths,
927
+ primaryBlockers: [...new Set(primaryBlockers)].sort(),
928
+ minimalSuggestedProfile,
929
+ repairCommands: {
930
+ preview: `npx --no-install owlrunkit profiles ${command} --workspace ${workspace} --dry-run`,
931
+ apply: minimalSuggestedProfile || primaryBlockers.length === 0
932
+ ? `npx --no-install owlrunkit profiles ${command} --workspace ${workspace} --apply`
933
+ : null,
934
+ },
935
+ issueCodes: changed.issueCodes,
936
+ };
937
+ }
938
+
789
939
  function profilesFileHash(root) {
790
940
  const ref = path.join(root, ".owlcoda", "runkit", "profiles.json");
791
941
  if (!existsSync(ref)) return null;
@@ -944,10 +1094,10 @@ function recoverProfileApplyTransaction({
944
1094
  return receipt;
945
1095
  }
946
1096
 
947
- export function detectProfilesV2({
1097
+ function detectProfilesVersion({
948
1098
  workspaceRoot,
949
1099
  projectToolResolver,
950
- } = {}) {
1100
+ } = {}, { schemaVersion, includeCoverage }) {
951
1101
  const root = resolveBoundedWorkspaceRoot(workspaceRoot);
952
1102
  const manifest = readWorkspaceJsonBounded(root, "package.json");
953
1103
  const discovered = discoverWorkspaces(root, manifest);
@@ -960,6 +1110,23 @@ export function detectProfilesV2({
960
1110
  toolBinding.packageManager,
961
1111
  true,
962
1112
  );
1113
+ const detectedSurfacePaths = includeCoverage
1114
+ ? (() => {
1115
+ const candidateSurfacePaths = new Set(
1116
+ rawCandidates.flatMap(profile => profile.paths),
1117
+ );
1118
+ return profileCandidates(
1119
+ root,
1120
+ manifest,
1121
+ toolBinding.executable,
1122
+ discovered.workspaces,
1123
+ toolBinding.packageManager,
1124
+ true,
1125
+ true,
1126
+ ).flatMap(profile => profile.paths)
1127
+ .filter(profilePath => !candidateSurfacePaths.has(profilePath));
1128
+ })()
1129
+ : [];
963
1130
  const candidates = bindCommandsToProjectTool(rawCandidates, toolBinding);
964
1131
  let inputManifest = [];
965
1132
  const issueCodes = [
@@ -971,21 +1138,44 @@ export function detectProfilesV2({
971
1138
  } catch (error) {
972
1139
  issueCodes.push(error instanceof Error ? error.message : String(error));
973
1140
  }
974
- const proposedProfiles = proposedProfileDocument(candidates, inputManifest);
1141
+ const initialProfiles = proposedProfileDocument(
1142
+ candidates,
1143
+ inputManifest,
1144
+ detectedSurfacePaths,
1145
+ );
1146
+ const currentProfilesSha256 = profilesFileHash(root);
1147
+ const coverage = includeCoverage
1148
+ ? profileCoveragePlan({
1149
+ root,
1150
+ proposedProfiles: initialProfiles,
1151
+ currentProfilesSha256,
1152
+ })
1153
+ : {
1154
+ status: "profiles_detected",
1155
+ proposedProfiles: initialProfiles,
1156
+ changedPaths: [],
1157
+ uncoveredChangedPaths: [],
1158
+ uncoveredSourcePaths: [],
1159
+ primaryBlockers: [],
1160
+ minimalSuggestedProfile: null,
1161
+ repairCommands: null,
1162
+ issueCodes: [],
1163
+ };
1164
+ const proposedProfiles = coverage.proposedProfiles;
1165
+ issueCodes.push(...coverage.issueCodes);
975
1166
  issueCodes.push(...validateProfileDocument(proposedProfiles));
976
1167
  const qualityIssues = [...new Set(issueCodes)].sort();
977
1168
  const confidence = qualityIssues.length === 0 && toolBinding.confidence === "high"
978
1169
  ? "high"
979
1170
  : "review_required";
980
- const currentProfilesSha256 = profilesFileHash(root);
981
1171
  const applyStatus = currentProfilesSha256 !== null
982
1172
  ? "profiles_already_exists"
983
1173
  : confidence === "high"
984
1174
  ? "ready_to_apply"
985
1175
  : "review_required";
986
1176
  const body = {
987
- schemaVersion: "OwlCodaRunKitProfileDetectionV2",
988
- status: "profiles_detected",
1177
+ schemaVersion,
1178
+ status: coverage.status,
989
1179
  dryRun: true,
990
1180
  writesPerformed: 0,
991
1181
  confidence,
@@ -1009,6 +1199,16 @@ export function detectProfilesV2({
1009
1199
  currentProfilesSha256,
1010
1200
  detectedScripts: detectedScripts(manifest),
1011
1201
  detectedWorkspaces: discovered.workspaces,
1202
+ ...(includeCoverage
1203
+ ? {
1204
+ changedPaths: coverage.changedPaths,
1205
+ uncoveredChangedPaths: coverage.uncoveredChangedPaths,
1206
+ uncoveredSourcePaths: coverage.uncoveredSourcePaths,
1207
+ primaryBlockers: coverage.primaryBlockers,
1208
+ minimalSuggestedProfile: coverage.minimalSuggestedProfile,
1209
+ repairCommands: coverage.repairCommands,
1210
+ }
1211
+ : {}),
1012
1212
  authorizationGranted: false,
1013
1213
  };
1014
1214
  return {
@@ -1017,6 +1217,20 @@ export function detectProfilesV2({
1017
1217
  };
1018
1218
  }
1019
1219
 
1220
+ export function detectProfilesV2(input = {}) {
1221
+ return detectProfilesVersion(input, {
1222
+ schemaVersion: "OwlCodaRunKitProfileDetectionV2",
1223
+ includeCoverage: false,
1224
+ });
1225
+ }
1226
+
1227
+ export function detectProfilesV3(input = {}) {
1228
+ return detectProfilesVersion(input, {
1229
+ schemaVersion: "OwlCodaRunKitProfileDetectionV3",
1230
+ includeCoverage: true,
1231
+ });
1232
+ }
1233
+
1020
1234
  function blockedApply(detection, issueCodes, currentProfilesSha256 = null) {
1021
1235
  return {
1022
1236
  schemaVersion: "OwlCodaRunKitProfilesApplyReceiptV1",
@@ -1032,13 +1246,19 @@ function blockedApply(detection, issueCodes, currentProfilesSha256 = null) {
1032
1246
  function verifyDetectionInputs(
1033
1247
  root,
1034
1248
  detection,
1035
- { allowedApplyStatuses = ["ready_to_apply"] } = {},
1249
+ {
1250
+ allowedApplyStatuses = ["ready_to_apply"],
1251
+ schemaVersion = "OwlCodaRunKitProfileDetectionV2",
1252
+ checkChangedPaths = false,
1253
+ } = {},
1036
1254
  ) {
1037
1255
  const issues = [];
1038
1256
  const { detectionSha256, ...body } = detection;
1039
1257
  if (
1040
- detection.schemaVersion !== "OwlCodaRunKitProfileDetectionV2"
1041
- || detection.status !== "profiles_detected"
1258
+ detection.schemaVersion !== schemaVersion
1259
+ || !(schemaVersion === "OwlCodaRunKitProfileDetectionV3"
1260
+ ? ["profiles_detected", "profiles_insufficient"].includes(detection.status)
1261
+ : detection.status === "profiles_detected")
1042
1262
  || !/^[a-f0-9]{64}$/.test(detectionSha256 ?? "")
1043
1263
  || sha256(canonicalJson(body)) !== detectionSha256
1044
1264
  ) {
@@ -1073,13 +1293,22 @@ function verifyDetectionInputs(
1073
1293
  }
1074
1294
  }
1075
1295
  }
1296
+ if (checkChangedPaths) {
1297
+ const currentChangedPaths = visibleGitChangedPaths(root);
1298
+ issues.push(...currentChangedPaths.issueCodes);
1299
+ if (!Array.isArray(detection.changedPaths)) {
1300
+ issues.push("profile_detection_invalid");
1301
+ } else if (canonicalJson(currentChangedPaths.paths) !== canonicalJson(detection.changedPaths)) {
1302
+ issues.push("profile_detection_changed_paths_drift");
1303
+ }
1304
+ }
1076
1305
  return [...new Set(issues)].sort();
1077
1306
  }
1078
1307
 
1079
- export function applyDetectedProfilesV2({
1308
+ function applyDetectedProfilesVersion({
1080
1309
  workspaceRoot,
1081
1310
  detection,
1082
- } = {}) {
1311
+ } = {}, { schemaVersion, checkChangedPaths }) {
1083
1312
  const root = resolveBoundedWorkspaceRoot(workspaceRoot);
1084
1313
  let currentProfilesSha256;
1085
1314
  try {
@@ -1087,7 +1316,10 @@ export function applyDetectedProfilesV2({
1087
1316
  } catch {
1088
1317
  return blockedApply(detection, ["profile_apply_directory_untrusted"]);
1089
1318
  }
1090
- const issues = verifyDetectionInputs(root, detection ?? {});
1319
+ const issues = verifyDetectionInputs(root, detection ?? {}, {
1320
+ schemaVersion,
1321
+ checkChangedPaths,
1322
+ });
1091
1323
  if (issues.length > 0) return blockedApply(detection, issues);
1092
1324
 
1093
1325
  const receiptPath = path.join(
@@ -1194,6 +1426,20 @@ export function applyDetectedProfilesV2({
1194
1426
  }
1195
1427
  }
1196
1428
 
1429
+ export function applyDetectedProfilesV2(input = {}) {
1430
+ return applyDetectedProfilesVersion(input, {
1431
+ schemaVersion: "OwlCodaRunKitProfileDetectionV2",
1432
+ checkChangedPaths: false,
1433
+ });
1434
+ }
1435
+
1436
+ export function applyDetectedProfilesV3(input = {}) {
1437
+ return applyDetectedProfilesVersion(input, {
1438
+ schemaVersion: "OwlCodaRunKitProfileDetectionV3",
1439
+ checkChangedPaths: true,
1440
+ });
1441
+ }
1442
+
1197
1443
  function mergeDetectedProfiles(currentProfiles, detectedProfiles) {
1198
1444
  const profiles = currentProfiles.profiles.map((profile) => structuredClone(profile));
1199
1445
  const byId = new Map(profiles.map((profile) => [profile.id, profile]));
@@ -1344,6 +1590,16 @@ function validateReconcileReceipt(receipt, reconcileSha256) {
1344
1590
  return receiptPath;
1345
1591
  }
1346
1592
 
1593
+ function isInitializationProfileScaffold(value) {
1594
+ return value !== null
1595
+ && typeof value === "object"
1596
+ && !Array.isArray(value)
1597
+ && value.schemaVersion === "OwlCodaRunKitProfilesV1"
1598
+ && Array.isArray(value.profiles)
1599
+ && value.profiles.length === 0
1600
+ && Object.keys(value).sort().join(",") === "profiles,schemaVersion";
1601
+ }
1602
+
1347
1603
  function recoverProfileReconcileTransaction(root) {
1348
1604
  const runtimeRoot = path.join(root, ".owlcoda", "runkit");
1349
1605
  if (!existsSync(runtimeRoot)) return null;
@@ -1464,6 +1720,7 @@ export function reconcileProfilesV2({
1464
1720
  let currentProfilesSha256;
1465
1721
  let currentProfiles;
1466
1722
  let currentProfilesBytes;
1723
+ let replacesInitializationScaffold = false;
1467
1724
  try {
1468
1725
  currentProfilesSha256 = profilesFileHash(root);
1469
1726
  if (currentProfilesSha256 === null) {
@@ -1477,8 +1734,9 @@ export function reconcileProfilesV2({
1477
1734
  root,
1478
1735
  path.join(".owlcoda", "runkit", "profiles.json"),
1479
1736
  );
1737
+ replacesInitializationScaffold = isInitializationProfileScaffold(currentProfiles);
1480
1738
  const documentIssues = validateProfileDocument(currentProfiles);
1481
- if (documentIssues.length > 0) {
1739
+ if (documentIssues.length > 0 && !replacesInitializationScaffold) {
1482
1740
  return blockedReconcile(null, documentIssues, currentProfilesSha256, !apply);
1483
1741
  }
1484
1742
  } catch {
@@ -1490,9 +1748,11 @@ export function reconcileProfilesV2({
1490
1748
  );
1491
1749
  }
1492
1750
 
1493
- const detection = detectProfilesV2({ workspaceRoot: root, projectToolResolver });
1751
+ const detection = detectProfilesV3({ workspaceRoot: root, projectToolResolver });
1494
1752
  const detectionIssues = verifyDetectionInputs(root, detection, {
1495
1753
  allowedApplyStatuses: ["profiles_already_exists"],
1754
+ schemaVersion: "OwlCodaRunKitProfileDetectionV3",
1755
+ checkChangedPaths: true,
1496
1756
  });
1497
1757
  if (detection.currentProfilesSha256 !== currentProfilesSha256) {
1498
1758
  detectionIssues.push("profile_reconcile_detection_preimage_mismatch");
@@ -1501,7 +1761,15 @@ export function reconcileProfilesV2({
1501
1761
  return blockedReconcile(detection, detectionIssues, currentProfilesSha256, !apply);
1502
1762
  }
1503
1763
 
1504
- const merged = mergeDetectedProfiles(currentProfiles, detection.proposedProfiles);
1764
+ const merged = replacesInitializationScaffold
1765
+ ? {
1766
+ proposedProfiles: structuredClone(detection.proposedProfiles),
1767
+ addedProfileIds: detection.proposedProfiles.profiles
1768
+ .map(profile => profile.id)
1769
+ .sort(),
1770
+ updatedProfiles: [],
1771
+ }
1772
+ : mergeDetectedProfiles(currentProfiles, detection.proposedProfiles);
1505
1773
  const proposedIssues = validateProfileDocumentWithDetectedSurfaces(
1506
1774
  root,
1507
1775
  merged.proposedProfiles,
@@ -18,6 +18,15 @@ 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.4", new Set([
22
+ "sha256:372d4c247e4b40b0fd2f3fe0f5bf928548095bfb7e2e92e9ac66790e2717f77d",
23
+ ])],
24
+ ["0.18.3", new Set([
25
+ "sha256:cc7a923649592084150828f24f0d90f280af92514bafdbc4999f4015b3175d5c",
26
+ ])],
27
+ ["0.18.2", new Set([
28
+ "sha256:abe17ff794b4019d8d476f09eb5d4105bae1070b1df0aab53e6c94c7ec8b65a6",
29
+ ])],
21
30
  ["0.18.1", new Set([
22
31
  "sha256:349b4157d3d089462b172de1619ccd169b5c1669b9322e2580ac6ec4f8616b07",
23
32
  ])],
@@ -602,20 +611,32 @@ function verifyInstalledCoreBinding(root, packageRoot, version) {
602
611
  ) {
603
612
  throw new Error("project_cli_core_config_invalid");
604
613
  }
614
+ const installedCore = verifyInstalledCoreClosure(packageRoot, version);
615
+ const actualCoreManifestSha256 = installedCore.coreManifestSha256;
616
+ if (actualCoreManifestSha256 !== core.coreManifestSha256) {
617
+ throw new Error("project_cli_installed_core_drift");
618
+ }
619
+ return {
620
+ schemaVersion: "OwlCodaRunKitInstalledCoreBindingV1",
621
+ coreVersion: version,
622
+ coreManifestSha256: actualCoreManifestSha256,
623
+ trustedIdentitySource: installedCore.trustedIdentitySource,
624
+ configPath: ".owlcoda/runkit/config.json",
625
+ };
626
+ }
627
+
628
+ function verifyInstalledCoreClosure(packageRoot, version) {
605
629
  const actualCoreManifestSha256 = installedCoreManifestSha256(packageRoot);
606
630
  const trusted = trustedCoreManifestHashes(version);
607
631
  if (!trusted.hashes.has(actualCoreManifestSha256)) {
608
632
  throw new Error("project_cli_untrusted_core_identity");
609
633
  }
610
- if (actualCoreManifestSha256 !== core.coreManifestSha256) {
611
- throw new Error("project_cli_installed_core_drift");
612
- }
613
634
  return {
614
635
  schemaVersion: "OwlCodaRunKitInstalledCoreBindingV1",
615
636
  coreVersion: version,
616
637
  coreManifestSha256: actualCoreManifestSha256,
617
638
  trustedIdentitySource: trusted.source,
618
- configPath: ".owlcoda/runkit/config.json",
639
+ configPath: null,
619
640
  };
620
641
  }
621
642
 
@@ -623,6 +644,7 @@ export function resolveProjectCli({
623
644
  workspaceRoot,
624
645
  expectedVersion = null,
625
646
  requireEntrypoint = true,
647
+ requireProjectConfig = requireEntrypoint,
626
648
  } = {}) {
627
649
  let root;
628
650
  try {
@@ -686,7 +708,9 @@ export function resolveProjectCli({
686
708
  "project_cli_symlink_rejected",
687
709
  );
688
710
  const installedCoreBinding = requireEntrypoint
689
- ? verifyInstalledCoreBinding(root, installed.packageRoot, version)
711
+ ? requireProjectConfig
712
+ ? verifyInstalledCoreBinding(root, installed.packageRoot, version)
713
+ : verifyInstalledCoreClosure(installed.packageRoot, version)
690
714
  : null;
691
715
  return {
692
716
  schemaVersion: "OwlCodaRunKitProjectCliBindingV1",