owlrunkit 0.18.3 → 0.18.4

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 (29) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +125 -52
  3. package/docs/architecture/OWLCODA_RUNKIT_0184_EXTERNAL_DELIVERY_INTAKE_PRODUCT_DECISION_20260812.md +209 -0
  4. package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +8 -7
  5. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +109 -77
  6. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  7. package/package.json +1 -1
  8. package/packages/attest/distribution-manifest-v1.json +5 -0
  9. package/packages/attest/src/quick-receipt-contract.mjs +3 -3
  10. package/packages/attest/src/quick.mjs +5 -0
  11. package/packages/attest/src/workspace-snapshot.mjs +10 -1
  12. package/registry-release-policy-v1.json +5 -5
  13. package/sbom.spdx.json +5 -5
  14. package/scripts/runkit-contract/assurance-router.mjs +25 -6
  15. package/scripts/runkit-contract/core-contract.mjs +9 -1
  16. package/scripts/runkit-contract/onboarding-doctor.mjs +1 -1
  17. package/scripts/runkit-contract/profile-onboarding.mjs +263 -15
  18. package/scripts/runkit-contract/project-cli-resolver.mjs +6 -0
  19. package/scripts/runkit-contract/project-external-delivery.mjs +707 -0
  20. package/scripts/runkit-contract/quick-verify.mjs +1 -1
  21. package/scripts/runkit-contract/runkit-bootstrap.mjs +6 -1
  22. package/scripts/runkit-contract/runkit-cli.mjs +278 -140
  23. package/scripts/runkit-contract/schemas/assurance-route-v2.schema.json +26 -0
  24. package/scripts/runkit-contract/schemas/profile-detection-v3.schema.json +170 -0
  25. package/scripts/runkit-contract/schemas/team-project-event-v3.schema.json +146 -0
  26. package/scripts/runkit-contract/schemas/team-project-status-v2.schema.json +85 -0
  27. package/scripts/runkit-contract/schemas/usage-mode-recommendation-v2.schema.json +136 -0
  28. package/scripts/runkit-contract/team-project.mjs +477 -15
  29. package/scripts/runkit-contract/usage-mode.mjs +28 -7
@@ -56,9 +56,9 @@ function incompleteReasons(input) {
56
56
  return [...new Set(reasons)].sort();
57
57
  }
58
58
 
59
- function result({ lane, riskMode, reasonCodes, nextCommand }) {
59
+ function result({ schemaVersion, lane, riskMode, reasonCodes, nextCommand }) {
60
60
  return {
61
- schemaVersion: "OwlCodaRunKitAssuranceRouteV1",
61
+ schemaVersion,
62
62
  status: "routed",
63
63
  lane,
64
64
  riskMode,
@@ -68,14 +68,15 @@ function result({ lane, riskMode, reasonCodes, nextCommand }) {
68
68
  };
69
69
  }
70
70
 
71
- export function routeRunKitAssuranceV1(input) {
71
+ function routeRunKitAssurance(input, { schemaVersion, commandPrefix }) {
72
72
  const incomplete = incompleteReasons(input);
73
73
  if (incomplete.length > 0) {
74
74
  return result({
75
+ schemaVersion,
75
76
  lane: "formal",
76
77
  riskMode: "full",
77
78
  reasonCodes: ["assurance_input_incomplete", ...incomplete],
78
- nextCommand: "owlrunkit formal start",
79
+ nextCommand: `${commandPrefix} formal start`,
79
80
  });
80
81
  }
81
82
 
@@ -95,15 +96,17 @@ export function routeRunKitAssuranceV1(input) {
95
96
  ];
96
97
  if (formalReasons.length > 0) {
97
98
  return result({
99
+ schemaVersion,
98
100
  lane: "formal",
99
101
  riskMode: "full",
100
102
  reasonCodes: [...new Set(formalReasons)].sort(),
101
- nextCommand: "owlrunkit formal start",
103
+ nextCommand: `${commandPrefix} formal start`,
102
104
  });
103
105
  }
104
106
 
105
107
  if (!input.needsDurableEvidence) {
106
108
  return result({
109
+ schemaVersion,
107
110
  lane: "none",
108
111
  riskMode: "lightweight",
109
112
  reasonCodes: ["durable_evidence_not_required"],
@@ -113,6 +116,7 @@ export function routeRunKitAssuranceV1(input) {
113
116
 
114
117
  if (input.exactCommandCount !== 1) {
115
118
  return result({
119
+ schemaVersion,
116
120
  lane: "none",
117
121
  riskMode: "lightweight",
118
122
  reasonCodes: ["quick_exact_command_required"],
@@ -121,9 +125,24 @@ export function routeRunKitAssuranceV1(input) {
121
125
  }
122
126
 
123
127
  return result({
128
+ schemaVersion,
124
129
  lane: "quick",
125
130
  riskMode: "lightweight",
126
131
  reasonCodes: ["single_bounded_verification"],
127
- nextCommand: "owlrunkit quick-verify",
132
+ nextCommand: `${commandPrefix} quick-verify`,
133
+ });
134
+ }
135
+
136
+ export function routeRunKitAssuranceV1(input) {
137
+ return routeRunKitAssurance(input, {
138
+ schemaVersion: "OwlCodaRunKitAssuranceRouteV1",
139
+ commandPrefix: "owlrunkit",
140
+ });
141
+ }
142
+
143
+ export function routeRunKitAssuranceV2(input) {
144
+ return routeRunKitAssurance(input, {
145
+ schemaVersion: "OwlCodaRunKitAssuranceRouteV2",
146
+ commandPrefix: "npx --no-install owlrunkit",
128
147
  });
129
148
  }
@@ -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.3";
17
+ export const CORE_VERSION = "0.18.4";
18
18
  export const RUNTIME_ROOT = ".owlcoda/runkit";
19
19
 
20
20
  const CORE_FILES = [
@@ -43,6 +43,7 @@ const CORE_FILES = [
43
43
  "project-cli-resolver.mjs",
44
44
  "project-control-parser.mjs",
45
45
  "project-control-state.mjs",
46
+ "project-external-delivery.mjs",
46
47
  "provenance-common.mjs",
47
48
  "quick-attest.mjs",
48
49
  "quick-canonical.mjs",
@@ -76,6 +77,7 @@ const CORE_FILES = [
76
77
  "verification-plan.mjs",
77
78
  "visual-smoke.mjs",
78
79
  "schemas/assurance-route-v1.schema.json",
80
+ "schemas/assurance-route-v2.schema.json",
79
81
  "schemas/closeout-receipt-v2.schema.json",
80
82
  "schemas/adoption-evidence-v1.schema.json",
81
83
  "schemas/coverage-adopt-request-v1.schema.json",
@@ -102,6 +104,7 @@ const CORE_FILES = [
102
104
  "schemas/owner-deployment-decision-v1.schema.json",
103
105
  "schemas/profile-apply-receipt-v1.schema.json",
104
106
  "schemas/profile-detection-v2.schema.json",
107
+ "schemas/profile-detection-v3.schema.json",
105
108
  "schemas/profile-reconcile-plan-v1.schema.json",
106
109
  "schemas/profile-reconcile-receipt-v1.schema.json",
107
110
  "schemas/project-config.schema.json",
@@ -127,9 +130,12 @@ const CORE_FILES = [
127
130
  "schemas/team-project-definition-v1.schema.json",
128
131
  "schemas/team-project-event-v1.schema.json",
129
132
  "schemas/team-project-event-v2.schema.json",
133
+ "schemas/team-project-event-v3.schema.json",
130
134
  "schemas/team-project-successor-receipt-v1.schema.json",
131
135
  "schemas/team-project-status-v1.schema.json",
136
+ "schemas/team-project-status-v2.schema.json",
132
137
  "schemas/usage-mode-recommendation-v1.schema.json",
138
+ "schemas/usage-mode-recommendation-v2.schema.json",
133
139
  "schemas/verification-envelope-v1.schema.json",
134
140
  "schemas/verification-finalize-attempt-v1.schema.json",
135
141
  "schemas/verification-preflight-v1.schema.json",
@@ -161,10 +167,12 @@ const CORE_FILES = [
161
167
  ];
162
168
 
163
169
  const CORE_DEPENDENCY_FILES = [
170
+ "packages/attest/schemas/workspace-snapshot-v1.schema.json",
164
171
  "packages/attest/src/formal.mjs",
165
172
  "packages/attest/src/offline-bundle.mjs",
166
173
  "packages/attest/src/quick-receipt-contract.mjs",
167
174
  "packages/attest/src/reference-contract.mjs",
175
+ "packages/attest/src/workspace-snapshot.mjs",
168
176
  ];
169
177
 
170
178
  function sha256(value) {
@@ -818,7 +818,7 @@ export function formatDoctorHuman(report) {
818
818
  : []),
819
819
  ...(otherWarnings.length > 0 ? [`Warnings: ${otherWarnings.join(", ")}`] : []),
820
820
  `Next: ${report.nextAllowedAction === "adopt_current_release"
821
- ? `owlrunkit adopt --workspace . --exact ${PACKAGE_NAME}@${EXPECTED_VERSION}`
821
+ ? `npx --no-install owlrunkit adopt --workspace . --exact ${PACKAGE_NAME}@${EXPECTED_VERSION}`
822
822
  : report.nextAllowedAction}`,
823
823
  ...(report.maintenanceNextAction
824
824
  ? [`Maintenance: ${report.maintenanceNextAction}`]
@@ -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]));
@@ -1490,9 +1736,11 @@ export function reconcileProfilesV2({
1490
1736
  );
1491
1737
  }
1492
1738
 
1493
- const detection = detectProfilesV2({ workspaceRoot: root, projectToolResolver });
1739
+ const detection = detectProfilesV3({ workspaceRoot: root, projectToolResolver });
1494
1740
  const detectionIssues = verifyDetectionInputs(root, detection, {
1495
1741
  allowedApplyStatuses: ["profiles_already_exists"],
1742
+ schemaVersion: "OwlCodaRunKitProfileDetectionV3",
1743
+ checkChangedPaths: true,
1496
1744
  });
1497
1745
  if (detection.currentProfilesSha256 !== currentProfilesSha256) {
1498
1746
  detectionIssues.push("profile_reconcile_detection_preimage_mismatch");
@@ -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.18.3", new Set([
22
+ "sha256:cc7a923649592084150828f24f0d90f280af92514bafdbc4999f4015b3175d5c",
23
+ ])],
24
+ ["0.18.2", new Set([
25
+ "sha256:abe17ff794b4019d8d476f09eb5d4105bae1070b1df0aab53e6c94c7ec8b65a6",
26
+ ])],
21
27
  ["0.18.1", new Set([
22
28
  "sha256:349b4157d3d089462b172de1619ccd169b5c1669b9322e2580ac6ec4f8616b07",
23
29
  ])],