owlrunkit 0.18.0 → 0.18.1

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 (26) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +13 -1
  3. package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +142 -0
  4. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +41 -16
  5. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  6. package/package.json +1 -1
  7. package/packages/attest/distribution-manifest-v1.json +6 -1
  8. package/packages/attest/src/quick.mjs +6 -1
  9. package/registry-release-policy-v1.json +5 -5
  10. package/sbom.spdx.json +5 -5
  11. package/scripts/runkit-contract/assurance-router.mjs +28 -20
  12. package/scripts/runkit-contract/core-contract.mjs +5 -1
  13. package/scripts/runkit-contract/inspect-presentation.mjs +38 -5
  14. package/scripts/runkit-contract/install-codex-skill.mjs +60 -1
  15. package/scripts/runkit-contract/onboarding-doctor.mjs +19 -1
  16. package/scripts/runkit-contract/profile-onboarding.mjs +597 -22
  17. package/scripts/runkit-contract/project-cli-resolver.mjs +3 -0
  18. package/scripts/runkit-contract/quick-attest.mjs +1 -0
  19. package/scripts/runkit-contract/quick-metrics.mjs +43 -5
  20. package/scripts/runkit-contract/quick-verify.mjs +15 -4
  21. package/scripts/runkit-contract/runkit-cli.mjs +196 -8
  22. package/scripts/runkit-contract/schemas/profile-reconcile-plan-v1.schema.json +105 -0
  23. package/scripts/runkit-contract/schemas/profile-reconcile-receipt-v1.schema.json +60 -0
  24. package/scripts/runkit-contract/schemas/usage-mode-recommendation-v1.schema.json +136 -0
  25. package/scripts/runkit-contract/templates/assurance-request.json +6 -0
  26. package/scripts/runkit-contract/usage-mode.mjs +229 -0
@@ -7,6 +7,7 @@ import {
7
7
  readFileSync,
8
8
  readdirSync,
9
9
  realpathSync,
10
+ renameSync,
10
11
  rmSync,
11
12
  writeFileSync,
12
13
  } from "node:fs";
@@ -550,6 +551,48 @@ function workspaceProfiles(
550
551
  .filter(Boolean);
551
552
  }
552
553
 
554
+ function existingProfileSurface(root, relativePath, profilePath) {
555
+ const absolute = path.join(root, relativePath);
556
+ try {
557
+ const stat = lstatSync(absolute);
558
+ return stat.isDirectory()
559
+ && !stat.isSymbolicLink()
560
+ && realpathSync(absolute) === path.resolve(absolute)
561
+ ? profilePath
562
+ : null;
563
+ } catch {
564
+ return null;
565
+ }
566
+ }
567
+
568
+ function existingProfileFile(root, candidates) {
569
+ for (const relativePath of candidates) {
570
+ try {
571
+ const absolute = path.join(root, relativePath);
572
+ const stat = lstatSync(absolute);
573
+ if (
574
+ stat.isFile()
575
+ && !stat.isSymbolicLink()
576
+ && realpathSync(absolute) === path.resolve(absolute)
577
+ ) {
578
+ return relativePath;
579
+ }
580
+ } catch {
581
+ // Continue to the next supported filename.
582
+ }
583
+ }
584
+ return null;
585
+ }
586
+
587
+ function realOrManifestSurface(root, directorySurfaces) {
588
+ const paths = directorySurfaces
589
+ .map(([relativePath, profilePath]) => (
590
+ existingProfileSurface(root, relativePath, profilePath)
591
+ ))
592
+ .filter(Boolean);
593
+ return paths.length > 0 ? paths : ["package.json"];
594
+ }
595
+
553
596
  function profileCandidates(
554
597
  root,
555
598
  manifest,
@@ -580,7 +623,7 @@ function profileCandidates(
580
623
  .filter(Boolean);
581
624
  candidates.push({
582
625
  id: "database",
583
- paths: ["prisma/**"],
626
+ paths: realOrManifestSurface(root, [["prisma", "prisma/**"]]),
584
627
  role: "primary",
585
628
  primary: false,
586
629
  requiresProfileIds: [],
@@ -599,7 +642,10 @@ function profileCandidates(
599
642
  .filter(Boolean);
600
643
  candidates.push({
601
644
  id: "node-quality",
602
- paths: ["src/**", "tests/**"],
645
+ paths: realOrManifestSurface(root, [
646
+ ["src", "src/**"],
647
+ ["tests", "tests/**"],
648
+ ]),
603
649
  role: "primary",
604
650
  primary: false,
605
651
  requiresProfileIds: [],
@@ -614,9 +660,17 @@ function profileCandidates(
614
660
  const commands = scripts.has("e2e") && npmExecutable
615
661
  ? [command(`${packageManager}-e2e`, "e2e", npmExecutable)]
616
662
  : [];
663
+ const configPath = existingProfileFile(root, [
664
+ "playwright.config.ts",
665
+ "playwright.config.js",
666
+ ]);
667
+ const paths = [
668
+ configPath,
669
+ existingProfileSurface(root, "tests/e2e", "tests/e2e/**"),
670
+ ].filter(Boolean);
617
671
  candidates.push({
618
672
  id: "web-e2e",
619
- paths: ["playwright.config.ts", "tests/e2e/**"],
673
+ paths: paths.length > 0 ? paths : ["package.json"],
620
674
  role: "primary",
621
675
  primary: false,
622
676
  requiresProfileIds: [],
@@ -753,6 +807,34 @@ function exactRegularFileBytes(absolutePath, expectedBytes) {
753
807
  }
754
808
  }
755
809
 
810
+ function readRegularFileInsideRoot(root, absolutePath) {
811
+ const stat = lstatSync(absolutePath);
812
+ const resolved = realpathSync(absolutePath);
813
+ if (
814
+ stat.isSymbolicLink()
815
+ || !stat.isFile()
816
+ || resolved !== absolutePath
817
+ || !insideRoot(root, resolved)
818
+ ) {
819
+ throw new Error("profile_transaction_file_untrusted");
820
+ }
821
+ return readFileSync(absolutePath, "utf8");
822
+ }
823
+
824
+ function trustedDirectoryInsideRoot(root, absolutePath) {
825
+ const stat = lstatSync(absolutePath);
826
+ const resolved = realpathSync(absolutePath);
827
+ if (
828
+ stat.isSymbolicLink()
829
+ || !stat.isDirectory()
830
+ || resolved !== absolutePath
831
+ || !insideRoot(root, resolved)
832
+ ) {
833
+ throw new Error("profile_transaction_directory_untrusted");
834
+ }
835
+ return resolved;
836
+ }
837
+
756
838
  function commitCreateOnlyStagedFile(stagedPath, finalPath, expectedBytes) {
757
839
  if (!exactRegularFileBytes(stagedPath, expectedBytes)) {
758
840
  throw new Error("profile_apply_staged_file_invalid");
@@ -947,7 +1029,11 @@ function blockedApply(detection, issueCodes, currentProfilesSha256 = null) {
947
1029
  };
948
1030
  }
949
1031
 
950
- function verifyDetectionInputs(root, detection) {
1032
+ function verifyDetectionInputs(
1033
+ root,
1034
+ detection,
1035
+ { allowedApplyStatuses = ["ready_to_apply"] } = {},
1036
+ ) {
951
1037
  const issues = [];
952
1038
  const { detectionSha256, ...body } = detection;
953
1039
  if (
@@ -958,7 +1044,10 @@ function verifyDetectionInputs(root, detection) {
958
1044
  ) {
959
1045
  issues.push("profile_detection_invalid");
960
1046
  }
961
- if (detection.confidence !== "high" || detection.applyStatus !== "ready_to_apply") {
1047
+ if (
1048
+ detection.confidence !== "high"
1049
+ || !allowedApplyStatuses.includes(detection.applyStatus)
1050
+ ) {
962
1051
  issues.push("profile_detection_not_ready_to_apply");
963
1052
  }
964
1053
  issues.push(...validateProfileDocument(detection.proposedProfiles));
@@ -1105,6 +1194,461 @@ export function applyDetectedProfilesV2({
1105
1194
  }
1106
1195
  }
1107
1196
 
1197
+ function mergeDetectedProfiles(currentProfiles, detectedProfiles) {
1198
+ const profiles = currentProfiles.profiles.map((profile) => structuredClone(profile));
1199
+ const byId = new Map(profiles.map((profile) => [profile.id, profile]));
1200
+ const addedProfileIds = [];
1201
+ const updatedProfiles = [];
1202
+ for (const detected of [...detectedProfiles.profiles]
1203
+ .sort((left, right) => left.id.localeCompare(right.id))) {
1204
+ const current = byId.get(detected.id);
1205
+ if (!current) {
1206
+ const impact = resolveProfileImpactDetailed({
1207
+ changedPaths: detected.paths.map(representativePath),
1208
+ profiles,
1209
+ });
1210
+ const primaryCoverageComplete = impact.uncoveredPaths.length === 0
1211
+ && !impact.warnings.some((warning) => warning.startsWith("supporting_only_match:"));
1212
+ if (primaryCoverageComplete) continue;
1213
+ const added = { ...structuredClone(detected), primary: false };
1214
+ profiles.push(added);
1215
+ byId.set(added.id, added);
1216
+ addedProfileIds.push(added.id);
1217
+ continue;
1218
+ }
1219
+ const addedPaths = detected.paths
1220
+ .filter((value) => !current.paths.includes(value))
1221
+ .sort();
1222
+ current.paths = [...new Set([...current.paths, ...detected.paths])].sort();
1223
+ const commands = new Map(current.commands.map((entry) => [entry.id, entry]));
1224
+ const addedCommandIds = [];
1225
+ const updatedCommandIds = [];
1226
+ for (const detectedCommand of detected.commands) {
1227
+ const currentCommand = commands.get(detectedCommand.id);
1228
+ if (!currentCommand) {
1229
+ current.commands.push(structuredClone(detectedCommand));
1230
+ commands.set(detectedCommand.id, detectedCommand);
1231
+ addedCommandIds.push(detectedCommand.id);
1232
+ } else if (canonicalJson(currentCommand) !== canonicalJson(detectedCommand)) {
1233
+ const index = current.commands.findIndex((entry) => entry.id === detectedCommand.id);
1234
+ current.commands[index] = structuredClone(detectedCommand);
1235
+ commands.set(detectedCommand.id, detectedCommand);
1236
+ updatedCommandIds.push(detectedCommand.id);
1237
+ }
1238
+ }
1239
+ current.commands.sort((left, right) => left.id.localeCompare(right.id));
1240
+ if (
1241
+ addedPaths.length > 0
1242
+ || addedCommandIds.length > 0
1243
+ || updatedCommandIds.length > 0
1244
+ ) {
1245
+ updatedProfiles.push({
1246
+ profileId: current.id,
1247
+ addedPaths,
1248
+ addedCommandIds: addedCommandIds.sort(),
1249
+ updatedCommandIds: updatedCommandIds.sort(),
1250
+ });
1251
+ }
1252
+ }
1253
+ return {
1254
+ proposedProfiles: {
1255
+ schemaVersion: "OwlCodaRunKitProfilesV1",
1256
+ profiles,
1257
+ },
1258
+ addedProfileIds,
1259
+ updatedProfiles,
1260
+ };
1261
+ }
1262
+
1263
+ function blockedReconcile(
1264
+ detection,
1265
+ issueCodes,
1266
+ currentProfilesSha256 = null,
1267
+ dryRun = true,
1268
+ ) {
1269
+ return {
1270
+ schemaVersion: "OwlCodaRunKitProfilesReconcilePlanV1",
1271
+ status: "profiles_reconcile_blocked",
1272
+ valid: false,
1273
+ exitCode: 2,
1274
+ dryRun,
1275
+ writesPerformed: 0,
1276
+ detectionSha256: detection?.detectionSha256 ?? null,
1277
+ currentProfilesSha256,
1278
+ issueCodes: [...new Set(issueCodes)].sort(),
1279
+ authorizationGranted: false,
1280
+ };
1281
+ }
1282
+
1283
+ function validateReconcileReceipt(receipt, reconcileSha256) {
1284
+ const hash = /^[a-f0-9]{64}$/;
1285
+ const receiptKeys = [
1286
+ "schemaVersion",
1287
+ "status",
1288
+ "exitCode",
1289
+ "writesPerformed",
1290
+ "dryRun",
1291
+ "reconcileSha256",
1292
+ "detectionSha256",
1293
+ "beforeProfilesSha256",
1294
+ "appliedProfilesSha256",
1295
+ "addedProfileIds",
1296
+ "updatedProfiles",
1297
+ "receiptPath",
1298
+ "issueCodes",
1299
+ "authorizationGranted",
1300
+ ];
1301
+ const receiptPath = path.join(
1302
+ ".owlcoda",
1303
+ "runkit",
1304
+ "profile-reconcile-receipts",
1305
+ `${reconcileSha256}.json`,
1306
+ );
1307
+ if (
1308
+ !exactKeys(receipt, receiptKeys)
1309
+ || receiptKeys.some((key) => !Object.hasOwn(receipt, key))
1310
+ || receipt.schemaVersion !== "OwlCodaRunKitProfilesReconcileReceiptV1"
1311
+ || receipt.status !== "profiles_reconciled"
1312
+ || receipt.exitCode !== 0
1313
+ || receipt.writesPerformed !== 2
1314
+ || receipt.dryRun !== false
1315
+ || receipt.reconcileSha256 !== reconcileSha256
1316
+ || !hash.test(receipt.detectionSha256 ?? "")
1317
+ || !hash.test(receipt.beforeProfilesSha256 ?? "")
1318
+ || !hash.test(receipt.appliedProfilesSha256 ?? "")
1319
+ || !Array.isArray(receipt.addedProfileIds)
1320
+ || receipt.addedProfileIds.some((value) => !safeIdentifier(value))
1321
+ || !Array.isArray(receipt.updatedProfiles)
1322
+ || receipt.updatedProfiles.some((update) => (
1323
+ !exactKeys(update, [
1324
+ "profileId",
1325
+ "addedPaths",
1326
+ "addedCommandIds",
1327
+ "updatedCommandIds",
1328
+ ])
1329
+ || !safeIdentifier(update.profileId)
1330
+ || !Array.isArray(update.addedPaths)
1331
+ || update.addedPaths.some((value) => typeof value !== "string")
1332
+ || !Array.isArray(update.addedCommandIds)
1333
+ || update.addedCommandIds.some((value) => !safeIdentifier(value))
1334
+ || !Array.isArray(update.updatedCommandIds)
1335
+ || update.updatedCommandIds.some((value) => !safeIdentifier(value))
1336
+ ))
1337
+ || receipt.receiptPath !== receiptPath
1338
+ || !Array.isArray(receipt.issueCodes)
1339
+ || receipt.issueCodes.length !== 0
1340
+ || receipt.authorizationGranted !== false
1341
+ ) {
1342
+ throw new Error("profile_reconcile_transaction_receipt_invalid");
1343
+ }
1344
+ return receiptPath;
1345
+ }
1346
+
1347
+ function recoverProfileReconcileTransaction(root) {
1348
+ const runtimeRoot = path.join(root, ".owlcoda", "runkit");
1349
+ if (!existsSync(runtimeRoot)) return null;
1350
+ trustedDirectoryInsideRoot(root, runtimeRoot);
1351
+ const transactionNames = readdirSync(runtimeRoot)
1352
+ .filter((entry) => entry.startsWith(".profile-reconcile-") && entry.endsWith(".lock"));
1353
+ if (transactionNames.length === 0) return null;
1354
+ if (
1355
+ transactionNames.length !== 1
1356
+ || !/^\.profile-reconcile-[a-f0-9]{64}\.lock$/u.test(transactionNames[0])
1357
+ ) {
1358
+ throw new Error("profile_reconcile_transaction_set_invalid");
1359
+ }
1360
+ const transactionName = transactionNames[0];
1361
+ const reconcileSha256 = transactionName.slice(
1362
+ ".profile-reconcile-".length,
1363
+ -".lock".length,
1364
+ );
1365
+ const transactionRoot = path.join(runtimeRoot, transactionName);
1366
+ trustedDirectoryInsideRoot(root, transactionRoot);
1367
+ const entries = readdirSync(transactionRoot).sort();
1368
+ if (
1369
+ entries.some((entry) => ![
1370
+ "previous-profiles.json",
1371
+ "profiles.json",
1372
+ "receipt.json",
1373
+ ].includes(entry))
1374
+ ) {
1375
+ throw new Error("profile_reconcile_transaction_contents_untrusted");
1376
+ }
1377
+ const previousProfiles = path.join(transactionRoot, "previous-profiles.json");
1378
+ const stagedProfiles = path.join(transactionRoot, "profiles.json");
1379
+ const stagedReceipt = path.join(transactionRoot, "receipt.json");
1380
+ const receiptRoot = ensureDirectoryChain(
1381
+ root,
1382
+ ".owlcoda/runkit/profile-reconcile-receipts",
1383
+ );
1384
+ const receiptAbsolutePath = path.join(receiptRoot, `${reconcileSha256}.json`);
1385
+ const receiptSource = existsSync(stagedReceipt) ? stagedReceipt : receiptAbsolutePath;
1386
+ const receipt = JSON.parse(readRegularFileInsideRoot(root, receiptSource));
1387
+ const receiptPath = validateReconcileReceipt(receipt, reconcileSha256);
1388
+ if (path.join(root, receiptPath) !== receiptAbsolutePath) {
1389
+ throw new Error("profile_reconcile_transaction_receipt_path_invalid");
1390
+ }
1391
+ const receiptBytes = `${JSON.stringify(receipt, null, 2)}\n`;
1392
+ if (!exactRegularFileBytes(receiptSource, receiptBytes)) {
1393
+ throw new Error("profile_reconcile_transaction_receipt_mismatch");
1394
+ }
1395
+ const previousBytes = readRegularFileInsideRoot(root, previousProfiles);
1396
+ if (sha256(previousBytes) !== receipt.beforeProfilesSha256) {
1397
+ throw new Error("profile_reconcile_transaction_preimage_mismatch");
1398
+ }
1399
+ const profilesPath = path.join(runtimeRoot, "profiles.json");
1400
+ const currentProfilesSha256 = profilesFileHash(root);
1401
+ if (
1402
+ currentProfilesSha256 !== receipt.beforeProfilesSha256
1403
+ && currentProfilesSha256 !== receipt.appliedProfilesSha256
1404
+ ) {
1405
+ throw new Error("profile_reconcile_transaction_profile_mismatch");
1406
+ }
1407
+ if (existsSync(stagedProfiles)) {
1408
+ const stagedProfilesBytes = readRegularFileInsideRoot(root, stagedProfiles);
1409
+ if (sha256(stagedProfilesBytes) !== receipt.appliedProfilesSha256) {
1410
+ throw new Error("profile_reconcile_transaction_staged_profile_mismatch");
1411
+ }
1412
+ } else if (currentProfilesSha256 !== receipt.appliedProfilesSha256) {
1413
+ throw new Error("profile_reconcile_transaction_staged_profile_missing");
1414
+ }
1415
+ if (
1416
+ existsSync(receiptAbsolutePath)
1417
+ && !exactRegularFileBytes(receiptAbsolutePath, receiptBytes)
1418
+ ) {
1419
+ throw new Error("profile_reconcile_transaction_committed_receipt_mismatch");
1420
+ }
1421
+
1422
+ let profilesCommittedByRecovery = false;
1423
+ try {
1424
+ if (currentProfilesSha256 === receipt.beforeProfilesSha256) {
1425
+ renameSync(stagedProfiles, profilesPath);
1426
+ profilesCommittedByRecovery = true;
1427
+ }
1428
+ if (!existsSync(receiptAbsolutePath)) {
1429
+ commitCreateOnlyStagedFile(stagedReceipt, receiptAbsolutePath, receiptBytes);
1430
+ }
1431
+ } catch (error) {
1432
+ if (
1433
+ profilesCommittedByRecovery
1434
+ && profilesFileHash(root) === receipt.appliedProfilesSha256
1435
+ ) {
1436
+ renameSync(previousProfiles, profilesPath);
1437
+ }
1438
+ throw error;
1439
+ }
1440
+ rmSync(transactionRoot, { recursive: true });
1441
+ return receipt;
1442
+ }
1443
+
1444
+ export function reconcileProfilesV2({
1445
+ workspaceRoot,
1446
+ apply = false,
1447
+ projectToolResolver,
1448
+ onAfterProfilesCommit,
1449
+ } = {}) {
1450
+ const root = resolveBoundedWorkspaceRoot(workspaceRoot);
1451
+ if (apply) {
1452
+ try {
1453
+ const recovered = recoverProfileReconcileTransaction(root);
1454
+ if (recovered !== null) return recovered;
1455
+ } catch {
1456
+ return blockedReconcile(
1457
+ null,
1458
+ ["profile_reconcile_transaction_recovery_invalid"],
1459
+ null,
1460
+ false,
1461
+ );
1462
+ }
1463
+ }
1464
+ let currentProfilesSha256;
1465
+ let currentProfiles;
1466
+ let currentProfilesBytes;
1467
+ try {
1468
+ currentProfilesSha256 = profilesFileHash(root);
1469
+ if (currentProfilesSha256 === null) {
1470
+ return blockedReconcile(null, ["profiles_missing"], null, !apply);
1471
+ }
1472
+ currentProfilesBytes = readFileSync(boundedRegularFile(
1473
+ root,
1474
+ path.join(".owlcoda", "runkit", "profiles.json"),
1475
+ ), "utf8");
1476
+ currentProfiles = readWorkspaceJsonBounded(
1477
+ root,
1478
+ path.join(".owlcoda", "runkit", "profiles.json"),
1479
+ );
1480
+ const documentIssues = validateProfileDocument(currentProfiles);
1481
+ if (documentIssues.length > 0) {
1482
+ return blockedReconcile(null, documentIssues, currentProfilesSha256, !apply);
1483
+ }
1484
+ } catch {
1485
+ return blockedReconcile(
1486
+ null,
1487
+ ["profile_reconcile_current_profiles_invalid"],
1488
+ null,
1489
+ !apply,
1490
+ );
1491
+ }
1492
+
1493
+ const detection = detectProfilesV2({ workspaceRoot: root, projectToolResolver });
1494
+ const detectionIssues = verifyDetectionInputs(root, detection, {
1495
+ allowedApplyStatuses: ["profiles_already_exists"],
1496
+ });
1497
+ if (detection.currentProfilesSha256 !== currentProfilesSha256) {
1498
+ detectionIssues.push("profile_reconcile_detection_preimage_mismatch");
1499
+ }
1500
+ if (detectionIssues.length > 0) {
1501
+ return blockedReconcile(detection, detectionIssues, currentProfilesSha256, !apply);
1502
+ }
1503
+
1504
+ const merged = mergeDetectedProfiles(currentProfiles, detection.proposedProfiles);
1505
+ const proposedIssues = validateProfileDocumentWithDetectedSurfaces(
1506
+ root,
1507
+ merged.proposedProfiles,
1508
+ );
1509
+ if (proposedIssues.length > 0) {
1510
+ return blockedReconcile(detection, proposedIssues, currentProfilesSha256, !apply);
1511
+ }
1512
+ const proposedProfilesBytes = `${JSON.stringify(merged.proposedProfiles, null, 2)}\n`;
1513
+ const proposedProfilesSha256 = sha256(proposedProfilesBytes);
1514
+ const planBody = {
1515
+ detectionSha256: detection.detectionSha256,
1516
+ currentProfilesSha256,
1517
+ proposedProfilesSha256,
1518
+ addedProfileIds: merged.addedProfileIds,
1519
+ updatedProfiles: merged.updatedProfiles,
1520
+ };
1521
+ const reconcileSha256 = sha256(canonicalJson(planBody));
1522
+ const plan = {
1523
+ schemaVersion: "OwlCodaRunKitProfilesReconcilePlanV1",
1524
+ status: proposedProfilesSha256 === currentProfilesSha256
1525
+ ? "profiles_already_current"
1526
+ : "profiles_reconcile_ready",
1527
+ valid: true,
1528
+ exitCode: 0,
1529
+ dryRun: !apply,
1530
+ writesPerformed: 0,
1531
+ reconcileSha256,
1532
+ ...planBody,
1533
+ proposedProfiles: merged.proposedProfiles,
1534
+ issueCodes: [],
1535
+ authorizationGranted: false,
1536
+ };
1537
+ if (!apply || proposedProfilesSha256 === currentProfilesSha256) return plan;
1538
+
1539
+ let runtimeRoot;
1540
+ let receiptRoot;
1541
+ try {
1542
+ runtimeRoot = ensureDirectoryChain(root, ".owlcoda/runkit");
1543
+ receiptRoot = ensureDirectoryChain(
1544
+ root,
1545
+ ".owlcoda/runkit/profile-reconcile-receipts",
1546
+ );
1547
+ } catch {
1548
+ return blockedReconcile(
1549
+ detection,
1550
+ ["profile_reconcile_directory_untrusted"],
1551
+ currentProfilesSha256,
1552
+ false,
1553
+ );
1554
+ }
1555
+ const profilesPath = path.join(runtimeRoot, "profiles.json");
1556
+ const receiptPath = path.join(
1557
+ ".owlcoda",
1558
+ "runkit",
1559
+ "profile-reconcile-receipts",
1560
+ `${reconcileSha256}.json`,
1561
+ );
1562
+ const receiptAbsolutePath = path.join(receiptRoot, `${reconcileSha256}.json`);
1563
+ const receipt = {
1564
+ schemaVersion: "OwlCodaRunKitProfilesReconcileReceiptV1",
1565
+ status: "profiles_reconciled",
1566
+ exitCode: 0,
1567
+ writesPerformed: 2,
1568
+ dryRun: false,
1569
+ reconcileSha256,
1570
+ detectionSha256: detection.detectionSha256,
1571
+ beforeProfilesSha256: currentProfilesSha256,
1572
+ appliedProfilesSha256: proposedProfilesSha256,
1573
+ addedProfileIds: merged.addedProfileIds,
1574
+ updatedProfiles: merged.updatedProfiles,
1575
+ receiptPath,
1576
+ issueCodes: [],
1577
+ authorizationGranted: false,
1578
+ };
1579
+ const receiptBytes = `${JSON.stringify(receipt, null, 2)}\n`;
1580
+ const transactionRoot = path.join(
1581
+ runtimeRoot,
1582
+ `.profile-reconcile-${reconcileSha256}.lock`,
1583
+ );
1584
+ try {
1585
+ mkdirSync(transactionRoot);
1586
+ } catch (error) {
1587
+ return blockedReconcile(
1588
+ detection,
1589
+ [error?.code === "EEXIST"
1590
+ ? "profile_reconcile_transaction_active"
1591
+ : "profile_reconcile_transaction_failed"],
1592
+ currentProfilesSha256,
1593
+ false,
1594
+ );
1595
+ }
1596
+ let profilesCommitted = false;
1597
+ let preserveTransaction = false;
1598
+ try {
1599
+ if (profilesFileHash(root) !== currentProfilesSha256) {
1600
+ throw new Error("profile_reconcile_preimage_changed");
1601
+ }
1602
+ if (existsSync(receiptAbsolutePath)) {
1603
+ throw new Error("profile_reconcile_receipt_already_exists");
1604
+ }
1605
+ const previousProfiles = path.join(transactionRoot, "previous-profiles.json");
1606
+ const stagedProfiles = path.join(transactionRoot, "profiles.json");
1607
+ const stagedReceipt = path.join(transactionRoot, "receipt.json");
1608
+ writeFileSync(previousProfiles, currentProfilesBytes, { flag: "wx" });
1609
+ writeFileSync(stagedProfiles, proposedProfilesBytes, { flag: "wx" });
1610
+ writeFileSync(stagedReceipt, receiptBytes, { flag: "wx" });
1611
+ if (!exactRegularFileBytes(stagedProfiles, proposedProfilesBytes)) {
1612
+ throw new Error("profile_reconcile_staged_profiles_invalid");
1613
+ }
1614
+ renameSync(stagedProfiles, profilesPath);
1615
+ profilesCommitted = true;
1616
+ onAfterProfilesCommit?.();
1617
+ commitCreateOnlyStagedFile(stagedReceipt, receiptAbsolutePath, receiptBytes);
1618
+ return receipt;
1619
+ } catch {
1620
+ let issueCode = "profile_reconcile_transaction_failed";
1621
+ if (
1622
+ profilesCommitted
1623
+ && profilesFileHash(root) === proposedProfilesSha256
1624
+ ) {
1625
+ const previousProfiles = path.join(transactionRoot, "previous-profiles.json");
1626
+ try {
1627
+ if (!exactRegularFileBytes(previousProfiles, currentProfilesBytes)) {
1628
+ throw new Error("profile_reconcile_previous_profiles_invalid");
1629
+ }
1630
+ renameSync(previousProfiles, profilesPath);
1631
+ } catch {
1632
+ preserveTransaction = true;
1633
+ issueCode = "profile_reconcile_transaction_rollback_failed";
1634
+ }
1635
+ } else if (profilesCommitted) {
1636
+ preserveTransaction = true;
1637
+ issueCode = "profile_reconcile_transaction_rollback_failed";
1638
+ }
1639
+ return blockedReconcile(
1640
+ detection,
1641
+ [issueCode],
1642
+ currentProfilesSha256,
1643
+ false,
1644
+ );
1645
+ } finally {
1646
+ if (!preserveTransaction) {
1647
+ rmSync(transactionRoot, { recursive: true, force: true });
1648
+ }
1649
+ }
1650
+ }
1651
+
1108
1652
  function exactKeys(value, allowed) {
1109
1653
  return value
1110
1654
  && typeof value === "object"
@@ -1241,6 +1785,53 @@ function validateProfileDocument(config) {
1241
1785
  return [...new Set(issues)].sort();
1242
1786
  }
1243
1787
 
1788
+ function profileLauncherIssues(config) {
1789
+ const issues = [];
1790
+ for (const profile of config.profiles ?? []) {
1791
+ for (const command of profile.commands ?? []) {
1792
+ if (typeof command.executable !== "string" || !path.isAbsolute(command.executable)) {
1793
+ continue;
1794
+ }
1795
+ const identity = `${profile.id ?? "unknown"}:${command.id ?? "unknown"}`;
1796
+ try {
1797
+ const stat = lstatSync(command.executable);
1798
+ if (stat.isSymbolicLink()) {
1799
+ issues.push(`profile_launcher_symlink_rejected:${identity}`);
1800
+ } else if (
1801
+ !stat.isFile()
1802
+ || realpathSync(command.executable) !== path.resolve(command.executable)
1803
+ ) {
1804
+ issues.push(`profile_launcher_not_regular_file:${identity}`);
1805
+ }
1806
+ } catch {
1807
+ issues.push(`profile_launcher_unavailable:${identity}`);
1808
+ }
1809
+ }
1810
+ }
1811
+ return issues;
1812
+ }
1813
+
1814
+ function validateProfileDocumentWithDetectedSurfaces(root, config) {
1815
+ const issues = validateProfileDocument(config);
1816
+ if (issues.length > 0) return issues;
1817
+ issues.push(...profileLauncherIssues(config));
1818
+ const detected = detectProfiles({ workspaceRoot: root });
1819
+ const detectedPaths = detected.candidates.flatMap((profile) =>
1820
+ profile.paths.map(representativePath));
1821
+ if (detectedPaths.length > 0) {
1822
+ const impact = resolveProfileImpactDetailed({
1823
+ changedPaths: detectedPaths,
1824
+ profiles: config.profiles,
1825
+ });
1826
+ issues.push(...impact.uncoveredPaths.map((value) =>
1827
+ `uncovered_detected_surface:${value}`));
1828
+ issues.push(...impact.warnings
1829
+ .filter((warning) => warning.startsWith("supporting_only_match:"))
1830
+ .map((warning) => `detected_surface_${warning}`));
1831
+ }
1832
+ return [...new Set(issues)].sort();
1833
+ }
1834
+
1244
1835
  export function validateProfiles({ workspaceRoot } = {}) {
1245
1836
  const root = resolveBoundedWorkspaceRoot(workspaceRoot);
1246
1837
  const issues = [];
@@ -1256,23 +1847,7 @@ export function validateProfiles({ workspaceRoot } = {}) {
1256
1847
  .filter((id) => typeof id === "string")
1257
1848
  .sort()
1258
1849
  : [];
1259
- issues.push(...validateProfileDocument(config));
1260
- if (issues.length === 0) {
1261
- const detected = detectProfiles({ workspaceRoot: root });
1262
- const detectedPaths = detected.candidates.flatMap((profile) =>
1263
- profile.paths.map(representativePath));
1264
- if (detectedPaths.length > 0) {
1265
- const impact = resolveProfileImpactDetailed({
1266
- changedPaths: detectedPaths,
1267
- profiles: config.profiles,
1268
- });
1269
- issues.push(...impact.uncoveredPaths.map((value) =>
1270
- `uncovered_detected_surface:${value}`));
1271
- issues.push(...impact.warnings
1272
- .filter((warning) => warning.startsWith("supporting_only_match:"))
1273
- .map((warning) => `detected_surface_${warning}`));
1274
- }
1275
- }
1850
+ issues.push(...validateProfileDocumentWithDetectedSurfaces(root, config));
1276
1851
  } catch (error) {
1277
1852
  issues.push(error instanceof Error ? error.message : String(error));
1278
1853
  }
@@ -18,6 +18,9 @@ const OFFICIAL_CLI_ENTRYPOINT =
18
18
  const EXACT_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/;
19
19
  const SHA256_REF = /^sha256:[a-f0-9]{64}$/;
20
20
  const TRUSTED_PRIOR_CORE_IDENTITIES = new Map([
21
+ ["0.18.0", new Set([
22
+ "sha256:72c481dbafe06a9c56cd940d74a3fe109a82b73a3c43d0bb54923ea6c584c04e",
23
+ ])],
21
24
  ["0.17.2", new Set([
22
25
  "sha256:67b883b8a763253b873fb6047c7e7e01c81123aa5250a0db5feaabd13cc4d860",
23
26
  ])],
@@ -1,4 +1,5 @@
1
1
  export {
2
2
  attestQuickReceipt,
3
3
  attestQuickReceiptDetails,
4
+ validQuickReceiptShape,
4
5
  } from "../../packages/attest/src/quick.mjs";