release-skill 0.9.15 → 0.9.17

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 (164) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/.kimi-plugin/plugin.json +1 -1
  5. package/.qoder-plugin/plugin.json +10 -0
  6. package/CHANGELOG.md +61 -0
  7. package/INSTALL.md +57 -9
  8. package/INSTALL.zh-CN.md +44 -7
  9. package/README.md +88 -29
  10. package/README.zh-CN.md +70 -27
  11. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  12. package/adapters/claude/bin/release-skill.bundle.mjs +1471 -672
  13. package/adapters/claude/schemas/release-plan.schema.json +1 -1
  14. package/adapters/claude/schemas/release-project.schema.json +19 -1
  15. package/adapters/claude/skills/release-finish/SKILL.md +65 -6
  16. package/adapters/claude/skills/release-help/SKILL.md +3 -3
  17. package/adapters/claude/skills/release-verify/SKILL.md +2 -2
  18. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  19. package/adapters/codex/bin/release-skill.bundle.mjs +1471 -672
  20. package/adapters/codex/schemas/release-plan.schema.json +1 -1
  21. package/adapters/codex/schemas/release-project.schema.json +19 -1
  22. package/adapters/codex/skills/release-finish/SKILL.md +65 -6
  23. package/adapters/codex/skills/release-help/SKILL.md +3 -3
  24. package/adapters/codex/skills/release-verify/SKILL.md +2 -2
  25. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  26. package/adapters/kimi/bin/release-skill.bundle.mjs +1471 -672
  27. package/adapters/kimi/schemas/release-plan.schema.json +1 -1
  28. package/adapters/kimi/schemas/release-project.schema.json +19 -1
  29. package/adapters/kimi/skills/release-finish/SKILL.md +65 -6
  30. package/adapters/kimi/skills/release-help/SKILL.md +3 -3
  31. package/adapters/kimi/skills/release-verify/SKILL.md +2 -2
  32. package/adapters/qoder/.qoder-plugin/plugin.json +10 -0
  33. package/adapters/qoder/bin/consumer-contract-vectors.json +110 -0
  34. package/adapters/qoder/bin/error-codes.json +161 -0
  35. package/adapters/qoder/bin/foundation-legal/skill-family-contracts/LICENSE +202 -0
  36. package/adapters/qoder/bin/foundation-legal/skill-family-contracts/NOTICE +10 -0
  37. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/LICENSE +202 -0
  38. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/NOTICE +10 -0
  39. package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/THIRD_PARTY_NOTICES +142 -0
  40. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/LICENSE +202 -0
  41. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/NOTICE +10 -0
  42. package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/native-NOTICE +2 -0
  43. package/adapters/qoder/bin/foundation-resource-binding.json +1 -0
  44. package/adapters/qoder/bin/kernel-protocol.json +60 -0
  45. package/adapters/qoder/bin/license-texts/Apache-2.0.txt +201 -0
  46. package/adapters/qoder/bin/license-texts/MIT.txt +21 -0
  47. package/adapters/qoder/bin/prebuild-manifest.json +79 -0
  48. package/adapters/qoder/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
  49. package/adapters/qoder/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
  50. package/adapters/qoder/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
  51. package/adapters/qoder/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
  52. package/adapters/qoder/bin/registry.json +347 -0
  53. package/adapters/qoder/bin/release-skill-local-finish.mjs +4 -0
  54. package/adapters/qoder/bin/release-skill.bundle.mjs +149446 -0
  55. package/adapters/qoder/bin/release-skill.mjs +54 -0
  56. package/adapters/qoder/bin/rules.json +104 -0
  57. package/adapters/qoder/bin/schemas/consumer-contract-vector.schema.json +149 -0
  58. package/adapters/qoder/data/hosts/claude/host-descriptor.json +45 -0
  59. package/adapters/qoder/data/hosts/codebuddy/host-descriptor.json +26 -0
  60. package/adapters/qoder/data/hosts/codex/host-descriptor.json +45 -0
  61. package/adapters/qoder/data/hosts/deepseek-harness/host-descriptor.json +23 -0
  62. package/adapters/qoder/data/hosts/kimi-code/host-descriptor.json +36 -0
  63. package/adapters/qoder/data/hosts/qoder/host-descriptor.json +26 -0
  64. package/adapters/qoder/data/hosts/registry.json +13 -0
  65. package/adapters/qoder/data/hosts/workbuddy/host-descriptor.json +34 -0
  66. package/adapters/qoder/data/licensing/registry.json +211 -0
  67. package/adapters/qoder/data/licensing/schema.json +207 -0
  68. package/adapters/qoder/native/safe-write/binding.gyp +41 -0
  69. package/adapters/qoder/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  70. package/adapters/qoder/native/safe-write/prebuilds.json +24 -0
  71. package/adapters/qoder/native/safe-write/src/safe_write.cc +2032 -0
  72. package/adapters/qoder/schemas/.render-manifest.json +41 -0
  73. package/adapters/qoder/schemas/approval-record.schema.json +115 -0
  74. package/adapters/qoder/schemas/artifact-lock.schema.json +111 -0
  75. package/adapters/qoder/schemas/artifact-plan.schema.json +52 -0
  76. package/adapters/qoder/schemas/artifact-policy.schema.json +76 -0
  77. package/adapters/qoder/schemas/evidence-event-v2.schema.json +164 -0
  78. package/adapters/qoder/schemas/evidence-event.schema.json +89 -0
  79. package/adapters/qoder/schemas/postpublish-approval-record.schema.json +47 -0
  80. package/adapters/qoder/schemas/release-plan.schema.json +1996 -0
  81. package/adapters/qoder/schemas/release-project.schema.json +1613 -0
  82. package/adapters/qoder/schemas/release-run.schema.json +777 -0
  83. package/adapters/qoder/skills/release-assess/SKILL.md +60 -0
  84. package/adapters/qoder/skills/release-config/SKILL.md +150 -0
  85. package/adapters/qoder/skills/release-docs/SKILL.md +119 -0
  86. package/adapters/qoder/skills/release-finish/SKILL.md +141 -0
  87. package/adapters/qoder/skills/release-help/SKILL.md +153 -0
  88. package/adapters/qoder/skills/release-marketplace/SKILL.md +156 -0
  89. package/adapters/qoder/skills/release-prepare/SKILL.md +135 -0
  90. package/adapters/qoder/skills/release-publish/SKILL.md +91 -0
  91. package/adapters/qoder/skills/release-reconcile/SKILL.md +80 -0
  92. package/adapters/qoder/skills/release-setup/SKILL.md +131 -0
  93. package/adapters/qoder/skills/release-verify/SKILL.md +109 -0
  94. package/adapters/qoder/src/schemas/adapter-build-manifest.schema.json +41 -0
  95. package/adapters/qoder/src/schemas/adapter-peer-verification-request.schema.json +54 -0
  96. package/adapters/qoder/src/schemas/adapter-peer-verification-result.schema.json +54 -0
  97. package/adapters/qoder/src/schemas/adapter-source.schema.json +47 -0
  98. package/adapters/qoder/src/schemas/audit-baseline-pin.schema.json +163 -0
  99. package/adapters/qoder/src/schemas/declared-read-surface-result.schema.json +61 -0
  100. package/adapters/qoder/src/schemas/engineering-baseline.schema.json +77 -0
  101. package/adapters/qoder/src/schemas/executable-identity-observation.schema.json +162 -0
  102. package/adapters/qoder/src/schemas/filesystem-root-binding.schema.json +14 -0
  103. package/adapters/qoder/src/schemas/filesystem-tree-observation.schema.json +71 -0
  104. package/adapters/qoder/src/schemas/fixed-set-publication-manifest.schema.json +108 -0
  105. package/adapters/qoder/src/schemas/fixed-set-publication-receipt.schema.json +148 -0
  106. package/adapters/qoder/src/schemas/host-capability-fact.schema.json +31 -0
  107. package/adapters/qoder/src/schemas/host-descriptor.schema.json +137 -0
  108. package/adapters/qoder/src/schemas/host-operation-plan.schema.json +57 -0
  109. package/adapters/qoder/src/schemas/host-operation-receipt.schema.json +81 -0
  110. package/adapters/qoder/src/schemas/host-probe-result.schema.json +30 -0
  111. package/adapters/qoder/src/schemas/host-registry.schema.json +18 -0
  112. package/adapters/qoder/src/schemas/host-verification-request.schema.json +67 -0
  113. package/adapters/qoder/src/schemas/host-verification-result.schema.json +149 -0
  114. package/adapters/qoder/src/schemas/managed-file-lock.schema.json +77 -0
  115. package/adapters/qoder/src/schemas/migration-manifest.schema.json +412 -0
  116. package/adapters/qoder/src/schemas/observation-scope.schema.json +91 -0
  117. package/adapters/qoder/src/schemas/operation-request.schema.json +58 -0
  118. package/adapters/qoder/src/schemas/operation-result.schema.json +130 -0
  119. package/adapters/qoder/src/schemas/platform-difference-registry.schema.json +110 -0
  120. package/adapters/qoder/src/schemas/plugin-verification-request.schema.json +499 -0
  121. package/adapters/qoder/src/schemas/plugin-verification-result.schema.json +1217 -0
  122. package/adapters/qoder/src/schemas/profile-adoption-declaration.schema.json +111 -0
  123. package/adapters/qoder/src/schemas/profile-descriptor.schema.json +99 -0
  124. package/adapters/qoder/src/schemas/project-manifest.schema.json +67 -0
  125. package/adapters/qoder/src/schemas/project-profile.schema.json +25 -0
  126. package/adapters/qoder/src/schemas/public-boundary-declaration.schema.json +73 -0
  127. package/adapters/qoder/src/schemas/report-binding.schema.json +42 -0
  128. package/adapters/qoder/src/schemas/report-model.schema.json +308 -0
  129. package/adapters/qoder/src/schemas/skill-family-directory-verification-request.schema.json +100 -0
  130. package/adapters/qoder/src/schemas/skill-family-directory-verification-result.schema.json +193 -0
  131. package/adapters/qoder/src/schemas/source-authority-receipt.schema.json +31 -0
  132. package/adapters/qoder/src/schemas/state-event-envelope.schema.json +38 -0
  133. package/adapters/qoder/src/schemas/state-snapshot-metadata.schema.json +25 -0
  134. package/adapters/qoder/src/schemas/structured-scan-policy.schema.json +97 -0
  135. package/adapters/qoder/src/schemas/surface-scan-policy.schema.json +42 -0
  136. package/adapters/qoder/src/schemas/timeout-policy.schema.json +24 -0
  137. package/adapters/qoder/src/schemas/token-estimate-record.schema.json +75 -0
  138. package/adapters/qoder/src/schemas/token-estimate-result.schema.json +43 -0
  139. package/adapters/qoder/src/schemas/watchdog-termination-envelope.schema.json +156 -0
  140. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  141. package/adapters/workbuddy/bin/release-skill.bundle.mjs +1471 -672
  142. package/adapters/workbuddy/schemas/release-plan.schema.json +1 -1
  143. package/adapters/workbuddy/schemas/release-project.schema.json +19 -1
  144. package/adapters/workbuddy/skills/release-finish/SKILL.md +65 -6
  145. package/adapters/workbuddy/skills/release-help/SKILL.md +3 -3
  146. package/adapters/workbuddy/skills/release-verify/SKILL.md +2 -2
  147. package/bin/release-skill-cli.mjs +152 -6
  148. package/bin/release-skill.bundle.mjs +1471 -672
  149. package/package.json +4 -2
  150. package/platform-manifest.json +70 -4
  151. package/references/02-project-config.md +25 -2
  152. package/schemas/release-plan.schema.json +1 -1
  153. package/schemas/release-project.schema.json +19 -1
  154. package/skills/release-finish/SKILL.md +65 -6
  155. package/skills/release-help/SKILL.md +3 -3
  156. package/skills/release-verify/SKILL.md +2 -2
  157. package/skills-src/release-finish/SKILL.md +65 -6
  158. package/skills-src/release-help/SKILL.md +3 -3
  159. package/skills-src/release-verify/SKILL.md +2 -2
  160. package/src/commands/post-release-local.mjs +360 -51
  161. package/src/commands/verify-records.mjs +467 -0
  162. package/src/core/postpublish.mjs +1 -1
  163. package/src/core/run.mjs +1 -1
  164. package/src/producers/build-adapters.mjs +36 -13
@@ -9,9 +9,9 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
9
9
  // Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
10
10
  const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
11
11
  // Package identity injected at build time — closure-independent --version probe.
12
- const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.15"});
12
+ const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.17"});
13
13
  // Build-time source digest for the BUNDLE_STALE freshness gate (see above).
14
- const __bundleSourceDigest = "e47969ed810fa0edbed7073f13df855be95feed7ffecc40f72dd887cab52ff97";
14
+ const __bundleSourceDigest = "8d2e6384871f9dee0d333c03e859296020ef69fc38efddb196fd7aecf76511d7";
15
15
 
16
16
  var __create = Object.create;
17
17
  var __defProp = Object.defineProperty;
@@ -36109,6 +36109,13 @@ var require_dist3 = __commonJS({
36109
36109
  }
36110
36110
  });
36111
36111
 
36112
+ // src/core/foundation-inflight.mjs
36113
+ var init_foundation_inflight = __esm({
36114
+ "src/core/foundation-inflight.mjs"() {
36115
+ init_src2();
36116
+ }
36117
+ });
36118
+
36112
36119
  // src/core/digest.mjs
36113
36120
  function canonicalJson2(obj) {
36114
36121
  const normalized = normalizeLenient(obj);
@@ -36160,13 +36167,6 @@ var init_digest = __esm({
36160
36167
  }
36161
36168
  });
36162
36169
 
36163
- // src/core/foundation-inflight.mjs
36164
- var init_foundation_inflight = __esm({
36165
- "src/core/foundation-inflight.mjs"() {
36166
- init_src2();
36167
- }
36168
- });
36169
-
36170
36170
  // src/core/pkg-root.mjs
36171
36171
  import { dirname, resolve } from "node:path";
36172
36172
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -43519,604 +43519,11 @@ var init_plan = __esm({
43519
43519
  }
43520
43520
  });
43521
43521
 
43522
- // src/core/run.mjs
43523
- var run_exports = {};
43524
- __export(run_exports, {
43525
- appendRunState: () => appendRunState,
43526
- assertImmutableRunAuthority: () => assertImmutableRunAuthority,
43527
- computeRunDigest: () => computeRunDigest,
43528
- createProductionPrepareRunDir: () => createProductionPrepareRunDir,
43529
- createProductionRunDir: () => createProductionRunDir,
43530
- loadRun: () => loadRun,
43531
- resolveDefaultRunDir: () => resolveDefaultRunDir,
43532
- resolveRunPath: () => resolveRunPath,
43533
- validateRun: () => validateRun,
43534
- validateRunCheckpointMapping: () => validateRunCheckpointMapping,
43535
- validateRunLineage: () => validateRunLineage,
43536
- validateRunPlanDigest: () => validateRunPlanDigest,
43537
- writeRunAtomic: () => writeRunAtomic
43538
- });
43539
- import { lstat as lstat27, mkdir as mkdir10, readFile as readFile18, stat as stat10 } from "node:fs/promises";
43540
- import { realpathSync as realpathSync3 } from "node:fs";
43541
- import { dirname as dirname4, join as join7, resolve as resolve8, basename as basename4, relative as relative7, isAbsolute as isAbsolute5 } from "node:path";
43542
- function resolveDefaultRunDir(planPath, command2, runId = `${command2}-${Date.now()}`) {
43543
- const absolute2 = resolve8(planPath);
43544
- const fileName = basename4(absolute2);
43545
- const parentDir = dirname4(absolute2);
43546
- if (fileName === "release-plan.json") {
43547
- return `${parentDir}/runs/${runId}`;
43548
- }
43549
- if (basename4(parentDir) === "plans") {
43550
- const releaseDir = dirname4(parentDir);
43551
- return join7(releaseDir, "runs", runId);
43552
- }
43553
- return `${parentDir}/runs/${runId}`;
43554
- }
43555
- function validateRun(run6, options = {}) {
43556
- const valid = validateRunSchema(run6);
43557
- if (!valid) {
43558
- const errors = validateRunSchema.errors ?? [];
43559
- const summary = errors.map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
43560
- throw new ReleaseError(
43561
- GATE_FAILED,
43562
- `release run schema validation failed: ${summary}`,
43563
- { validationErrors: errors }
43564
- );
43565
- }
43566
- const actionIds = /* @__PURE__ */ new Set();
43567
- for (const checkpoint of run6.checkpoints ?? []) {
43568
- if (actionIds.has(checkpoint.actionId)) {
43569
- throw new ReleaseError(
43570
- GATE_FAILED,
43571
- `release run has duplicate checkpoint for action "${checkpoint.actionId}"`,
43572
- { actionId: checkpoint.actionId }
43573
- );
43574
- }
43575
- actionIds.add(checkpoint.actionId);
43576
- }
43577
- if (options.requireDigest === true && !run6.runDigest) {
43578
- throw new ReleaseError(GATE_FAILED, "release run is missing required runDigest");
43579
- }
43580
- if (run6.runDigest && run6.runDigest !== computeRunDigest(run6)) {
43581
- throw new ReleaseError(
43582
- GATE_FAILED,
43583
- "release run digest does not match its content",
43584
- { expected: computeRunDigest(run6), actual: run6.runDigest }
43585
- );
43586
- }
43587
- }
43588
- async function resolveRunPath(runPath) {
43589
- let stats;
43590
- try {
43591
- stats = await lstat27(runPath);
43592
- } catch (err) {
43593
- if (err?.code === "ENOENT") return runPath;
43594
- throw new ReleaseError(
43595
- GATE_FAILED,
43596
- `cannot inspect release run path: ${err.message}`,
43597
- { runPath, cause: err.code }
43598
- );
43599
- }
43600
- if (stats.isFile()) return runPath;
43601
- if (stats.isSymbolicLink()) {
43602
- let targetStats;
43603
- try {
43604
- targetStats = await stat10(runPath);
43605
- } catch (err) {
43606
- if (err?.code === "ENOENT") return runPath;
43607
- throw new ReleaseError(
43608
- GATE_FAILED,
43609
- `cannot inspect release run path: ${err.message}`,
43610
- { runPath, cause: err.code }
43611
- );
43612
- }
43613
- if (targetStats.isDirectory()) {
43614
- throw new ReleaseError(
43615
- GATE_FAILED,
43616
- "release run path is a symlinked directory; pass the run file (release-run.json) or the real run directory",
43617
- { runPath }
43618
- );
43619
- }
43620
- return runPath;
43621
- }
43622
- if (stats.isDirectory()) {
43623
- const candidate = join7(runPath, "release-run.json");
43624
- try {
43625
- const candidateStats = await lstat27(candidate);
43626
- if (!candidateStats.isFile()) {
43627
- throw new ReleaseError(
43628
- GATE_FAILED,
43629
- `release run directory does not contain a regular release-run.json: ${runPath}`,
43630
- { runPath, expected: "release-run.json" }
43631
- );
43632
- }
43633
- } catch (err) {
43634
- if (err instanceof ReleaseError) throw err;
43635
- throw new ReleaseError(
43636
- GATE_FAILED,
43637
- `release run directory must contain release-run.json: ${err.message}`,
43638
- { runPath, expected: "release-run.json", cause: err.code }
43639
- );
43640
- }
43641
- return candidate;
43642
- }
43643
- return runPath;
43644
- }
43645
- async function loadRun(runPath, options = {}) {
43646
- const resolvedRunPath = await resolveRunPath(runPath);
43647
- let raw;
43648
- try {
43649
- raw = await readFile18(resolvedRunPath, "utf8");
43650
- } catch (err) {
43651
- throw new ReleaseError(
43652
- GATE_FAILED,
43653
- `cannot read release run: ${err.message}`,
43654
- { runPath: resolvedRunPath, cause: err.code }
43655
- );
43656
- }
43657
- let run6;
43658
- try {
43659
- run6 = JSON.parse(raw);
43660
- } catch (err) {
43661
- throw new ReleaseError(
43662
- GATE_FAILED,
43663
- `release run is not valid JSON: ${err.message}`,
43664
- { runPath: resolvedRunPath }
43665
- );
43666
- }
43667
- validateRun(run6, options);
43668
- if (options.authorityPlanPath) {
43669
- assertImmutableRunAuthority(resolvedRunPath, options.authorityPlanPath, run6);
43670
- }
43671
- return run6;
43672
- }
43673
- function assertImmutableRunAuthority(runPath, planPath, run6) {
43674
- const absolutePlan = realpathSync3(resolve8(planPath));
43675
- const planDir = dirname4(absolutePlan);
43676
- if (basename4(planDir) !== "plans") {
43677
- throw new ReleaseError(GATE_FAILED, "run authority requires an immutable plans/<digest>.json plan path");
43678
- }
43679
- let authorityRoot = dirname4(planDir);
43680
- let cursor = planDir;
43681
- while (dirname4(cursor) !== cursor) {
43682
- if (basename4(cursor) === ".release-skill") {
43683
- authorityRoot = cursor;
43684
- break;
43685
- }
43686
- cursor = dirname4(cursor);
43687
- }
43688
- const runsDir = join7(authorityRoot, "runs");
43689
- const absoluteRun = realpathSync3(resolve8(runPath));
43690
- const rel = relative7(runsDir, absoluteRun);
43691
- if (isAbsolute5(rel) || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
43692
- throw new ReleaseError(GATE_FAILED, "production run authority must be inside the plan sibling runs/ directory");
43693
- }
43694
- const fileName = basename4(absoluteRun);
43695
- const isFinal = fileName === "release-run.json";
43696
- const isState = /^\d{6}\.json$/.test(fileName) && basename4(dirname4(absoluteRun)) === "states" && run6.stateSequence === Number(fileName.slice(0, 6));
43697
- if (!isFinal && !isState) {
43698
- throw new ReleaseError(
43699
- GATE_FAILED,
43700
- "production run authority must be release-run.json or a bound states/<sequence>.json snapshot",
43701
- { runPath }
43702
- );
43703
- }
43704
- }
43705
- async function createProductionRunDir(runDir, planPath) {
43706
- const absolutePlan = realpathSync3(resolve8(planPath));
43707
- const planDir = dirname4(absolutePlan);
43708
- if (basename4(planDir) !== "plans") {
43709
- throw new ReleaseError(GATE_FAILED, "production run directory requires an immutable plans/<digest>.json authority");
43710
- }
43711
- const authorityRoot = dirname4(planDir);
43712
- return createProductionRunDirWithinAuthority(runDir, authorityRoot);
43713
- }
43714
- async function createProductionPrepareRunDir(runDir, releaseDir) {
43715
- const authorityRoot = resolve8(releaseDir);
43716
- let authorityStat;
43717
- try {
43718
- authorityStat = await lstat27(authorityRoot);
43719
- } catch (error) {
43720
- throw new ReleaseError(GATE_FAILED, "cannot inspect production .release-skill authority root", {
43721
- releaseDir: authorityRoot,
43722
- cause: error.code
43723
- });
43724
- }
43725
- if (authorityStat.isSymbolicLink() || !authorityStat.isDirectory()) {
43726
- throw new ReleaseError(
43727
- GATE_FAILED,
43728
- "production .release-skill authority root must be a real directory, not a symlink or special file",
43729
- { releaseDir: authorityRoot }
43730
- );
43731
- }
43732
- const physicalAuthorityRoot = realpathSync3(authorityRoot);
43733
- if (physicalAuthorityRoot !== authorityRoot) {
43734
- throw new ReleaseError(
43735
- GATE_FAILED,
43736
- "production .release-skill authority root physical identity does not match the project authority",
43737
- { releaseDir: authorityRoot, physicalReleaseDir: physicalAuthorityRoot }
43738
- );
43739
- }
43740
- return createProductionRunDirWithinAuthority(runDir, physicalAuthorityRoot);
43741
- }
43742
- async function createProductionRunDirWithinAuthority(runDir, authorityRoot) {
43743
- const runsDir = join7(authorityRoot, "runs");
43744
- let runsStat;
43745
- try {
43746
- runsStat = await lstat27(runsDir);
43747
- } catch (error) {
43748
- if (error.code !== "ENOENT") {
43749
- throw new ReleaseError(GATE_FAILED, "cannot inspect production runs authority root", {
43750
- runsDir,
43751
- cause: error.code
43752
- });
43753
- }
43754
- try {
43755
- await mkdir10(runsDir, { mode: 448 });
43756
- } catch (mkdirError) {
43757
- if (mkdirError.code !== "EEXIST") {
43758
- throw new ReleaseError(GATE_FAILED, "cannot create production runs authority root", {
43759
- runsDir,
43760
- cause: mkdirError.code
43761
- });
43762
- }
43763
- }
43764
- runsStat = await lstat27(runsDir);
43765
- }
43766
- if (runsStat.isSymbolicLink() || !runsStat.isDirectory()) {
43767
- throw new ReleaseError(
43768
- GATE_FAILED,
43769
- "production .release-skill/runs authority root must be a real directory, not a symlink or special file",
43770
- { runsDir }
43771
- );
43772
- }
43773
- const physicalRunsDir = realpathSync3(runsDir);
43774
- if (physicalRunsDir !== runsDir) {
43775
- throw new ReleaseError(
43776
- GATE_FAILED,
43777
- "production .release-skill/runs authority root physical identity does not match the plan authority",
43778
- { runsDir, physicalRunsDir }
43779
- );
43780
- }
43781
- const requested = resolve8(runDir);
43782
- let physicalParent;
43783
- try {
43784
- physicalParent = realpathSync3(dirname4(requested));
43785
- } catch (error) {
43786
- throw new ReleaseError(GATE_FAILED, "production run directory parent is unavailable", {
43787
- runDir,
43788
- cause: error.code
43789
- });
43790
- }
43791
- if (physicalParent !== physicalRunsDir || basename4(requested) === "" || basename4(requested) === "." || basename4(requested) === "..") {
43792
- throw new ReleaseError(
43793
- GATE_FAILED,
43794
- "production run directory must be a fresh direct child of the immutable .release-skill/runs authority",
43795
- { runDir, runsDir: physicalRunsDir }
43796
- );
43797
- }
43798
- try {
43799
- await lstat27(requested);
43800
- throw new ReleaseError(GATE_FAILED, "production run directory already exists; authority directories cannot be reused", { runDir });
43801
- } catch (error) {
43802
- if (error instanceof ReleaseError) throw error;
43803
- if (error.code !== "ENOENT") {
43804
- throw new ReleaseError(GATE_FAILED, "cannot establish fresh production run directory", {
43805
- runDir,
43806
- cause: error.code
43807
- });
43808
- }
43809
- }
43810
- try {
43811
- await mkdir10(requested, { mode: 448 });
43812
- } catch (error) {
43813
- throw new ReleaseError(GATE_FAILED, "cannot exclusively create production run directory", {
43814
- runDir,
43815
- cause: error.code
43816
- });
43817
- }
43818
- const physicalRunDir = realpathSync3(requested);
43819
- if (dirname4(physicalRunDir) !== physicalRunsDir) {
43820
- throw new ReleaseError(GATE_FAILED, "production run directory escaped its immutable authority after creation", { runDir });
43821
- }
43822
- return physicalRunDir;
43823
- }
43824
- async function validateStatePredecessorChain(run6, runPath, options = {}) {
43825
- if (run6.stateSequence === void 0) {
43826
- if (run6.previousStateDigest !== void 0) {
43827
- throw new ReleaseError(GATE_FAILED, "non-state run cannot claim previousStateDigest");
43828
- }
43829
- return;
43830
- }
43831
- let current = run6;
43832
- let currentPath = realpathSync3(resolve8(runPath));
43833
- let traversed = 0;
43834
- while (true) {
43835
- const sequence = current.stateSequence;
43836
- const expectedName = `${String(sequence).padStart(6, "0")}.json`;
43837
- if (!Number.isSafeInteger(sequence) || sequence < 0 || basename4(currentPath) !== expectedName || basename4(dirname4(currentPath)) !== "states") {
43838
- throw new ReleaseError(GATE_FAILED, "run state sequence is not bound to its immutable states/<sequence>.json slot");
43839
- }
43840
- if (sequence === 0) {
43841
- if (current.previousStateDigest !== void 0) {
43842
- throw new ReleaseError(GATE_FAILED, "initial run state must not claim a predecessor digest");
43843
- }
43844
- return;
43845
- }
43846
- if (!current.previousStateDigest) {
43847
- throw new ReleaseError(GATE_FAILED, "run state is missing previousStateDigest");
43848
- }
43849
- traversed += 1;
43850
- if (traversed > 1e5) {
43851
- throw new ReleaseError(GATE_FAILED, "run state predecessor chain exceeds maximum depth");
43852
- }
43853
- const previousPath = join7(dirname4(currentPath), `${String(sequence - 1).padStart(6, "0")}.json`);
43854
- const previous = await loadRun(previousPath, {
43855
- requireDigest: true,
43856
- ...options.production ? { authorityPlanPath: options.planPath } : {}
43857
- });
43858
- if (previous.stateSequence !== sequence - 1 || previous.runId !== current.runId || previous.command !== current.command || previous.planDigest !== current.planDigest) {
43859
- throw new ReleaseError(GATE_FAILED, "run state predecessor does not belong to the same monotonic authority chain");
43860
- }
43861
- if (current.previousStateDigest !== previous.runDigest) {
43862
- throw new ReleaseError(GATE_FAILED, "run state previousStateDigest does not match the immutable predecessor bytes");
43863
- }
43864
- current = previous;
43865
- currentPath = realpathSync3(previousPath);
43866
- }
43867
- }
43868
- function validateSourceRunEdge(child, parent) {
43869
- if (child.command === "reconcile") {
43870
- if (!["publish", "reconcile"].includes(parent.command) || parent.status !== "PARTIAL") {
43871
- throw new ReleaseError(
43872
- GATE_FAILED,
43873
- "reconcile lineage must reference a PARTIAL publish or reconcile run",
43874
- { childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
43875
- );
43876
- }
43877
- return;
43878
- }
43879
- if (child.command === "verify") {
43880
- if (!["publish", "reconcile"].includes(parent.command) || parent.status !== "PUBLISHED") {
43881
- throw new ReleaseError(
43882
- GATE_FAILED,
43883
- "verify lineage must reference a PUBLISHED publish or reconcile run",
43884
- { childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
43885
- );
43886
- }
43887
- }
43888
- if (child.command === "postverify") {
43889
- if (parent.command !== "verify" || parent.status !== "VERIFIED") {
43890
- throw new ReleaseError(
43891
- GATE_FAILED,
43892
- "postverify lineage must reference a VERIFIED verify run",
43893
- { childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
43894
- );
43895
- }
43896
- }
43897
- }
43898
- async function validateRunLineage(run6, options = {}) {
43899
- const { plan, planPath, runPath, production = false, maxDepth = 16 } = options;
43900
- if (maxDepth < 0) {
43901
- throw new ReleaseError(GATE_FAILED, "release run lineage exceeds maximum depth");
43902
- }
43903
- validateRun(run6, { requireDigest: production });
43904
- if (production) assertImmutableRunAuthority(runPath, planPath, run6);
43905
- await validateStatePredecessorChain(run6, runPath, { production, planPath });
43906
- validateRunPlanDigest(run6, plan, { planPath });
43907
- if (run6.command === "publish") return;
43908
- if (!["reconcile", "verify", "postverify"].includes(run6.command)) {
43909
- throw new ReleaseError(GATE_FAILED, `unsupported run command in lineage: ${run6.command}`);
43910
- }
43911
- if (!run6.sourceRunPath || !run6.sourceRunId || !run6.sourceRunDigest) {
43912
- throw new ReleaseError(GATE_FAILED, `${run6.command} run is missing complete source run lineage`);
43913
- }
43914
- const parent = await loadRun(run6.sourceRunPath, {
43915
- requireDigest: true,
43916
- ...production ? { authorityPlanPath: planPath } : {}
43917
- });
43918
- if (parent.runId !== run6.sourceRunId || parent.runDigest !== run6.sourceRunDigest) {
43919
- throw new ReleaseError(
43920
- GATE_FAILED,
43921
- "source run lineage id/digest does not match referenced immutable run bytes",
43922
- { sourceRunPath: run6.sourceRunPath }
43923
- );
43924
- }
43925
- validateSourceRunEdge(run6, parent);
43926
- await validateRunLineage(parent, {
43927
- plan,
43928
- planPath,
43929
- runPath: run6.sourceRunPath,
43930
- production,
43931
- maxDepth: maxDepth - 1
43932
- });
43933
- }
43934
- function validateRunPlanDigest(run6, plan, options = {}) {
43935
- const requirePresence = options.requirePresence !== false;
43936
- const expectedDigest = computePlanDigest(plan);
43937
- if (options.planPath) assertImmutablePlanAuthority(options.planPath, plan);
43938
- if (!run6.planDigest) {
43939
- if (requirePresence) {
43940
- throw new ReleaseError(
43941
- GATE_FAILED,
43942
- "source run is missing required planDigest field",
43943
- { runId: run6.runId }
43944
- );
43945
- }
43946
- return;
43947
- }
43948
- if (run6.planDigest !== expectedDigest) {
43949
- throw new ReleaseError(
43950
- GATE_FAILED,
43951
- `run planDigest mismatch: run says ${String(run6.planDigest).slice(0, 16)}..., plan is ${expectedDigest.slice(0, 16)}...`,
43952
- { runDigest: run6.planDigest, planDigest: expectedDigest }
43953
- );
43954
- }
43955
- if (plan.production && run6.planPath && options.planPath && resolve8(run6.planPath) !== resolve8(options.planPath)) {
43956
- throw new ReleaseError(
43957
- GATE_FAILED,
43958
- "source run immutable plan path does not match the supplied plan authority",
43959
- { runPlanPath: run6.planPath, suppliedPlanPath: options.planPath }
43960
- );
43961
- }
43962
- }
43963
- function validateRunCheckpointMapping(run6, planActions) {
43964
- const seenPlanActionIds = /* @__PURE__ */ new Set();
43965
- for (const action of planActions) {
43966
- if (seenPlanActionIds.has(action.id)) {
43967
- throw new ReleaseError(
43968
- GATE_FAILED,
43969
- `release plan has duplicate action id "${action.id}"`,
43970
- { actionId: action.id }
43971
- );
43972
- }
43973
- seenPlanActionIds.add(action.id);
43974
- }
43975
- const planActionIds = new Set(planActions.map((a) => a.id));
43976
- const planActionsById = new Map(planActions.map((action) => [action.id, action]));
43977
- const seenCheckpointIds = /* @__PURE__ */ new Set();
43978
- for (const cp4 of run6.checkpoints) {
43979
- if (seenCheckpointIds.has(cp4.actionId)) {
43980
- throw new ReleaseError(
43981
- GATE_FAILED,
43982
- `source run has duplicate checkpoint for action "${cp4.actionId}"`,
43983
- { actionId: cp4.actionId }
43984
- );
43985
- }
43986
- seenCheckpointIds.add(cp4.actionId);
43987
- }
43988
- const runCheckpointIds = new Set(run6.checkpoints.map((cp4) => cp4.actionId));
43989
- for (const action of planActions) {
43990
- if (!runCheckpointIds.has(action.id)) {
43991
- throw new ReleaseError(
43992
- GATE_FAILED,
43993
- `source run missing checkpoint for plan action "${action.id}"`,
43994
- { actionId: action.id }
43995
- );
43996
- }
43997
- }
43998
- for (const cp4 of run6.checkpoints) {
43999
- if (!planActionIds.has(cp4.actionId)) {
44000
- throw new ReleaseError(
44001
- GATE_FAILED,
44002
- `source run has checkpoint for unknown action "${cp4.actionId}"`,
44003
- { actionId: cp4.actionId }
44004
- );
44005
- }
44006
- const action = planActionsById.get(cp4.actionId);
44007
- if (cp4.actionType !== action.type) {
44008
- throw new ReleaseError(
44009
- GATE_FAILED,
44010
- `source run checkpoint actionType mismatch for action "${cp4.actionId}": run says "${cp4.actionType}", plan says "${action.type}"`,
44011
- { actionId: cp4.actionId, runActionType: cp4.actionType, planActionType: action.type }
44012
- );
44013
- }
44014
- }
44015
- }
44016
- function computeRunDigest(run6) {
44017
- const { runDigest: _, ...rest } = run6;
44018
- return sha256Hex(JSON.stringify(rest, null, 2));
44019
- }
44020
- function sealRun(run6) {
44021
- const sealed = { ...run6 };
44022
- sealed.runDigest = computeRunDigest(sealed);
44023
- validateRun(sealed, { requireDigest: true });
44024
- return sealed;
44025
- }
44026
- async function writeRunAtomic(runPath, run6) {
44027
- const sealed = sealRun(run6);
44028
- const json = JSON.stringify(sealed, null, 2);
44029
- const dir = dirname4(runPath);
44030
- await mkdir10(dir, { recursive: true });
44031
- try {
44032
- await publishFileExclusive(dir, basename4(runPath), json, { mode: 384 });
44033
- } catch (cause) {
44034
- if (cause?.details?.kind === HARNESS_ERROR_KINDS.EXCLUSIVE_PUBLISH_CONFLICT) {
44035
- const existing = await readFile18(runPath, "utf8").catch(() => null);
44036
- if (existing === json) return Object.freeze(sealed);
44037
- throw new ReleaseError(
44038
- GATE_FAILED,
44039
- "run file already exists with different bytes; exclusive-create rejected overwrite",
44040
- { runPath }
44041
- );
44042
- }
44043
- throw new ReleaseError(
44044
- GATE_FAILED,
44045
- `run file write failed: ${cause?.message ?? String(cause)}`,
44046
- { runPath }
44047
- );
44048
- }
44049
- return Object.freeze(sealed);
44050
- }
44051
- async function appendRunState(runDir, sequence, run6) {
44052
- if (!Number.isSafeInteger(sequence) || sequence < 0) {
44053
- throw new ReleaseError(GATE_FAILED, "run state sequence must be a non-negative integer");
44054
- }
44055
- const statesDir = join7(runDir, "states");
44056
- let previousStateDigest;
44057
- if (sequence > 0) {
44058
- const previousPath = join7(statesDir, `${String(sequence - 1).padStart(6, "0")}.json`);
44059
- const previous = await loadRun(previousPath, { requireDigest: true });
44060
- if (previous.stateSequence !== sequence - 1 || previous.runId !== run6.runId || previous.command !== run6.command || previous.planDigest !== run6.planDigest) {
44061
- throw new ReleaseError(
44062
- GATE_FAILED,
44063
- "run state predecessor does not belong to the same monotonic authority chain",
44064
- { sequence, previousPath }
44065
- );
44066
- }
44067
- previousStateDigest = previous.runDigest;
44068
- }
44069
- const { previousStateDigest: _discarded, ...nextRun } = run6;
44070
- const state = sealRun({
44071
- ...nextRun,
44072
- stateSequence: sequence,
44073
- ...previousStateDigest ? { previousStateDigest } : {}
44074
- });
44075
- const statePath = join7(statesDir, `${String(sequence).padStart(6, "0")}.json`);
44076
- await writeRunAtomic(statePath, state);
44077
- return Object.freeze({ state, statePath });
44078
- }
44079
- var import_ajv4, import_ajv_formats2, RELEASE_RUN_SCHEMA, ajv2, validateRunSchema;
44080
- var init_run = __esm({
44081
- async "src/core/run.mjs"() {
44082
- import_ajv4 = __toESM(require_ajv(), 1);
44083
- import_ajv_formats2 = __toESM(require_dist3(), 1);
44084
- await init_plan();
44085
- init_digest();
44086
- init_errors3();
44087
- init_trusted_resource();
44088
- init_foundation_inflight();
44089
- RELEASE_RUN_SCHEMA = JSON.parse((await readTrustedPackageResource(
44090
- "schemas/release-run.schema.json"
44091
- )).toString("utf8"));
44092
- __name(resolveDefaultRunDir, "resolveDefaultRunDir");
44093
- ajv2 = new import_ajv4.default({ allErrors: true, strict: false });
44094
- (0, import_ajv_formats2.default)(ajv2);
44095
- validateRunSchema = ajv2.compile(RELEASE_RUN_SCHEMA);
44096
- __name(validateRun, "validateRun");
44097
- __name(resolveRunPath, "resolveRunPath");
44098
- __name(loadRun, "loadRun");
44099
- __name(assertImmutableRunAuthority, "assertImmutableRunAuthority");
44100
- __name(createProductionRunDir, "createProductionRunDir");
44101
- __name(createProductionPrepareRunDir, "createProductionPrepareRunDir");
44102
- __name(createProductionRunDirWithinAuthority, "createProductionRunDirWithinAuthority");
44103
- __name(validateStatePredecessorChain, "validateStatePredecessorChain");
44104
- __name(validateSourceRunEdge, "validateSourceRunEdge");
44105
- __name(validateRunLineage, "validateRunLineage");
44106
- __name(validateRunPlanDigest, "validateRunPlanDigest");
44107
- __name(validateRunCheckpointMapping, "validateRunCheckpointMapping");
44108
- __name(computeRunDigest, "computeRunDigest");
44109
- __name(sealRun, "sealRun");
44110
- __name(writeRunAtomic, "writeRunAtomic");
44111
- __name(appendRunState, "appendRunState");
44112
- }
44113
- });
44114
-
44115
43522
  // src/core/baseline.mjs
44116
43523
  import { execFile as execFileCb2 } from "node:child_process";
44117
43524
  import { promisify as promisify2 } from "node:util";
44118
43525
  import { createHash as createHash9 } from "node:crypto";
44119
- import { lstat as lstat28, open as open9, readlink as readlink3 } from "node:fs/promises";
43526
+ import { lstat as lstat27, open as open9, readlink as readlink3 } from "node:fs/promises";
44120
43527
  import { constants as fsConstants } from "node:fs";
44121
43528
  import path38 from "node:path";
44122
43529
  function isControlPlanePath(p) {
@@ -44200,7 +43607,7 @@ async function computeWorkspaceDigest(root) {
44200
43607
  `Refusing to read path outside repository root: ${relPath} resolves to ${absPath}`
44201
43608
  );
44202
43609
  }
44203
- const stat22 = await lstat28(absPath);
43610
+ const stat22 = await lstat27(absPath);
44204
43611
  let type;
44205
43612
  if (stat22.isSymbolicLink()) {
44206
43613
  type = "symlink";
@@ -44323,8 +43730,8 @@ var init_baseline2 = __esm({
44323
43730
  });
44324
43731
 
44325
43732
  // src/core/approval.mjs
44326
- import { readFile as readFile19 } from "node:fs/promises";
44327
- import { basename as basename5, dirname as dirname5, join as join8, resolve as resolve9 } from "node:path";
43733
+ import { readFile as readFile18 } from "node:fs/promises";
43734
+ import { basename as basename4, dirname as dirname4, join as join7, resolve as resolve8 } from "node:path";
44328
43735
  function validateApprovalRecordSchema(approval) {
44329
43736
  if (validateApprovalSchema(approval)) return;
44330
43737
  const errors = validateApprovalSchema.errors ?? [];
@@ -44398,9 +43805,9 @@ function assertImmutableApprovalAuthority(approvalPath, plan, rawApproval) {
44398
43805
  if (!plan?.production) return;
44399
43806
  const planDigest = computePlanDigest(plan);
44400
43807
  const approvalDigest = computeApprovalDigest(rawApproval);
44401
- const absolute2 = resolve9(approvalPath);
44402
- const planDirectory = dirname5(absolute2);
44403
- if (basename5(absolute2) !== `${approvalDigest}.json` || basename5(planDirectory) !== planDigest || basename5(dirname5(planDirectory)) !== "approvals") {
43808
+ const absolute2 = resolve8(approvalPath);
43809
+ const planDirectory = dirname4(absolute2);
43810
+ if (basename4(absolute2) !== `${approvalDigest}.json` || basename4(planDirectory) !== planDigest || basename4(dirname4(planDirectory)) !== "approvals") {
44404
43811
  throw new ReleaseError(
44405
43812
  GATE_FAILED,
44406
43813
  "production commands require approvals/<planDigest>/<approvalDigest>.json immutable authority",
@@ -44586,21 +43993,21 @@ function validateApproval(plan, approval, options = {}) {
44586
43993
  requireUnexpired: options.requireUnexpired
44587
43994
  });
44588
43995
  }
44589
- var import_ajv5, import_ajv_formats3, approvalSchema, approvalAjv, validateApprovalSchema, MAX_APPROVAL_MS, CLOCK_SKEW_TOLERANCE_MS;
43996
+ var import_ajv4, import_ajv_formats2, approvalSchema, approvalAjv, validateApprovalSchema, MAX_APPROVAL_MS, CLOCK_SKEW_TOLERANCE_MS;
44590
43997
  var init_approval = __esm({
44591
43998
  async "src/core/approval.mjs"() {
44592
43999
  init_errors3();
44593
44000
  await init_plan();
44594
44001
  init_src2();
44595
- import_ajv5 = __toESM(require_ajv(), 1);
44596
- import_ajv_formats3 = __toESM(require_dist3(), 1);
44002
+ import_ajv4 = __toESM(require_ajv(), 1);
44003
+ import_ajv_formats2 = __toESM(require_dist3(), 1);
44597
44004
  init_baseline2();
44598
44005
  init_trusted_resource();
44599
44006
  approvalSchema = JSON.parse((await readTrustedPackageResource(
44600
44007
  "schemas/approval-record.schema.json"
44601
44008
  )).toString("utf8"));
44602
- approvalAjv = new import_ajv5.default({ allErrors: true, strict: false });
44603
- (0, import_ajv_formats3.default)(approvalAjv);
44009
+ approvalAjv = new import_ajv4.default({ allErrors: true, strict: false });
44010
+ (0, import_ajv_formats2.default)(approvalAjv);
44604
44011
  validateApprovalSchema = approvalAjv.compile(approvalSchema);
44605
44012
  __name(validateApprovalRecordSchema, "validateApprovalRecordSchema");
44606
44013
  __name(computeApprovalDigest, "computeApprovalDigest");
@@ -44707,6 +44114,977 @@ var init_checkpoints = __esm({
44707
44114
  }
44708
44115
  });
44709
44116
 
44117
+ // src/core/run.mjs
44118
+ var run_exports = {};
44119
+ __export(run_exports, {
44120
+ appendRunState: () => appendRunState,
44121
+ assertImmutableRunAuthority: () => assertImmutableRunAuthority,
44122
+ computeRunDigest: () => computeRunDigest,
44123
+ createProductionPrepareRunDir: () => createProductionPrepareRunDir,
44124
+ createProductionRunDir: () => createProductionRunDir,
44125
+ loadRun: () => loadRun,
44126
+ resolveDefaultRunDir: () => resolveDefaultRunDir,
44127
+ resolveRunPath: () => resolveRunPath,
44128
+ validateRun: () => validateRun,
44129
+ validateRunCheckpointMapping: () => validateRunCheckpointMapping,
44130
+ validateRunLineage: () => validateRunLineage,
44131
+ validateRunPlanDigest: () => validateRunPlanDigest,
44132
+ validateSourceRunEdge: () => validateSourceRunEdge,
44133
+ writeRunAtomic: () => writeRunAtomic
44134
+ });
44135
+ import { lstat as lstat28, mkdir as mkdir10, readFile as readFile19, stat as stat10 } from "node:fs/promises";
44136
+ import { realpathSync as realpathSync3 } from "node:fs";
44137
+ import { dirname as dirname5, join as join8, resolve as resolve9, basename as basename5, relative as relative7, isAbsolute as isAbsolute5 } from "node:path";
44138
+ function resolveDefaultRunDir(planPath, command2, runId = `${command2}-${Date.now()}`) {
44139
+ const absolute2 = resolve9(planPath);
44140
+ const fileName = basename5(absolute2);
44141
+ const parentDir = dirname5(absolute2);
44142
+ if (fileName === "release-plan.json") {
44143
+ return `${parentDir}/runs/${runId}`;
44144
+ }
44145
+ if (basename5(parentDir) === "plans") {
44146
+ const releaseDir = dirname5(parentDir);
44147
+ return join8(releaseDir, "runs", runId);
44148
+ }
44149
+ return `${parentDir}/runs/${runId}`;
44150
+ }
44151
+ function validateRun(run6, options = {}) {
44152
+ const valid = validateRunSchema(run6);
44153
+ if (!valid) {
44154
+ const errors = validateRunSchema.errors ?? [];
44155
+ const summary = errors.map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
44156
+ throw new ReleaseError(
44157
+ GATE_FAILED,
44158
+ `release run schema validation failed: ${summary}`,
44159
+ { validationErrors: errors }
44160
+ );
44161
+ }
44162
+ const actionIds = /* @__PURE__ */ new Set();
44163
+ for (const checkpoint of run6.checkpoints ?? []) {
44164
+ if (actionIds.has(checkpoint.actionId)) {
44165
+ throw new ReleaseError(
44166
+ GATE_FAILED,
44167
+ `release run has duplicate checkpoint for action "${checkpoint.actionId}"`,
44168
+ { actionId: checkpoint.actionId }
44169
+ );
44170
+ }
44171
+ actionIds.add(checkpoint.actionId);
44172
+ }
44173
+ if (options.requireDigest === true && !run6.runDigest) {
44174
+ throw new ReleaseError(GATE_FAILED, "release run is missing required runDigest");
44175
+ }
44176
+ if (run6.runDigest && run6.runDigest !== computeRunDigest(run6)) {
44177
+ throw new ReleaseError(
44178
+ GATE_FAILED,
44179
+ "release run digest does not match its content",
44180
+ { expected: computeRunDigest(run6), actual: run6.runDigest }
44181
+ );
44182
+ }
44183
+ }
44184
+ async function resolveRunPath(runPath) {
44185
+ let stats;
44186
+ try {
44187
+ stats = await lstat28(runPath);
44188
+ } catch (err) {
44189
+ if (err?.code === "ENOENT") return runPath;
44190
+ throw new ReleaseError(
44191
+ GATE_FAILED,
44192
+ `cannot inspect release run path: ${err.message}`,
44193
+ { runPath, cause: err.code }
44194
+ );
44195
+ }
44196
+ if (stats.isFile()) return runPath;
44197
+ if (stats.isSymbolicLink()) {
44198
+ let targetStats;
44199
+ try {
44200
+ targetStats = await stat10(runPath);
44201
+ } catch (err) {
44202
+ if (err?.code === "ENOENT") return runPath;
44203
+ throw new ReleaseError(
44204
+ GATE_FAILED,
44205
+ `cannot inspect release run path: ${err.message}`,
44206
+ { runPath, cause: err.code }
44207
+ );
44208
+ }
44209
+ if (targetStats.isDirectory()) {
44210
+ throw new ReleaseError(
44211
+ GATE_FAILED,
44212
+ "release run path is a symlinked directory; pass the run file (release-run.json) or the real run directory",
44213
+ { runPath }
44214
+ );
44215
+ }
44216
+ return runPath;
44217
+ }
44218
+ if (stats.isDirectory()) {
44219
+ const candidate = join8(runPath, "release-run.json");
44220
+ try {
44221
+ const candidateStats = await lstat28(candidate);
44222
+ if (!candidateStats.isFile()) {
44223
+ throw new ReleaseError(
44224
+ GATE_FAILED,
44225
+ `release run directory does not contain a regular release-run.json: ${runPath}`,
44226
+ { runPath, expected: "release-run.json" }
44227
+ );
44228
+ }
44229
+ } catch (err) {
44230
+ if (err instanceof ReleaseError) throw err;
44231
+ throw new ReleaseError(
44232
+ GATE_FAILED,
44233
+ `release run directory must contain release-run.json: ${err.message}`,
44234
+ { runPath, expected: "release-run.json", cause: err.code }
44235
+ );
44236
+ }
44237
+ return candidate;
44238
+ }
44239
+ return runPath;
44240
+ }
44241
+ async function loadRun(runPath, options = {}) {
44242
+ const resolvedRunPath = await resolveRunPath(runPath);
44243
+ let raw;
44244
+ try {
44245
+ raw = await readFile19(resolvedRunPath, "utf8");
44246
+ } catch (err) {
44247
+ throw new ReleaseError(
44248
+ GATE_FAILED,
44249
+ `cannot read release run: ${err.message}`,
44250
+ { runPath: resolvedRunPath, cause: err.code }
44251
+ );
44252
+ }
44253
+ let run6;
44254
+ try {
44255
+ run6 = JSON.parse(raw);
44256
+ } catch (err) {
44257
+ throw new ReleaseError(
44258
+ GATE_FAILED,
44259
+ `release run is not valid JSON: ${err.message}`,
44260
+ { runPath: resolvedRunPath }
44261
+ );
44262
+ }
44263
+ validateRun(run6, options);
44264
+ if (options.authorityPlanPath) {
44265
+ assertImmutableRunAuthority(resolvedRunPath, options.authorityPlanPath, run6);
44266
+ }
44267
+ return run6;
44268
+ }
44269
+ function assertImmutableRunAuthority(runPath, planPath, run6) {
44270
+ const absolutePlan = realpathSync3(resolve9(planPath));
44271
+ const planDir = dirname5(absolutePlan);
44272
+ if (basename5(planDir) !== "plans") {
44273
+ throw new ReleaseError(GATE_FAILED, "run authority requires an immutable plans/<digest>.json plan path");
44274
+ }
44275
+ let authorityRoot = dirname5(planDir);
44276
+ let cursor = planDir;
44277
+ while (dirname5(cursor) !== cursor) {
44278
+ if (basename5(cursor) === ".release-skill") {
44279
+ authorityRoot = cursor;
44280
+ break;
44281
+ }
44282
+ cursor = dirname5(cursor);
44283
+ }
44284
+ const runsDir = join8(authorityRoot, "runs");
44285
+ const absoluteRun = realpathSync3(resolve9(runPath));
44286
+ const rel = relative7(runsDir, absoluteRun);
44287
+ if (isAbsolute5(rel) || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
44288
+ throw new ReleaseError(GATE_FAILED, "production run authority must be inside the plan sibling runs/ directory");
44289
+ }
44290
+ const fileName = basename5(absoluteRun);
44291
+ const isFinal = fileName === "release-run.json";
44292
+ const isState = /^\d{6}\.json$/.test(fileName) && basename5(dirname5(absoluteRun)) === "states" && run6.stateSequence === Number(fileName.slice(0, 6));
44293
+ if (!isFinal && !isState) {
44294
+ throw new ReleaseError(
44295
+ GATE_FAILED,
44296
+ "production run authority must be release-run.json or a bound states/<sequence>.json snapshot",
44297
+ { runPath }
44298
+ );
44299
+ }
44300
+ }
44301
+ async function createProductionRunDir(runDir, planPath) {
44302
+ const absolutePlan = realpathSync3(resolve9(planPath));
44303
+ const planDir = dirname5(absolutePlan);
44304
+ if (basename5(planDir) !== "plans") {
44305
+ throw new ReleaseError(GATE_FAILED, "production run directory requires an immutable plans/<digest>.json authority");
44306
+ }
44307
+ const authorityRoot = dirname5(planDir);
44308
+ return createProductionRunDirWithinAuthority(runDir, authorityRoot);
44309
+ }
44310
+ async function createProductionPrepareRunDir(runDir, releaseDir) {
44311
+ const authorityRoot = resolve9(releaseDir);
44312
+ let authorityStat;
44313
+ try {
44314
+ authorityStat = await lstat28(authorityRoot);
44315
+ } catch (error) {
44316
+ throw new ReleaseError(GATE_FAILED, "cannot inspect production .release-skill authority root", {
44317
+ releaseDir: authorityRoot,
44318
+ cause: error.code
44319
+ });
44320
+ }
44321
+ if (authorityStat.isSymbolicLink() || !authorityStat.isDirectory()) {
44322
+ throw new ReleaseError(
44323
+ GATE_FAILED,
44324
+ "production .release-skill authority root must be a real directory, not a symlink or special file",
44325
+ { releaseDir: authorityRoot }
44326
+ );
44327
+ }
44328
+ const physicalAuthorityRoot = realpathSync3(authorityRoot);
44329
+ if (physicalAuthorityRoot !== authorityRoot) {
44330
+ throw new ReleaseError(
44331
+ GATE_FAILED,
44332
+ "production .release-skill authority root physical identity does not match the project authority",
44333
+ { releaseDir: authorityRoot, physicalReleaseDir: physicalAuthorityRoot }
44334
+ );
44335
+ }
44336
+ return createProductionRunDirWithinAuthority(runDir, physicalAuthorityRoot);
44337
+ }
44338
+ async function createProductionRunDirWithinAuthority(runDir, authorityRoot) {
44339
+ const runsDir = join8(authorityRoot, "runs");
44340
+ let runsStat;
44341
+ try {
44342
+ runsStat = await lstat28(runsDir);
44343
+ } catch (error) {
44344
+ if (error.code !== "ENOENT") {
44345
+ throw new ReleaseError(GATE_FAILED, "cannot inspect production runs authority root", {
44346
+ runsDir,
44347
+ cause: error.code
44348
+ });
44349
+ }
44350
+ try {
44351
+ await mkdir10(runsDir, { mode: 448 });
44352
+ } catch (mkdirError) {
44353
+ if (mkdirError.code !== "EEXIST") {
44354
+ throw new ReleaseError(GATE_FAILED, "cannot create production runs authority root", {
44355
+ runsDir,
44356
+ cause: mkdirError.code
44357
+ });
44358
+ }
44359
+ }
44360
+ runsStat = await lstat28(runsDir);
44361
+ }
44362
+ if (runsStat.isSymbolicLink() || !runsStat.isDirectory()) {
44363
+ throw new ReleaseError(
44364
+ GATE_FAILED,
44365
+ "production .release-skill/runs authority root must be a real directory, not a symlink or special file",
44366
+ { runsDir }
44367
+ );
44368
+ }
44369
+ const physicalRunsDir = realpathSync3(runsDir);
44370
+ if (physicalRunsDir !== runsDir) {
44371
+ throw new ReleaseError(
44372
+ GATE_FAILED,
44373
+ "production .release-skill/runs authority root physical identity does not match the plan authority",
44374
+ { runsDir, physicalRunsDir }
44375
+ );
44376
+ }
44377
+ const requested = resolve9(runDir);
44378
+ let physicalParent;
44379
+ try {
44380
+ physicalParent = realpathSync3(dirname5(requested));
44381
+ } catch (error) {
44382
+ throw new ReleaseError(GATE_FAILED, "production run directory parent is unavailable", {
44383
+ runDir,
44384
+ cause: error.code
44385
+ });
44386
+ }
44387
+ if (physicalParent !== physicalRunsDir || basename5(requested) === "" || basename5(requested) === "." || basename5(requested) === "..") {
44388
+ throw new ReleaseError(
44389
+ GATE_FAILED,
44390
+ "production run directory must be a fresh direct child of the immutable .release-skill/runs authority",
44391
+ { runDir, runsDir: physicalRunsDir }
44392
+ );
44393
+ }
44394
+ try {
44395
+ await lstat28(requested);
44396
+ throw new ReleaseError(GATE_FAILED, "production run directory already exists; authority directories cannot be reused", { runDir });
44397
+ } catch (error) {
44398
+ if (error instanceof ReleaseError) throw error;
44399
+ if (error.code !== "ENOENT") {
44400
+ throw new ReleaseError(GATE_FAILED, "cannot establish fresh production run directory", {
44401
+ runDir,
44402
+ cause: error.code
44403
+ });
44404
+ }
44405
+ }
44406
+ try {
44407
+ await mkdir10(requested, { mode: 448 });
44408
+ } catch (error) {
44409
+ throw new ReleaseError(GATE_FAILED, "cannot exclusively create production run directory", {
44410
+ runDir,
44411
+ cause: error.code
44412
+ });
44413
+ }
44414
+ const physicalRunDir = realpathSync3(requested);
44415
+ if (dirname5(physicalRunDir) !== physicalRunsDir) {
44416
+ throw new ReleaseError(GATE_FAILED, "production run directory escaped its immutable authority after creation", { runDir });
44417
+ }
44418
+ return physicalRunDir;
44419
+ }
44420
+ async function validateStatePredecessorChain(run6, runPath, options = {}) {
44421
+ if (run6.stateSequence === void 0) {
44422
+ if (run6.previousStateDigest !== void 0) {
44423
+ throw new ReleaseError(GATE_FAILED, "non-state run cannot claim previousStateDigest");
44424
+ }
44425
+ return;
44426
+ }
44427
+ let current = run6;
44428
+ let currentPath = realpathSync3(resolve9(runPath));
44429
+ let traversed = 0;
44430
+ while (true) {
44431
+ const sequence = current.stateSequence;
44432
+ const expectedName = `${String(sequence).padStart(6, "0")}.json`;
44433
+ if (!Number.isSafeInteger(sequence) || sequence < 0 || basename5(currentPath) !== expectedName || basename5(dirname5(currentPath)) !== "states") {
44434
+ throw new ReleaseError(GATE_FAILED, "run state sequence is not bound to its immutable states/<sequence>.json slot");
44435
+ }
44436
+ if (sequence === 0) {
44437
+ if (current.previousStateDigest !== void 0) {
44438
+ throw new ReleaseError(GATE_FAILED, "initial run state must not claim a predecessor digest");
44439
+ }
44440
+ return;
44441
+ }
44442
+ if (!current.previousStateDigest) {
44443
+ throw new ReleaseError(GATE_FAILED, "run state is missing previousStateDigest");
44444
+ }
44445
+ traversed += 1;
44446
+ if (traversed > 1e5) {
44447
+ throw new ReleaseError(GATE_FAILED, "run state predecessor chain exceeds maximum depth");
44448
+ }
44449
+ const previousPath = join8(dirname5(currentPath), `${String(sequence - 1).padStart(6, "0")}.json`);
44450
+ const previous = await loadRun(previousPath, {
44451
+ requireDigest: true,
44452
+ ...options.production ? { authorityPlanPath: options.planPath } : {}
44453
+ });
44454
+ if (previous.stateSequence !== sequence - 1 || previous.runId !== current.runId || previous.command !== current.command || previous.planDigest !== current.planDigest) {
44455
+ throw new ReleaseError(GATE_FAILED, "run state predecessor does not belong to the same monotonic authority chain");
44456
+ }
44457
+ if (current.previousStateDigest !== previous.runDigest) {
44458
+ throw new ReleaseError(GATE_FAILED, "run state previousStateDigest does not match the immutable predecessor bytes");
44459
+ }
44460
+ current = previous;
44461
+ currentPath = realpathSync3(previousPath);
44462
+ }
44463
+ }
44464
+ function validateSourceRunEdge(child, parent) {
44465
+ if (child.command === "reconcile") {
44466
+ if (!["publish", "reconcile"].includes(parent.command) || parent.status !== "PARTIAL") {
44467
+ throw new ReleaseError(
44468
+ GATE_FAILED,
44469
+ "reconcile lineage must reference a PARTIAL publish or reconcile run",
44470
+ { childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
44471
+ );
44472
+ }
44473
+ return;
44474
+ }
44475
+ if (child.command === "verify") {
44476
+ if (!["publish", "reconcile"].includes(parent.command) || parent.status !== "PUBLISHED") {
44477
+ throw new ReleaseError(
44478
+ GATE_FAILED,
44479
+ "verify lineage must reference a PUBLISHED publish or reconcile run",
44480
+ { childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
44481
+ );
44482
+ }
44483
+ }
44484
+ if (child.command === "postverify") {
44485
+ if (parent.command !== "verify" || parent.status !== "VERIFIED") {
44486
+ throw new ReleaseError(
44487
+ GATE_FAILED,
44488
+ "postverify lineage must reference a VERIFIED verify run",
44489
+ { childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
44490
+ );
44491
+ }
44492
+ }
44493
+ }
44494
+ async function validateRunLineage(run6, options = {}) {
44495
+ const { plan, planPath, runPath, production = false, maxDepth = 16 } = options;
44496
+ if (maxDepth < 0) {
44497
+ throw new ReleaseError(GATE_FAILED, "release run lineage exceeds maximum depth");
44498
+ }
44499
+ validateRun(run6, { requireDigest: production });
44500
+ if (production) assertImmutableRunAuthority(runPath, planPath, run6);
44501
+ await validateStatePredecessorChain(run6, runPath, { production, planPath });
44502
+ validateRunPlanDigest(run6, plan, { planPath });
44503
+ if (run6.command === "publish") return;
44504
+ if (!["reconcile", "verify", "postverify"].includes(run6.command)) {
44505
+ throw new ReleaseError(GATE_FAILED, `unsupported run command in lineage: ${run6.command}`);
44506
+ }
44507
+ if (!run6.sourceRunPath || !run6.sourceRunId || !run6.sourceRunDigest) {
44508
+ throw new ReleaseError(GATE_FAILED, `${run6.command} run is missing complete source run lineage`);
44509
+ }
44510
+ const parent = await loadRun(run6.sourceRunPath, {
44511
+ requireDigest: true,
44512
+ ...production ? { authorityPlanPath: planPath } : {}
44513
+ });
44514
+ if (parent.runId !== run6.sourceRunId || parent.runDigest !== run6.sourceRunDigest) {
44515
+ throw new ReleaseError(
44516
+ GATE_FAILED,
44517
+ "source run lineage id/digest does not match referenced immutable run bytes",
44518
+ { sourceRunPath: run6.sourceRunPath }
44519
+ );
44520
+ }
44521
+ validateSourceRunEdge(run6, parent);
44522
+ await validateRunLineage(parent, {
44523
+ plan,
44524
+ planPath,
44525
+ runPath: run6.sourceRunPath,
44526
+ production,
44527
+ maxDepth: maxDepth - 1
44528
+ });
44529
+ }
44530
+ function validateRunPlanDigest(run6, plan, options = {}) {
44531
+ const requirePresence = options.requirePresence !== false;
44532
+ const expectedDigest = computePlanDigest(plan);
44533
+ if (options.planPath) assertImmutablePlanAuthority(options.planPath, plan);
44534
+ if (!run6.planDigest) {
44535
+ if (requirePresence) {
44536
+ throw new ReleaseError(
44537
+ GATE_FAILED,
44538
+ "source run is missing required planDigest field",
44539
+ { runId: run6.runId }
44540
+ );
44541
+ }
44542
+ return;
44543
+ }
44544
+ if (run6.planDigest !== expectedDigest) {
44545
+ throw new ReleaseError(
44546
+ GATE_FAILED,
44547
+ `run planDigest mismatch: run says ${String(run6.planDigest).slice(0, 16)}..., plan is ${expectedDigest.slice(0, 16)}...`,
44548
+ { runDigest: run6.planDigest, planDigest: expectedDigest }
44549
+ );
44550
+ }
44551
+ if (plan.production && run6.planPath && options.planPath && resolve9(run6.planPath) !== resolve9(options.planPath)) {
44552
+ throw new ReleaseError(
44553
+ GATE_FAILED,
44554
+ "source run immutable plan path does not match the supplied plan authority",
44555
+ { runPlanPath: run6.planPath, suppliedPlanPath: options.planPath }
44556
+ );
44557
+ }
44558
+ }
44559
+ function validateRunCheckpointMapping(run6, planActions) {
44560
+ const seenPlanActionIds = /* @__PURE__ */ new Set();
44561
+ for (const action of planActions) {
44562
+ if (seenPlanActionIds.has(action.id)) {
44563
+ throw new ReleaseError(
44564
+ GATE_FAILED,
44565
+ `release plan has duplicate action id "${action.id}"`,
44566
+ { actionId: action.id }
44567
+ );
44568
+ }
44569
+ seenPlanActionIds.add(action.id);
44570
+ }
44571
+ const planActionIds = new Set(planActions.map((a) => a.id));
44572
+ const planActionsById = new Map(planActions.map((action) => [action.id, action]));
44573
+ const seenCheckpointIds = /* @__PURE__ */ new Set();
44574
+ for (const cp4 of run6.checkpoints) {
44575
+ if (seenCheckpointIds.has(cp4.actionId)) {
44576
+ throw new ReleaseError(
44577
+ GATE_FAILED,
44578
+ `source run has duplicate checkpoint for action "${cp4.actionId}"`,
44579
+ { actionId: cp4.actionId }
44580
+ );
44581
+ }
44582
+ seenCheckpointIds.add(cp4.actionId);
44583
+ }
44584
+ const runCheckpointIds = new Set(run6.checkpoints.map((cp4) => cp4.actionId));
44585
+ for (const action of planActions) {
44586
+ if (!runCheckpointIds.has(action.id)) {
44587
+ throw new ReleaseError(
44588
+ GATE_FAILED,
44589
+ `source run missing checkpoint for plan action "${action.id}"`,
44590
+ { actionId: action.id }
44591
+ );
44592
+ }
44593
+ }
44594
+ for (const cp4 of run6.checkpoints) {
44595
+ if (!planActionIds.has(cp4.actionId)) {
44596
+ throw new ReleaseError(
44597
+ GATE_FAILED,
44598
+ `source run has checkpoint for unknown action "${cp4.actionId}"`,
44599
+ { actionId: cp4.actionId }
44600
+ );
44601
+ }
44602
+ const action = planActionsById.get(cp4.actionId);
44603
+ if (cp4.actionType !== action.type) {
44604
+ throw new ReleaseError(
44605
+ GATE_FAILED,
44606
+ `source run checkpoint actionType mismatch for action "${cp4.actionId}": run says "${cp4.actionType}", plan says "${action.type}"`,
44607
+ { actionId: cp4.actionId, runActionType: cp4.actionType, planActionType: action.type }
44608
+ );
44609
+ }
44610
+ }
44611
+ }
44612
+ function computeRunDigest(run6) {
44613
+ const { runDigest: _, ...rest } = run6;
44614
+ return sha256Hex(JSON.stringify(rest, null, 2));
44615
+ }
44616
+ function sealRun(run6) {
44617
+ const sealed = { ...run6 };
44618
+ sealed.runDigest = computeRunDigest(sealed);
44619
+ validateRun(sealed, { requireDigest: true });
44620
+ return sealed;
44621
+ }
44622
+ async function writeRunAtomic(runPath, run6) {
44623
+ const sealed = sealRun(run6);
44624
+ const json = JSON.stringify(sealed, null, 2);
44625
+ const dir = dirname5(runPath);
44626
+ await mkdir10(dir, { recursive: true });
44627
+ try {
44628
+ await publishFileExclusive(dir, basename5(runPath), json, { mode: 384 });
44629
+ } catch (cause) {
44630
+ if (cause?.details?.kind === HARNESS_ERROR_KINDS.EXCLUSIVE_PUBLISH_CONFLICT) {
44631
+ const existing = await readFile19(runPath, "utf8").catch(() => null);
44632
+ if (existing === json) return Object.freeze(sealed);
44633
+ throw new ReleaseError(
44634
+ GATE_FAILED,
44635
+ "run file already exists with different bytes; exclusive-create rejected overwrite",
44636
+ { runPath }
44637
+ );
44638
+ }
44639
+ throw new ReleaseError(
44640
+ GATE_FAILED,
44641
+ `run file write failed: ${cause?.message ?? String(cause)}`,
44642
+ { runPath }
44643
+ );
44644
+ }
44645
+ return Object.freeze(sealed);
44646
+ }
44647
+ async function appendRunState(runDir, sequence, run6) {
44648
+ if (!Number.isSafeInteger(sequence) || sequence < 0) {
44649
+ throw new ReleaseError(GATE_FAILED, "run state sequence must be a non-negative integer");
44650
+ }
44651
+ const statesDir = join8(runDir, "states");
44652
+ let previousStateDigest;
44653
+ if (sequence > 0) {
44654
+ const previousPath = join8(statesDir, `${String(sequence - 1).padStart(6, "0")}.json`);
44655
+ const previous = await loadRun(previousPath, { requireDigest: true });
44656
+ if (previous.stateSequence !== sequence - 1 || previous.runId !== run6.runId || previous.command !== run6.command || previous.planDigest !== run6.planDigest) {
44657
+ throw new ReleaseError(
44658
+ GATE_FAILED,
44659
+ "run state predecessor does not belong to the same monotonic authority chain",
44660
+ { sequence, previousPath }
44661
+ );
44662
+ }
44663
+ previousStateDigest = previous.runDigest;
44664
+ }
44665
+ const { previousStateDigest: _discarded, ...nextRun } = run6;
44666
+ const state = sealRun({
44667
+ ...nextRun,
44668
+ stateSequence: sequence,
44669
+ ...previousStateDigest ? { previousStateDigest } : {}
44670
+ });
44671
+ const statePath = join8(statesDir, `${String(sequence).padStart(6, "0")}.json`);
44672
+ await writeRunAtomic(statePath, state);
44673
+ return Object.freeze({ state, statePath });
44674
+ }
44675
+ var import_ajv5, import_ajv_formats3, RELEASE_RUN_SCHEMA, ajv2, validateRunSchema;
44676
+ var init_run = __esm({
44677
+ async "src/core/run.mjs"() {
44678
+ import_ajv5 = __toESM(require_ajv(), 1);
44679
+ import_ajv_formats3 = __toESM(require_dist3(), 1);
44680
+ await init_plan();
44681
+ init_digest();
44682
+ init_errors3();
44683
+ init_trusted_resource();
44684
+ init_foundation_inflight();
44685
+ RELEASE_RUN_SCHEMA = JSON.parse((await readTrustedPackageResource(
44686
+ "schemas/release-run.schema.json"
44687
+ )).toString("utf8"));
44688
+ __name(resolveDefaultRunDir, "resolveDefaultRunDir");
44689
+ ajv2 = new import_ajv5.default({ allErrors: true, strict: false });
44690
+ (0, import_ajv_formats3.default)(ajv2);
44691
+ validateRunSchema = ajv2.compile(RELEASE_RUN_SCHEMA);
44692
+ __name(validateRun, "validateRun");
44693
+ __name(resolveRunPath, "resolveRunPath");
44694
+ __name(loadRun, "loadRun");
44695
+ __name(assertImmutableRunAuthority, "assertImmutableRunAuthority");
44696
+ __name(createProductionRunDir, "createProductionRunDir");
44697
+ __name(createProductionPrepareRunDir, "createProductionPrepareRunDir");
44698
+ __name(createProductionRunDirWithinAuthority, "createProductionRunDirWithinAuthority");
44699
+ __name(validateStatePredecessorChain, "validateStatePredecessorChain");
44700
+ __name(validateSourceRunEdge, "validateSourceRunEdge");
44701
+ __name(validateRunLineage, "validateRunLineage");
44702
+ __name(validateRunPlanDigest, "validateRunPlanDigest");
44703
+ __name(validateRunCheckpointMapping, "validateRunCheckpointMapping");
44704
+ __name(computeRunDigest, "computeRunDigest");
44705
+ __name(sealRun, "sealRun");
44706
+ __name(writeRunAtomic, "writeRunAtomic");
44707
+ __name(appendRunState, "appendRunState");
44708
+ }
44709
+ });
44710
+
44711
+ // src/commands/verify-records.mjs
44712
+ var verify_records_exports = {};
44713
+ __export(verify_records_exports, {
44714
+ VERIFY_RECORDS_EXIT_CODES: () => VERIFY_RECORDS_EXIT_CODES,
44715
+ verifyReleaseRecords: () => verifyReleaseRecords
44716
+ });
44717
+ function sourceLabel(source) {
44718
+ if (typeof source !== "string" || source.length === 0) return null;
44719
+ return source.replaceAll("\\", "/").split("/").filter(Boolean).at(-1) ?? null;
44720
+ }
44721
+ function parseInput(input, role, addFinding) {
44722
+ const summary = { role, source: sourceLabel(input?.source) };
44723
+ if (!input || input.bytes === void 0 || input.bytes === null) {
44724
+ addFinding("INPUT_MISSING", role, `required ${role} input was not provided`, "INSUFFICIENT");
44725
+ return { summary, digest: null, document: null, bytes: null, usable: false };
44726
+ }
44727
+ let bytes;
44728
+ try {
44729
+ bytes = Buffer.isBuffer(input.bytes) ? Buffer.from(input.bytes) : Buffer.from(input.bytes);
44730
+ } catch {
44731
+ addFinding("INPUT_DAMAGED", role, `${role} input is not byte-compatible`);
44732
+ return { summary, digest: null, document: null, bytes: null, usable: false };
44733
+ }
44734
+ const bytesSha256 = digestBytes(bytes);
44735
+ try {
44736
+ const document2 = JSON.parse(bytes.toString("utf8"));
44737
+ if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) {
44738
+ addFinding("INPUT_DAMAGED", role, `${role} input must be a JSON object`);
44739
+ return {
44740
+ summary,
44741
+ digest: { bytesSha256, carriedDomainDigest: null, recomputedDomainDigest: null },
44742
+ document: null,
44743
+ bytes,
44744
+ usable: false
44745
+ };
44746
+ }
44747
+ return {
44748
+ summary,
44749
+ digest: { bytesSha256, carriedDomainDigest: null, recomputedDomainDigest: null },
44750
+ document: document2,
44751
+ bytes,
44752
+ usable: true
44753
+ };
44754
+ } catch {
44755
+ addFinding("INPUT_DAMAGED", role, `${role} input is not valid JSON`);
44756
+ return {
44757
+ summary,
44758
+ digest: { bytesSha256, carriedDomainDigest: null, recomputedDomainDigest: null },
44759
+ document: null,
44760
+ bytes,
44761
+ usable: false
44762
+ };
44763
+ }
44764
+ }
44765
+ function sameStringSet(left, right) {
44766
+ return JSON.stringify([...new Set(left)].sort()) === JSON.stringify([...new Set(right)].sort());
44767
+ }
44768
+ function commandStatusIsValid(run6) {
44769
+ if (run6.command === "publish" || run6.command === "reconcile") {
44770
+ return run6.status === "PARTIAL" || run6.status === "PUBLISHED";
44771
+ }
44772
+ return run6.command === "verify" && run6.status === "VERIFIED";
44773
+ }
44774
+ function checkpointStatusIsValid(run6) {
44775
+ if (run6.status === "VERIFIED") {
44776
+ return run6.checkpoints.every((checkpoint) => ["succeeded", "skipped"].includes(checkpoint.status));
44777
+ }
44778
+ if (run6.status === "PUBLISHED") {
44779
+ return run6.checkpoints.every((checkpoint) => isRemoteWriteAction(checkpoint.actionType) ? ["succeeded", "skipped"].includes(checkpoint.status) : isMarketplaceAction(checkpoint.actionType) ? ["succeeded", "skipped", "deferred", "failed"].includes(checkpoint.status) : true);
44780
+ }
44781
+ if (run6.status === "PARTIAL") {
44782
+ return run6.checkpoints.some((checkpoint) => !["succeeded", "skipped", "deferred"].includes(checkpoint.status));
44783
+ }
44784
+ return false;
44785
+ }
44786
+ function verifyReleaseRecords(request = {}) {
44787
+ const findings = [];
44788
+ const addFinding = /* @__PURE__ */ __name((code, role, message, outcome = "CONTRADICTED") => {
44789
+ findings.push({ code, role, message, _outcome: outcome });
44790
+ }, "addFinding");
44791
+ const planInput = parseInput(request.plan, "plan", addFinding);
44792
+ const approvalInput = parseInput(request.approval, "approval", addFinding);
44793
+ const targetInput = parseInput(request.targetRun, "targetRun", addFinding);
44794
+ const sourceInputs = Array.isArray(request.sourceRuns) ? request.sourceRuns.map((input, index) => parseInput(input, `sourceRun[${index}]`, addFinding)) : [];
44795
+ const plan = planInput.document;
44796
+ const approval = approvalInput.document;
44797
+ const targetRun = targetInput.document;
44798
+ let planSupported = false;
44799
+ if (planInput.usable) {
44800
+ if (!SUPPORTED_PLAN_VERSIONS.has(plan?.planVersion)) {
44801
+ addFinding("FORMAT_UNSUPPORTED", "plan", `unsupported release plan version: ${String(plan?.planVersion)}`, "INSUFFICIENT");
44802
+ } else {
44803
+ planSupported = true;
44804
+ try {
44805
+ validatePlan(plan);
44806
+ } catch (error) {
44807
+ addFinding("INPUT_DAMAGED", "plan", error.message);
44808
+ planSupported = false;
44809
+ }
44810
+ const recomputed = computePlanDigest(plan);
44811
+ planInput.digest.carriedDomainDigest = plan?.digest ?? null;
44812
+ planInput.digest.recomputedDomainDigest = recomputed;
44813
+ if (!plan?.digest || plan.digest !== recomputed) {
44814
+ addFinding("PLAN_DIGEST_MISMATCH", "plan", "plan digest does not match its binding content");
44815
+ }
44816
+ }
44817
+ }
44818
+ let approvalSupported = false;
44819
+ let approvalDigest = null;
44820
+ if (approvalInput.usable) {
44821
+ approvalDigest = computeApprovalDigest(approvalInput.bytes);
44822
+ approvalInput.digest.recomputedDomainDigest = approvalDigest;
44823
+ try {
44824
+ validateApprovalRecordSchema(approval);
44825
+ approvalSupported = true;
44826
+ } catch (error) {
44827
+ addFinding("INPUT_DAMAGED", "approval", error.message);
44828
+ }
44829
+ if (approvalSupported) {
44830
+ try {
44831
+ validateApprovalTimeWindow(approval, {
44832
+ clock: /* @__PURE__ */ __name(() => approval.approvedAt, "clock"),
44833
+ requireUnexpired: false
44834
+ });
44835
+ } catch (error) {
44836
+ addFinding("INPUT_DAMAGED", "approval", error.message);
44837
+ approvalSupported = false;
44838
+ }
44839
+ }
44840
+ }
44841
+ const runEntries = [
44842
+ { input: targetInput, role: "targetRun", isTarget: true },
44843
+ ...sourceInputs.map((input, index) => ({ input, role: `sourceRun[${index}]`, isTarget: false }))
44844
+ ];
44845
+ const usableRuns = [];
44846
+ let trustedTargetTerminalStatus = null;
44847
+ for (const entry of runEntries) {
44848
+ const run6 = entry.input.document;
44849
+ if (!entry.input.usable) continue;
44850
+ entry.input.digest.carriedDomainDigest = run6?.runDigest ?? null;
44851
+ entry.input.digest.recomputedDomainDigest = computeRunDigest(run6);
44852
+ if (!SUPPORTED_RUN_COMMANDS.has(run6?.command) || !SUPPORTED_TERMINAL_STATUSES.has(run6?.status)) {
44853
+ addFinding(
44854
+ "FORMAT_UNSUPPORTED",
44855
+ entry.role,
44856
+ `unsupported release run command/status: ${String(run6?.command)}/${String(run6?.status)}`,
44857
+ "INSUFFICIENT"
44858
+ );
44859
+ continue;
44860
+ }
44861
+ const { runDigest: _runDigest, ...unsignedRun } = run6;
44862
+ try {
44863
+ validateRun(unsignedRun);
44864
+ } catch (error) {
44865
+ addFinding("INPUT_DAMAGED", entry.role, error.message);
44866
+ continue;
44867
+ }
44868
+ const digestMatches = Boolean(run6.runDigest) && run6.runDigest === entry.input.digest.recomputedDomainDigest;
44869
+ const transitionIsValid = commandStatusIsValid(run6);
44870
+ const terminalCheckpointsAreValid = checkpointStatusIsValid(run6);
44871
+ if (!digestMatches) {
44872
+ addFinding("RUN_DIGEST_MISMATCH", entry.role, "run digest does not match its content");
44873
+ }
44874
+ if (!transitionIsValid) {
44875
+ addFinding("TRANSITION_MISMATCH", entry.role, `run command ${run6.command} cannot terminate as ${run6.status}`);
44876
+ }
44877
+ if (!terminalCheckpointsAreValid) {
44878
+ addFinding("CHECKPOINT_MISMATCH", entry.role, `checkpoint results do not support terminal status ${run6.status}`);
44879
+ }
44880
+ if (entry.isTarget && digestMatches && transitionIsValid && terminalCheckpointsAreValid) {
44881
+ trustedTargetTerminalStatus = run6.status;
44882
+ }
44883
+ usableRuns.push({ ...entry, run: run6 });
44884
+ }
44885
+ if (planSupported && typeof request.unitId === "string" && typeof request.targetVersion === "string") {
44886
+ const expectedUnit = (plan.units ?? []).find((unit) => unit.id === request.unitId);
44887
+ if (!expectedUnit || expectedUnit.targetVersion !== request.targetVersion) {
44888
+ addFinding("IDENTITY_MISMATCH", "plan", "expected release unit and version do not match the plan");
44889
+ }
44890
+ } else if (typeof request.unitId !== "string" || typeof request.targetVersion !== "string") {
44891
+ addFinding("INPUT_MISSING", "identity", "unitId and targetVersion are required", "INSUFFICIENT");
44892
+ }
44893
+ if (planSupported && approvalSupported) {
44894
+ const planDigest = computePlanDigest(plan);
44895
+ if (approval.planDigest !== planDigest) {
44896
+ addFinding("PLAN_BINDING_MISMATCH", "approval", "approval planDigest does not match the supplied plan");
44897
+ }
44898
+ const expectedUnitVersions = Object.fromEntries((plan.units ?? []).map((unit) => [unit.id, unit.targetVersion]));
44899
+ const approvedUnitVersions = approval.unitVersions ?? ((plan.units ?? []).length === 1 ? { [plan.units[0].id]: approval.targetVersion } : {});
44900
+ const identityMatches = sameStringSet(Object.keys(expectedUnitVersions), Object.keys(approvedUnitVersions)) && Object.entries(expectedUnitVersions).every(([unitId, version]) => approvedUnitVersions[unitId] === version) && (approval.targetVersion === void 0 || new Set(Object.values(expectedUnitVersions)).size !== 1 || approval.targetVersion === Object.values(expectedUnitVersions)[0]);
44901
+ if (!identityMatches) {
44902
+ addFinding("IDENTITY_MISMATCH", "approval", "approval unit/version identity does not match the plan");
44903
+ }
44904
+ const planActions = (plan.externalActions ?? []).map((action) => action.id);
44905
+ if (!sameStringSet(approval.approvedActions ?? [], planActions)) {
44906
+ addFinding("APPROVAL_ACTION_MISMATCH", "approval", "approved actions do not exactly match the plan actions");
44907
+ }
44908
+ try {
44909
+ validateApproval(plan, approval, {
44910
+ clock: /* @__PURE__ */ __name(() => approval.approvedAt, "clock"),
44911
+ requireUnexpired: false
44912
+ });
44913
+ } catch (error) {
44914
+ const alreadyRepresented = /planDigest/i.test(error.message) && findings.some((finding) => finding.role === "approval" && finding.code === "PLAN_BINDING_MISMATCH") || /version|unitVersions/i.test(error.message) && findings.some((finding) => finding.role === "approval" && finding.code === "IDENTITY_MISMATCH") || /approved|action/i.test(error.message) && findings.some((finding) => finding.role === "approval" && finding.code === "APPROVAL_ACTION_MISMATCH");
44915
+ if (!alreadyRepresented) {
44916
+ addFinding("INPUT_DAMAGED", "approval", error.message);
44917
+ }
44918
+ }
44919
+ }
44920
+ if (planSupported) {
44921
+ for (const entry of usableRuns) {
44922
+ try {
44923
+ validateRunPlanDigest(entry.run, plan);
44924
+ } catch (error) {
44925
+ addFinding("PLAN_BINDING_MISMATCH", entry.role, error.message);
44926
+ }
44927
+ try {
44928
+ validateRunCheckpointMapping(entry.run, plan.externalActions ?? []);
44929
+ } catch (error) {
44930
+ addFinding("CHECKPOINT_MISMATCH", entry.role, error.message);
44931
+ }
44932
+ }
44933
+ }
44934
+ if (approvalDigest) {
44935
+ for (const entry of usableRuns) {
44936
+ if (!entry.run.approvalDigest) {
44937
+ addFinding("INPUT_MISSING", entry.role, "run does not carry the approval digest needed to bind the supplied approval", "INSUFFICIENT");
44938
+ } else if (entry.run.approvalDigest !== approvalDigest) {
44939
+ addFinding("APPROVAL_DIGEST_MISMATCH", entry.role, "run approvalDigest does not match the supplied approval bytes");
44940
+ }
44941
+ }
44942
+ }
44943
+ const supportingRuns = usableRuns.filter((entry) => !entry.isTarget);
44944
+ const chain = [];
44945
+ let lineageComplete = false;
44946
+ if (targetRun && usableRuns.some((entry) => entry.isTarget)) {
44947
+ let current = usableRuns.find((entry) => entry.isTarget);
44948
+ const visited = /* @__PURE__ */ new Set();
44949
+ for (let depth = 0; depth <= 16 && current; depth += 1) {
44950
+ chain.push(current.run);
44951
+ const visitKey = `${current.run.runId}:${current.run.runDigest}`;
44952
+ if (visited.has(visitKey)) {
44953
+ addFinding("RUN_LINEAGE_MISMATCH", current.role, "release run lineage contains a cycle");
44954
+ break;
44955
+ }
44956
+ visited.add(visitKey);
44957
+ if (current.run.command === "publish") {
44958
+ lineageComplete = true;
44959
+ break;
44960
+ }
44961
+ if (!current.run.sourceRunId || !current.run.sourceRunDigest || !current.run.sourceRunPath) {
44962
+ addFinding("INPUT_MISSING", current.role, "run is missing complete source-run lineage fields", "INSUFFICIENT");
44963
+ break;
44964
+ }
44965
+ const exactParent = supportingRuns.find((candidate) => candidate.run.runId === current.run.sourceRunId && candidate.run.runDigest === current.run.sourceRunDigest);
44966
+ if (!exactParent) {
44967
+ const sameId = supportingRuns.find((candidate) => candidate.run.runId === current.run.sourceRunId);
44968
+ if (sameId) {
44969
+ addFinding("RUN_LINEAGE_MISMATCH", current.role, "source run id is present but its digest does not match");
44970
+ } else {
44971
+ addFinding("INPUT_MISSING", current.role, "explicit source run required by lineage was not supplied", "INSUFFICIENT");
44972
+ }
44973
+ break;
44974
+ }
44975
+ try {
44976
+ validateSourceRunEdge(current.run, exactParent.run);
44977
+ } catch (error) {
44978
+ addFinding("TRANSITION_MISMATCH", current.role, error.message);
44979
+ break;
44980
+ }
44981
+ current = exactParent;
44982
+ }
44983
+ if (!lineageComplete && chain.length > 16) {
44984
+ addFinding("RUN_LINEAGE_MISMATCH", "targetRun", "release run lineage exceeds maximum depth");
44985
+ }
44986
+ }
44987
+ for (const entry of usableRuns) {
44988
+ const run6 = entry.run;
44989
+ if (run6.stateSequence === void 0) {
44990
+ if (run6.previousStateDigest !== void 0) {
44991
+ addFinding("RUN_LINEAGE_MISMATCH", entry.role, "non-state run claims a previous state digest");
44992
+ }
44993
+ continue;
44994
+ }
44995
+ if (run6.stateSequence === 0) {
44996
+ if (run6.previousStateDigest !== void 0) {
44997
+ addFinding("RUN_LINEAGE_MISMATCH", entry.role, "initial state run claims a previous state digest");
44998
+ }
44999
+ continue;
45000
+ }
45001
+ const predecessor = supportingRuns.find((candidate) => candidate.run.runId === run6.runId && candidate.run.stateSequence === run6.stateSequence - 1);
45002
+ if (!predecessor) {
45003
+ addFinding("INPUT_MISSING", entry.role, "explicit predecessor state snapshot was not supplied", "INSUFFICIENT");
45004
+ } else if (predecessor.run.runDigest !== run6.previousStateDigest || predecessor.run.command !== run6.command || predecessor.run.planDigest !== run6.planDigest) {
45005
+ addFinding("RUN_LINEAGE_MISMATCH", entry.role, "state predecessor identity or digest does not match");
45006
+ }
45007
+ }
45008
+ if (approvalSupported) {
45009
+ const executionRun = [...chain].reverse().find((run6) => ["publish", "reconcile"].includes(run6.command) && (run6.checkpoints ?? []).some((checkpoint) => isRemoteWriteAction(checkpoint.actionType)));
45010
+ if (!lineageComplete || !executionRun?.startedAt || !executionRun?.finishedAt) {
45011
+ addFinding("HISTORICAL_TIME_MISSING", "approval", "historical publish execution interval cannot be proven", "INSUFFICIENT");
45012
+ } else {
45013
+ const approvedAt = Date.parse(approval.approvedAt);
45014
+ const expiresAt = Date.parse(approval.expiresAt);
45015
+ const startedAt = Date.parse(executionRun.startedAt);
45016
+ const finishedAt = Date.parse(executionRun.finishedAt);
45017
+ if (!Number.isFinite(startedAt) || !Number.isFinite(finishedAt) || startedAt < approvedAt || finishedAt > expiresAt || finishedAt < startedAt) {
45018
+ addFinding("HISTORICAL_TIME_OUTSIDE_WINDOW", "approval", "publish execution interval is outside the approval window");
45019
+ }
45020
+ }
45021
+ }
45022
+ findings.sort((left, right) => (FINDING_RANK.get(left.code) ?? 999) - (FINDING_RANK.get(right.code) ?? 999) || left.role.localeCompare(right.role) || left.message.localeCompare(right.message));
45023
+ const hasContradiction = findings.some((finding) => finding._outcome === "CONTRADICTED");
45024
+ const hasInsufficient = findings.some((finding) => finding._outcome === "INSUFFICIENT");
45025
+ const status = hasContradiction ? "CONTRADICTED" : hasInsufficient ? "INSUFFICIENT" : "CONSISTENT";
45026
+ return {
45027
+ status,
45028
+ unitId: typeof request.unitId === "string" ? request.unitId : null,
45029
+ targetVersion: typeof request.targetVersion === "string" ? request.targetVersion : null,
45030
+ historicalTerminalStatus: trustedTargetTerminalStatus,
45031
+ inputs: {
45032
+ plan: planInput.summary,
45033
+ approval: approvalInput.summary,
45034
+ targetRun: targetInput.summary,
45035
+ sourceRuns: sourceInputs.map((input) => input.summary)
45036
+ },
45037
+ digests: {
45038
+ plan: planInput.digest,
45039
+ approval: approvalInput.digest,
45040
+ targetRun: targetInput.digest,
45041
+ sourceRuns: sourceInputs.map((input) => input.digest)
45042
+ },
45043
+ findings: findings.map(({ _outcome, ...finding }) => finding)
45044
+ };
45045
+ }
45046
+ var VERIFY_RECORDS_EXIT_CODES, SUPPORTED_PLAN_VERSIONS, SUPPORTED_RUN_COMMANDS, SUPPORTED_TERMINAL_STATUSES, FINDING_ORDER, FINDING_RANK;
45047
+ var init_verify_records = __esm({
45048
+ async "src/commands/verify-records.mjs"() {
45049
+ init_src2();
45050
+ await init_approval();
45051
+ init_checkpoints();
45052
+ await init_plan();
45053
+ await init_run();
45054
+ VERIFY_RECORDS_EXIT_CODES = Object.freeze({
45055
+ CONSISTENT: 0,
45056
+ CONTRADICTED: 1,
45057
+ INSUFFICIENT: 2
45058
+ });
45059
+ SUPPORTED_PLAN_VERSIONS = /* @__PURE__ */ new Set([1, 2, 3]);
45060
+ SUPPORTED_RUN_COMMANDS = /* @__PURE__ */ new Set(["publish", "reconcile", "verify"]);
45061
+ SUPPORTED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["PARTIAL", "PUBLISHED", "VERIFIED"]);
45062
+ FINDING_ORDER = Object.freeze([
45063
+ "INPUT_MISSING",
45064
+ "INPUT_DAMAGED",
45065
+ "FORMAT_UNSUPPORTED",
45066
+ "PLAN_DIGEST_MISMATCH",
45067
+ "APPROVAL_DIGEST_MISMATCH",
45068
+ "RUN_DIGEST_MISMATCH",
45069
+ "IDENTITY_MISMATCH",
45070
+ "PLAN_BINDING_MISMATCH",
45071
+ "APPROVAL_ACTION_MISMATCH",
45072
+ "RUN_LINEAGE_MISMATCH",
45073
+ "TRANSITION_MISMATCH",
45074
+ "CHECKPOINT_MISMATCH",
45075
+ "HISTORICAL_TIME_MISSING",
45076
+ "HISTORICAL_TIME_OUTSIDE_WINDOW"
45077
+ ]);
45078
+ FINDING_RANK = new Map(FINDING_ORDER.map((code, index) => [code, index]));
45079
+ __name(sourceLabel, "sourceLabel");
45080
+ __name(parseInput, "parseInput");
45081
+ __name(sameStringSet, "sameStringSet");
45082
+ __name(commandStatusIsValid, "commandStatusIsValid");
45083
+ __name(checkpointStatusIsValid, "checkpointStatusIsValid");
45084
+ __name(verifyReleaseRecords, "verifyReleaseRecords");
45085
+ }
45086
+ });
45087
+
44710
45088
  // src/core/presets.mjs
44711
45089
  var presets_exports = {};
44712
45090
  __export(presets_exports, {
@@ -45854,7 +46232,7 @@ var init_postpublish = __esm({
45854
46232
  ENV_KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
45855
46233
  SAFE_ID_RE2 = /^[a-z0-9][a-z0-9._-]*$/;
45856
46234
  BRANCH_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
45857
- LOCAL_HOSTS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "codebuddy", "workbuddy"]);
46235
+ LOCAL_HOSTS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "codebuddy", "workbuddy", "qoder"]);
45858
46236
  HUB_HOST_RE = /^[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/u;
45859
46237
  HUB_REF_RE = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._-]*(?:\/[A-Za-z0-9][A-Za-z0-9._-]*)*$/u;
45860
46238
  PAYLOAD_SOURCE_TAG_WORKTREE = "tag-worktree";
@@ -132187,17 +132565,38 @@ function hubTargets(plan) {
132187
132565
  codebuddy: "Handle manually; CodeBuddy cannot pin a Hub ref in this flow.",
132188
132566
  workbuddy: "Handle manually; WorkBuddy cannot pin a Hub ref in this flow (it follows the CodeBuddy manual boundary)."
132189
132567
  };
132190
- return local.hosts.map((host) => ({
132191
- targetKind: "hub-backed",
132192
- executionMode: "manual",
132193
- unitId: declaration.unitId,
132194
- host,
132195
- plugin: local.plugin,
132196
- hub,
132197
- message: `${manualInstruction[host]} Install or upgrade ${local.plugin} from Hub ${hub.name}; release-skill does not execute or probe this action.`,
132198
- ...unit?.publicRepo ? { publicRepo: unit.publicRepo } : {},
132199
- ...tag ? { frozenTag: tag } : {}
132200
- }));
132568
+ return local.hosts.map((host) => {
132569
+ if (host === "qoder") {
132570
+ return {
132571
+ targetKind: "hub-backed",
132572
+ executionMode: "executable",
132573
+ unitId: declaration.unitId,
132574
+ host,
132575
+ plugin: local.plugin,
132576
+ marketplace: hub.name,
132577
+ hub,
132578
+ version: unit?.targetVersion,
132579
+ pluginRepo: unit?.publicRepo,
132580
+ pluginCommit: unit?.frozenSnapshot?.commit,
132581
+ snapshotPath: unit?.frozenSnapshot?.path,
132582
+ manifestDigest: unit?.frozenSnapshot?.manifestDigest,
132583
+ timeoutMs: 3e5,
132584
+ message: `Update ${local.plugin} from Qoder Hub ${hub.name}; a new session or /plugins reload is required before treating the updated plugin as loaded.`,
132585
+ ...tag ? { frozenTag: tag } : {}
132586
+ };
132587
+ }
132588
+ return {
132589
+ targetKind: "hub-backed",
132590
+ executionMode: "manual",
132591
+ unitId: declaration.unitId,
132592
+ host,
132593
+ plugin: local.plugin,
132594
+ hub,
132595
+ message: `${manualInstruction[host]} Install or upgrade ${local.plugin} from Hub ${hub.name}; release-skill does not execute or probe this action.`,
132596
+ ...unit?.publicRepo ? { publicRepo: unit.publicRepo } : {},
132597
+ ...tag ? { frozenTag: tag } : {}
132598
+ };
132599
+ });
132201
132600
  });
132202
132601
  }
132203
132602
  function mergePostReleaseTargets(executableTargets, manualTargets) {
@@ -132252,6 +132651,28 @@ function assertExecutableTarget(target) {
132252
132651
  throw new Error(`local host update requires the frozen public commit for unit ${target.unitId}`);
132253
132652
  }
132254
132653
  }
132654
+ function assertQoderExecutableTarget(target) {
132655
+ for (const field of [
132656
+ "unitId",
132657
+ "plugin",
132658
+ "marketplace",
132659
+ "version",
132660
+ "pluginRepo",
132661
+ "pluginCommit",
132662
+ "snapshotPath",
132663
+ "manifestDigest"
132664
+ ]) {
132665
+ if (typeof target[field] !== "string" || target[field].length === 0) {
132666
+ throw new Error(`Qoder local host update target is missing ${field}`);
132667
+ }
132668
+ }
132669
+ if (!/^[a-f0-9]{40}$/u.test(target.pluginCommit)) {
132670
+ throw new Error(`Qoder local host update requires the frozen public commit for unit ${target.unitId}`);
132671
+ }
132672
+ if (!/^[a-f0-9]{64}$/u.test(target.manifestDigest)) {
132673
+ throw new Error(`Qoder local host update requires the frozen snapshot digest for unit ${target.unitId}`);
132674
+ }
132675
+ }
132255
132676
  function buildShipNextStep({ root, statePath, unitIds }) {
132256
132677
  const argv = ["release-skill", "ship"];
132257
132678
  if (typeof root === "string" && root.length > 0) argv.push("--root", root);
@@ -132277,8 +132698,12 @@ function derivePostReleaseChecklist(plan, {
132277
132698
  }
132278
132699
  const units = plan.units ?? [];
132279
132700
  const uncovered = units.filter((unit) => !BRANCH_ACTION_INCLUDED.has(unit.productionConfig?.branchStrategy));
132280
- const executableTargets = pluginTargets(plan);
132281
- const manualTargets = hubTargets(plan);
132701
+ const declaredHubTargets = hubTargets(plan);
132702
+ const executableTargets = [
132703
+ ...pluginTargets(plan),
132704
+ ...declaredHubTargets.filter((target) => target.executionMode === "executable")
132705
+ ];
132706
+ const manualTargets = declaredHubTargets.filter((target) => target.executionMode === "manual");
132282
132707
  const targets = mergePostReleaseTargets(executableTargets, manualTargets);
132283
132708
  const hasPendingPostVerify = postVerifyHooks(plan).length > 0 && !postVerifyComplete && targets.length > 0;
132284
132709
  const hasStatePath = typeof statePath === "string" && statePath.length > 0;
@@ -132642,6 +133067,184 @@ function exactPluginObservation(target, stdout) {
132642
133067
  function normalizeGitSource(source) {
132643
133068
  return String(source ?? "").replace(/^git\+/, "").replace(/^https:\/\/github\.com\//, "").replace(/^git@github\.com:/, "").replace(/\.git$/u, "").replace(/\/$/u, "");
132644
133069
  }
133070
+ function normalizeHubGitSource(source, githubHost) {
133071
+ return String(source ?? "").replace(/^git\+/, "").replace(new RegExp(`^https://${String(githubHost).replaceAll(".", "\\.").replaceAll("-", "\\-")}/`), "").replace(new RegExp(`^git@${String(githubHost).replaceAll(".", "\\.").replaceAll("-", "\\-")}:`), "").replace(/\.git$/u, "").replace(/\/$/u, "");
133072
+ }
133073
+ async function observeQoderMarketplace(target, command2, env, run6) {
133074
+ const listed = await run6(command2, [...QODER_MARKETPLACE_LIST_ARGS], {
133075
+ env,
133076
+ timeout: target.timeoutMs
133077
+ });
133078
+ const parsed = parseJson2(listed.stdout, "qoder marketplace list");
133079
+ if (!Array.isArray(parsed)) throw new Error("qoder marketplace list did not return an array");
133080
+ const matches = parsed.filter((entry) => entry?.name === target.marketplace);
133081
+ if (matches.length === 0) return { installed: false };
133082
+ if (matches.length !== 1) {
133083
+ throw new Error(`qoder marketplace list returned conflicting entries for ${target.marketplace}`);
133084
+ }
133085
+ const [found] = matches;
133086
+ if (found.source?.source !== "git" || normalizeHubGitSource(found.source.url, target.hub.githubHost) !== target.hub.repo) {
133087
+ throw new Error(`qoder marketplace ${target.marketplace} does not point to ${target.hub.repo}`);
133088
+ }
133089
+ if (typeof found.installLocation !== "string" || found.installLocation.length === 0) {
133090
+ throw new Error(`qoder marketplace ${target.marketplace} has no observable checkout root`);
133091
+ }
133092
+ const [remote, branch] = await Promise.all([
133093
+ run6("git", ["-C", found.installLocation, "remote", "get-url", "origin"], {
133094
+ env,
133095
+ timeout: 3e4
133096
+ }),
133097
+ run6("git", ["-C", found.installLocation, "symbolic-ref", "-q", "HEAD"], {
133098
+ env,
133099
+ timeout: 3e4
133100
+ })
133101
+ ]);
133102
+ if (normalizeHubGitSource(remote.stdout.trim(), target.hub.githubHost) !== target.hub.repo || branch.stdout.trim() !== target.hub.ref) {
133103
+ throw new Error(`qoder marketplace ${target.marketplace} checkout does not match the frozen Hub source`);
133104
+ }
133105
+ return { installed: true, root: found.installLocation, found };
133106
+ }
133107
+ async function observeQoderPlugin(target, command2, env, run6) {
133108
+ const listed = await run6(command2, [...QODER_PLUGIN_LIST_ARGS], {
133109
+ env,
133110
+ timeout: target.timeoutMs
133111
+ });
133112
+ const parsed = parseJson2(listed.stdout, "qoder plugin list");
133113
+ if (!Array.isArray(parsed)) throw new Error("qoder plugin list did not return an array");
133114
+ const selector = `${target.plugin}@${target.marketplace}`;
133115
+ const matches = parsed.filter((entry) => entry?.id === selector);
133116
+ if (matches.length === 0) return { installed: false };
133117
+ if (matches.length !== 1) {
133118
+ throw new Error(`qoder plugin list returned conflicting entries for ${selector}`);
133119
+ }
133120
+ const [found] = matches;
133121
+ if (found.name !== target.plugin || found.source !== selector || found.scope !== "user" || typeof found.installPath !== "string" || found.installPath.length === 0) {
133122
+ throw new Error(`qoder plugin ${selector} does not match its frozen user-scope identity`);
133123
+ }
133124
+ return {
133125
+ installed: true,
133126
+ exact: found.version === target.version,
133127
+ installPath: found.installPath,
133128
+ found
133129
+ };
133130
+ }
133131
+ async function readQoderManifest(root, label) {
133132
+ const manifest = parseJson2(
133133
+ await readFileContained(root, ".qoder-plugin/plugin.json", { encoding: "utf8" }),
133134
+ label
133135
+ );
133136
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
133137
+ throw new Error(`${label} did not return an object`);
133138
+ }
133139
+ return manifest;
133140
+ }
133141
+ function assertQoderPluginManifest(target, manifest, label) {
133142
+ if (manifest.name !== target.plugin || manifest.version !== target.version || manifest.skills !== "./adapters/qoder/skills/") {
133143
+ throw new Error(`${label} does not match the frozen Qoder plugin identity and projection path`);
133144
+ }
133145
+ }
133146
+ async function assertFrozenQoderManifest(target, root) {
133147
+ const { snapshotDir: snapshotRoot } = await verifyFrozenSnapshot({
133148
+ root,
133149
+ snapshotPath: target.snapshotPath,
133150
+ expectedDigest: target.manifestDigest
133151
+ });
133152
+ const manifest = await readQoderManifest(snapshotRoot, "frozen Qoder plugin manifest");
133153
+ assertQoderPluginManifest(target, manifest, "frozen Qoder plugin manifest");
133154
+ }
133155
+ async function assertQoderHubEntry(target, marketplaceRoot) {
133156
+ const marketplace = parseJson2(
133157
+ await readFileContained(marketplaceRoot, "marketplace.json", { encoding: "utf8" }),
133158
+ "Qoder Hub marketplace manifest"
133159
+ );
133160
+ if (marketplace?.name !== target.marketplace || !Array.isArray(marketplace.plugins)) {
133161
+ throw new Error("Qoder Hub marketplace manifest does not match the frozen marketplace");
133162
+ }
133163
+ const matches = marketplace.plugins.filter((entry) => entry?.name === target.plugin);
133164
+ if (matches.length !== 1) {
133165
+ throw new Error(`Qoder Hub marketplace manifest must contain exactly one ${target.plugin} entry`);
133166
+ }
133167
+ const source = matches[0]?.source;
133168
+ if (source?.source !== "url" || normalizeHubGitSource(source.url, target.hub.githubHost) !== target.pluginRepo || source.sha !== target.pluginCommit) {
133169
+ throw new Error("Qoder Hub entry does not match the frozen public source identity");
133170
+ }
133171
+ }
133172
+ async function verifyQoderInstalledPayload({
133173
+ target,
133174
+ root,
133175
+ installPath,
133176
+ verifyInstalledPayload
133177
+ }) {
133178
+ const manifest = await readQoderManifest(installPath, "installed Qoder plugin manifest");
133179
+ assertQoderPluginManifest(target, manifest, "installed Qoder plugin manifest");
133180
+ await verifyInstalledPayload({
133181
+ snapshotPath: target.snapshotPath,
133182
+ manifestDigest: target.manifestDigest,
133183
+ payloadContract: QODER_PAYLOAD_CONTRACT,
133184
+ marketplaceLocation: "external"
133185
+ }, { root }, installPath, "qoder");
133186
+ }
133187
+ async function runQoderUpdate(target, detected, run6, {
133188
+ root,
133189
+ verifyInstalledPayload
133190
+ }) {
133191
+ const env = hostEnvironment("qoder");
133192
+ await assertFrozenQoderManifest(target, root);
133193
+ const marketplace = await observeQoderMarketplace(target, detected.command, env, run6);
133194
+ if (!marketplace.installed) {
133195
+ return {
133196
+ status: "MANUAL_REQUIRED",
133197
+ reason: `qoder marketplace ${target.marketplace} is not installed; no marketplace was added`
133198
+ };
133199
+ }
133200
+ const before = await observeQoderPlugin(target, detected.command, env, run6);
133201
+ if (!before.installed) {
133202
+ return {
133203
+ status: "MANUAL_REQUIRED",
133204
+ reason: "qoder target plugin is not installed; no initial installation was performed"
133205
+ };
133206
+ }
133207
+ if (before.exact) {
133208
+ await assertQoderHubEntry(target, marketplace.root);
133209
+ await verifyQoderInstalledPayload({
133210
+ target,
133211
+ root,
133212
+ installPath: before.installPath,
133213
+ verifyInstalledPayload
133214
+ });
133215
+ return { status: "ALREADY_CURRENT", version: target.version };
133216
+ }
133217
+ await run6(detected.command, ["plugins", "marketplace", "update", target.marketplace], {
133218
+ env,
133219
+ timeout: target.timeoutMs
133220
+ });
133221
+ const refreshed = await observeQoderMarketplace(target, detected.command, env, run6);
133222
+ if (!refreshed.installed) {
133223
+ throw new Error(`qoder marketplace ${target.marketplace} disappeared after refresh`);
133224
+ }
133225
+ await assertQoderHubEntry(target, refreshed.root);
133226
+ await run6(detected.command, [
133227
+ "plugins",
133228
+ "update",
133229
+ `${target.plugin}@${target.marketplace}`,
133230
+ "--scope",
133231
+ "user"
133232
+ ], { env, timeout: target.timeoutMs });
133233
+ const after = await observeQoderPlugin(target, detected.command, env, run6);
133234
+ if (!after.exact) throw new Error("qoder did not update to the frozen plugin version");
133235
+ await verifyQoderInstalledPayload({
133236
+ target,
133237
+ root,
133238
+ installPath: after.installPath,
133239
+ verifyInstalledPayload
133240
+ });
133241
+ return {
133242
+ status: "UPDATED",
133243
+ version: target.version,
133244
+ restartRequired: true,
133245
+ reloadInstruction: "Start a new Qoder session or run /plugins reload before checking the loaded version."
133246
+ };
133247
+ }
132645
133248
  function parseFrozenRemoteRef(target, stdout) {
132646
133249
  const directRefs = /* @__PURE__ */ new Map();
132647
133250
  const peeledRefs = /* @__PURE__ */ new Map();
@@ -132954,14 +133557,14 @@ expect {
132954
133557
  eof { failEof remove-confirmation 126 }
132955
133558
  }
132956
133559
  expect {
132957
- -nocase -re {Trust this folder\\?} { directoryTrust }
133560
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
132958
133561
  -re $promptPattern {}
132959
133562
  timeout { failTimeout remove-prompt 128 130 }
132960
133563
  eof { failEof remove-prompt 129 }
132961
133564
  }
132962
133565
  }
132963
133566
  -re $promptPattern {}
132964
- -nocase -re {Trust this folder\\?} { directoryTrust }
133567
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
132965
133568
  timeout { failTimeout remove-dialog 122 124 }
132966
133569
  eof { failEof remove-dialog 123 }
132967
133570
  }
@@ -133035,9 +133638,62 @@ proc failEof {state code} {
133035
133638
  exit $code
133036
133639
  }
133037
133640
 
133038
- proc directoryTrust {} {
133039
- puts stderr "KIMI_TUI_STATE:directory-trust:manual-required"
133040
- exit 80
133641
+ proc unexpectedDirectoryTrust {} {
133642
+ puts stderr "KIMI_TUI_STATE:directory-trust:unexpected"
133643
+ exit 147
133644
+ }
133645
+
133646
+ proc confirmInitialDirectoryTrust {} {
133647
+ global expect_out promptPattern
133648
+ set dialogBuffer $expect_out(buffer)
133649
+ expect {
133650
+ -nocase -re {\u276F[^\\r\\n]*No,[ \\t]*exit} {
133651
+ append dialogBuffer $expect_out(buffer)
133652
+ }
133653
+ -re {\u276F[^\\r\\n]*\\r*\\n} {
133654
+ puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
133655
+ exit 143
133656
+ }
133657
+ timeout {
133658
+ puts stderr "KIMI_TUI_STATE:directory-trust-selected-row:timeout"
133659
+ exit 140
133660
+ }
133661
+ eof { failEof directory-trust-selected-row 141 }
133662
+ }
133663
+ expect {
133664
+ -nocase -re {(?:^|\\r|\\n)[ \\t]+Trust this folder[ \\t]*\\r*\\n} {
133665
+ append dialogBuffer $expect_out(buffer)
133666
+ }
133667
+ -re {(?:^|\\r|\\n)[^\\r\\n]*\\r*\\n} {
133668
+ puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
133669
+ exit 143
133670
+ }
133671
+ timeout { failTimeout directory-trust-target-row 148 150 }
133672
+ eof { failEof directory-trust-target-row 149 }
133673
+ }
133674
+ set dialog [cleanScreen $dialogBuffer]
133675
+ if {![regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*No,[ \\t]*exit} $dialog]
133676
+ || ![regexp -nocase {(^|\\n)[ \\t]+Trust this folder[ \\t]*($|\\n)} $dialog]} {
133677
+ puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
133678
+ exit 143
133679
+ }
133680
+ send -- "\\033\\[B"
133681
+ expect {
133682
+ -nocase -re {\u276F[^\\r\\n]*Trust this folder} {}
133683
+ -re {\u276F[^\\r\\n]*\\r*\\n} {
133684
+ puts stderr "KIMI_TUI_STATE:directory-trust-confirm-selection:unknown"
133685
+ exit 146
133686
+ }
133687
+ timeout { failTimeout directory-trust-confirm-selection 144 146 }
133688
+ eof { failEof directory-trust-confirm-selection 145 }
133689
+ }
133690
+ send -- "\\033\\[13u"
133691
+ expect {
133692
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
133693
+ -re $promptPattern {}
133694
+ timeout { failTimeout directory-trust-prompt 151 153 }
133695
+ eof { failEof directory-trust-prompt 152 }
133696
+ }
133041
133697
  }
133042
133698
 
133043
133699
  proc submitCommand {command} {
@@ -133053,7 +133709,7 @@ if {[catch {exec stty columns 240 rows 60 < $spawn_out(slave,name)} resizeError]
133053
133709
  exit 131
133054
133710
  }
133055
133711
  expect {
133056
- -nocase -re {Trust this folder\\?} { directoryTrust }
133712
+ -nocase -re {Trust this folder\\?} { confirmInitialDirectoryTrust }
133057
133713
  -re $promptPattern {}
133058
133714
  timeout { failTimeout initial-prompt 101 103 }
133059
133715
  eof { failEof initial-prompt 102 }
@@ -133061,7 +133717,7 @@ expect {
133061
133717
  ${removeCommand}submitCommand "/plugins install $installUrl"
133062
133718
  set dialogBuffer ""
133063
133719
  expect {
133064
- -nocase -re {Trust this folder\\?} { directoryTrust }
133720
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
133065
133721
  -nocase -re {(?:Install third-party plugin|Trust and install from)[ \\t]} {
133066
133722
  append dialogBuffer $expect_out(buffer)
133067
133723
  }
@@ -133069,7 +133725,7 @@ expect {
133069
133725
  eof { failEof plugin-trust-anchor 105 }
133070
133726
  }
133071
133727
  expect {
133072
- -nocase -re {Trust this folder\\?} { directoryTrust }
133728
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
133073
133729
  -nocase -re {\u276F[^\\r\\n]*(?:Exit|Cancel|Trust and install)} {
133074
133730
  append dialogBuffer $expect_out(buffer)
133075
133731
  }
@@ -133105,7 +133761,7 @@ if {[regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*trust and install} $dialog]} {
133105
133761
  } elseif {[regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*(cancel|exit)} $dialog]} {
133106
133762
  send -- "\\033\\[B"
133107
133763
  expect {
133108
- -nocase -re {Trust this folder\\?} { directoryTrust }
133764
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
133109
133765
  -nocase -re {\u276F[^\\r\\n]*Trust and install} {}
133110
133766
  -re {\u276F[^\\r\\n]*\\r*\\n} {
133111
133767
  puts stderr "KIMI_TUI_STATE:plugin-trust-confirm-selection:unknown"
@@ -133121,7 +133777,7 @@ if {[regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*trust and install} $dialog]} {
133121
133777
  }
133122
133778
 
133123
133779
  expect {
133124
- -nocase -re {Trust this folder\\?} { directoryTrust }
133780
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
133125
133781
  -nocase -re {Install finished[^\\r\\n]*see details below\\.} {}
133126
133782
  -nocase -re {Installing plugin from[^\\r\\n]*(?:\\r|\\n)} {
133127
133783
  exp_continue -continue_timer
@@ -133141,14 +133797,14 @@ expect {
133141
133797
  eof { failEof install-result 137 }
133142
133798
  }
133143
133799
  expect {
133144
- -nocase -re {Trust this folder\\?} { directoryTrust }
133800
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
133145
133801
  -re $promptPattern {}
133146
133802
  timeout { failTimeout post-install-prompt 114 116 }
133147
133803
  eof { failEof post-install-prompt 115 }
133148
133804
  }
133149
133805
  submitCommand "/reload"
133150
133806
  expect {
133151
- -nocase -re {Trust this folder\\?} { directoryTrust }
133807
+ -nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
133152
133808
  -re $promptPattern {}
133153
133809
  timeout { failTimeout reload-prompt 117 119 }
133154
133810
  eof { failEof reload-prompt 118 }
@@ -133246,29 +133902,20 @@ async function runKimiUpdate(target, detected, run6, kimiHome, {
133246
133902
  }
133247
133903
  const installUrl = `https://github.com/${target.pluginRepo}/releases/tag/${target.pluginTag}`;
133248
133904
  const removePlugin = before.source === "legacy" ? target.plugin : "";
133249
- try {
133250
- await run6(detected.expectCommand, ["-c", kimiExpectProgram({
133251
- ...removePlugin ? { removePlugin } : {}
133252
- })], {
133253
- timeout: Math.max(3e5, target.timeoutMs),
133254
- env: {
133255
- ...env,
133256
- RELEASE_SKILL_KIMI_COMMAND: detected.command,
133257
- RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
133258
- RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
133259
- RELEASE_SKILL_KIMI_EXPECTED_REPO: `https://github.com/${target.pluginRepo}`,
133260
- RELEASE_SKILL_KIMI_EXPECTED_TAG: target.pluginTag
133261
- }
133262
- });
133263
- } catch (error) {
133264
- if (error?.exitStatus === 80) {
133265
- return {
133266
- status: "MANUAL_REQUIRED",
133267
- reason: "Kimi requires folder trust; release-finish did not confirm the folder or continue installation"
133268
- };
133905
+ await run6(detected.expectCommand, ["-c", kimiExpectProgram({
133906
+ ...removePlugin ? { removePlugin } : {}
133907
+ })], {
133908
+ cwd: root,
133909
+ timeout: Math.max(3e5, target.timeoutMs),
133910
+ env: {
133911
+ ...env,
133912
+ RELEASE_SKILL_KIMI_COMMAND: detected.command,
133913
+ RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
133914
+ RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
133915
+ RELEASE_SKILL_KIMI_EXPECTED_REPO: `https://github.com/${target.pluginRepo}`,
133916
+ RELEASE_SKILL_KIMI_EXPECTED_TAG: target.pluginTag
133269
133917
  }
133270
- throw error;
133271
- }
133918
+ });
133272
133919
  }, { prefix: "release-skill-kimi-update-" });
133273
133920
  if (tuiOutcome) return tuiOutcome;
133274
133921
  const after = await observeKimiTarget(target, kimiHome, run6);
@@ -133325,8 +133972,11 @@ async function updateLocalHostPluginsInternal({
133325
133972
  const selected = new Set(Array.isArray(selectedHosts) ? selectedHosts : []);
133326
133973
  const unknown = [...selected].filter((host) => !checklist.localHostUpdate.hosts.includes(host));
133327
133974
  if (unknown.length > 0) throw new Error(`selected hosts are not declared by the plan: ${unknown.join(", ")}`);
133328
- const targets = pluginTargets(plan).filter((item) => selected.has(item.host));
133329
- for (const target of targets) assertExecutableTarget(target);
133975
+ const targets = checklist.localHostUpdate.targets.filter((item) => item.executionMode !== "manual" && selected.has(item.host));
133976
+ for (const target of targets) {
133977
+ if (target.host === "qoder") assertQoderExecutableTarget(target);
133978
+ else assertExecutableTarget(target);
133979
+ }
133330
133980
  const results = [];
133331
133981
  for (const target of targets) {
133332
133982
  try {
@@ -133344,6 +133994,9 @@ async function updateLocalHostPluginsInternal({
133344
133994
  plan,
133345
133995
  root,
133346
133996
  verifyInstalledPayload
133997
+ }) : target.host === "qoder" ? await runQoderUpdate(target, detected, run6, {
133998
+ root,
133999
+ verifyInstalledPayload
133347
134000
  }) : await runStructuredUpdate(target, detected, run6, {
133348
134001
  plan,
133349
134002
  root,
@@ -133363,7 +134016,7 @@ async function updateLocalHostPluginsInternal({
133363
134016
  releaseStatusChanged: false
133364
134017
  };
133365
134018
  }
133366
- var HOSTS_BY_ACTION, DISTRIBUTION_BY_ACTION, BRANCH_ACTION_INCLUDED, CODEBUDDY_MACOS_PATH, SAFE_ENV_KEYS, CODEBUDDY_PLUGIN_LIST_ARGS, CODEBUDDY_MARKETPLACE_LIST_ARGS;
134019
+ var HOSTS_BY_ACTION, DISTRIBUTION_BY_ACTION, BRANCH_ACTION_INCLUDED, CODEBUDDY_MACOS_PATH, SAFE_ENV_KEYS, CODEBUDDY_PLUGIN_LIST_ARGS, CODEBUDDY_MARKETPLACE_LIST_ARGS, QODER_PLUGIN_LIST_ARGS, QODER_MARKETPLACE_LIST_ARGS, QODER_PAYLOAD_CONTRACT;
133367
134020
  var init_post_release_local = __esm({
133368
134021
  async "src/commands/post-release-local.mjs"() {
133369
134022
  init_src2();
@@ -133372,6 +134025,7 @@ var init_post_release_local = __esm({
133372
134025
  await init_plugin_marketplace();
133373
134026
  init_postpublish();
133374
134027
  await init_run();
134028
+ init_frozen();
133375
134029
  HOSTS_BY_ACTION = Object.freeze({
133376
134030
  "claude-marketplace-install": ["claude"],
133377
134031
  "codex-marketplace-install": ["codex"],
@@ -133412,12 +134066,16 @@ var init_post_release_local = __esm({
133412
134066
  ]);
133413
134067
  CODEBUDDY_PLUGIN_LIST_ARGS = Object.freeze(["plugin", "list", "--json"]);
133414
134068
  CODEBUDDY_MARKETPLACE_LIST_ARGS = Object.freeze(["plugin", "marketplace", "list"]);
134069
+ QODER_PLUGIN_LIST_ARGS = Object.freeze(["plugins", "list", "--json"]);
134070
+ QODER_MARKETPLACE_LIST_ARGS = Object.freeze(["plugins", "marketplace", "list", "--json"]);
134071
+ QODER_PAYLOAD_CONTRACT = "external-marketplace-v1";
133415
134072
  __name(attachFoundationFailure, "attachFoundationFailure");
133416
134073
  __name(actionTarget, "actionTarget");
133417
134074
  __name(pluginTargets, "pluginTargets");
133418
134075
  __name(hubTargets, "hubTargets");
133419
134076
  __name(mergePostReleaseTargets, "mergePostReleaseTargets");
133420
134077
  __name(assertExecutableTarget, "assertExecutableTarget");
134078
+ __name(assertQoderExecutableTarget, "assertQoderExecutableTarget");
133421
134079
  __name(buildShipNextStep, "buildShipNextStep");
133422
134080
  __name(derivePostReleaseChecklist, "derivePostReleaseChecklist");
133423
134081
  __name(postVerifyHooks, "postVerifyHooks");
@@ -133441,6 +134099,15 @@ var init_post_release_local = __esm({
133441
134099
  __name(observeCodeBuddyMarketplaceAfterResidual, "observeCodeBuddyMarketplaceAfterResidual");
133442
134100
  __name(exactPluginObservation, "exactPluginObservation");
133443
134101
  __name(normalizeGitSource, "normalizeGitSource");
134102
+ __name(normalizeHubGitSource, "normalizeHubGitSource");
134103
+ __name(observeQoderMarketplace, "observeQoderMarketplace");
134104
+ __name(observeQoderPlugin, "observeQoderPlugin");
134105
+ __name(readQoderManifest, "readQoderManifest");
134106
+ __name(assertQoderPluginManifest, "assertQoderPluginManifest");
134107
+ __name(assertFrozenQoderManifest, "assertFrozenQoderManifest");
134108
+ __name(assertQoderHubEntry, "assertQoderHubEntry");
134109
+ __name(verifyQoderInstalledPayload, "verifyQoderInstalledPayload");
134110
+ __name(runQoderUpdate, "runQoderUpdate");
133444
134111
  __name(parseFrozenRemoteRef, "parseFrozenRemoteRef");
133445
134112
  __name(preflightStructuredMarketplace, "preflightStructuredMarketplace");
133446
134113
  __name(observeMarketplace, "observeMarketplace");
@@ -146705,10 +147372,10 @@ guardOutputStream(process.stdout);
146705
147372
  guardOutputStream(process.stderr);
146706
147373
  registerPathRedactor(redactSensitivePaths);
146707
147374
  var execFile19 = promisify26(execFileCb22);
146708
- var COMMANDS = /* @__PURE__ */ new Set(["help", "setup", "assess", "prepare", "approve", "publish", "reconcile", "verify", "postverify", "ship", "post-release", "attest", "hooks", "artifacts", "docs", "distribute", "route", "lineage"]);
147375
+ var COMMANDS = /* @__PURE__ */ new Set(["help", "setup", "assess", "prepare", "approve", "publish", "reconcile", "verify", "verify-records", "postverify", "ship", "post-release", "attest", "hooks", "artifacts", "docs", "distribute", "route", "lineage"]);
146709
147376
  function printHubManualTargets(targets = []) {
146710
147377
  for (const target of targets) {
146711
- if (target.targetKind === "hub-backed") {
147378
+ if (target.targetKind === "hub-backed" && target.executionMode === "manual") {
146712
147379
  console.log(`Manual host update: ${target.plugin} via Hub ${target.hub.name} (${target.host}). ${target.message}`);
146713
147380
  }
146714
147381
  }
@@ -146864,6 +147531,12 @@ async function performEnvironmentChecks() {
146864
147531
  required: false,
146865
147532
  usage: "\u4EC5\u4F9B\u53D1\u5E03\u540E\u7684\u4EBA\u5DE5\u5B89\u88C5\u4F7F\u7528\uFF1Brelease-skill \u4E0D\u6838\u9A8C CodeBuddy/WorkBuddy \u5B89\u88C5"
146866
147533
  };
147534
+ const qoderCheck = await checkDependency("qoder", ["--version"]);
147535
+ checks.qoder = {
147536
+ ...qoderCheck,
147537
+ required: false,
147538
+ usage: "\u4EC5\u4F9B\u53D1\u5E03\u540E\u7684\u53EF\u9009 Qoder \u672C\u673A\u66F4\u65B0\u4F7F\u7528\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6"
147539
+ };
146867
147540
  return checks;
146868
147541
  }
146869
147542
  __name(performEnvironmentChecks, "performEnvironmentChecks");
@@ -146892,7 +147565,7 @@ function getCapabilityMaturity() {
146892
147565
  publish: {
146893
147566
  available: true,
146894
147567
  mode: "controlled production (protocol-tested; no OS/network sandbox)",
146895
- description: "Publishes frozen GitHub/npm artifacts after one readable-plan approval, runs automated Claude/Codex checkpoints, and emits non-blocking Kimi/CodeBuddy manual follow-ups"
147568
+ description: "Publishes frozen GitHub/npm artifacts after one readable-plan approval, runs automated Claude/Codex checkpoints, and emits non-blocking Kimi/CodeBuddy manual follow-ups plus the optional Qoder post-release update"
146896
147569
  },
146897
147570
  reconcile: {
146898
147571
  available: true,
@@ -146902,7 +147575,12 @@ function getCapabilityMaturity() {
146902
147575
  verify: {
146903
147576
  available: true,
146904
147577
  mode: "fresh consumer verification (protocol-tested; no OS/network sandbox)",
146905
- description: "Recheck remote state, exact npm installation, CLI help, and automated Claude/Codex installs before VERIFIED; Kimi/CodeBuddy remain unverified manual follow-ups"
147578
+ description: "Recheck remote state, exact npm installation, CLI help, and automated Claude/Codex installs before VERIFIED; Kimi/CodeBuddy remain unverified manual follow-ups and Qoder remains optional post-release local work"
147579
+ },
147580
+ verifyRecords: {
147581
+ available: true,
147582
+ mode: "offline explicit-input verification",
147583
+ description: "Verify caller-supplied plan, approval, and release-run bytes without record discovery, remote access, process execution, or state changes"
146906
147584
  },
146907
147585
  postverify: {
146908
147586
  available: true,
@@ -146942,6 +147620,7 @@ Commands:
146942
147620
  publish Publish frozen GitHub/npm artifacts after approval
146943
147621
  reconcile Resume PARTIAL state from evidence; conflicts require a human
146944
147622
  verify Fresh remote and consumer verification; only this reaches VERIFIED
147623
+ verify-records Verify explicitly supplied historical plan, approval, and run records offline
146945
147624
  postverify Run approved postVerify hooks from a VERIFIED run in an independent run
146946
147625
  ship Resume one durable prepare -> approve -> publish -> verify flow; completes a parked
146947
147626
  postVerify hook once its checkpoint approval is provided (--hook-approval)
@@ -146960,6 +147639,8 @@ Options:
146960
147639
  --plan <path> Path to the release plan file (required for approve/publish/reconcile/verify/postverify)
146961
147640
  --run <path> Path to the release run file, or a run directory whose release-run.json
146962
147641
  is resolved automatically (required for reconcile/verify/postverify)
147642
+ --target-run <path> Target release-run.json for verify-records
147643
+ --source-run <path> Explicit predecessor release-run.json for verify-records (repeatable)
146963
147644
  --approval <path> Path to the release-level approval record (required for publish/postverify)
146964
147645
  --production Prepare immutable Git/npm production artifacts
146965
147646
  --output <path> Override prepare/approve output path (non-production only)
@@ -146988,6 +147669,7 @@ Options:
146988
147669
  --select-hooks <ids> Comma-separated proposal ids to adopt (propose-hooks mode)
146989
147670
  --foundation-profile <path> Explicit foundation postPublish profile JSON (proposal input only; never auto-applied)
146990
147671
  --unit <id> Select a release unit for prepare/ship (repeatable); docs refresh accepts one unit
147672
+ --target-version <version> Expected release unit version for prepare/ship/verify-records
146991
147673
  --confirm-refresh <sha256:...> Confirm the exact dry-run refreshDigest before any document write
146992
147674
  --ack-local-document-write Acknowledge the explicit local release-document write (docs refresh --write)
146993
147675
  --platform <id> Legacy attestation platform: kimi or codebuddy
@@ -147000,7 +147682,7 @@ Options:
147000
147682
  --hook-approval <path> Checkpoint approval for one requiresApproval hook (ship/distribute/postverify; repeatable)
147001
147683
  --state <path> Override the durable ship state file
147002
147684
  --update-local-hosts Update installed plugins for selected local hosts after VERIFIED
147003
- --hosts <ids> Comma-separated local hosts for post-release update. No local host is updated unless --hosts contains at least one id
147685
+ --hosts <ids> Comma-separated local hosts for post-release update: claude,codex,kimi,codebuddy,workbuddy,qoder. No local host is updated unless --hosts contains at least one id
147004
147686
  --confirm-plan <digest> Confirm the exact VERIFIED plan before local host mutation
147005
147687
  --no-hook-cache Force every prepare hook to run in full; neither read nor write the hook cache
147006
147688
  --json Output results as JSON
@@ -147013,6 +147695,7 @@ Safety:
147013
147695
  The ship command runs configured hooks and gates automatically. Frozen plan approval is the only
147014
147696
  normal release-level approval; requiresApproval postPublish hooks keep independent checkpoint approvals.
147015
147697
  Kimi/CodeBuddy installations are non-blocking manual follow-up tasks (not verified by system).
147698
+ Qoder is an optional post-release local update; it never becomes a release distribution checkpoint.
147016
147699
  prepare copies current public files into a local snapshot; it does not rewrite source files.
147017
147700
  - Default mode is offline (release-skill pipeline does no remote writes)
147018
147701
  - prepare output goes to .release-skill/ directory only
@@ -147021,6 +147704,7 @@ Safety:
147021
147704
  - docs refresh --write rewrites only declared README managed regions and the current CHANGELOG entry after exact refreshDigest confirmation; it never commits, pushes, tags, publishes, or installs.
147022
147705
  - publish requires explicit approval; plan digest is auto-read from the plan file
147023
147706
  - post-release local host updates are optional local mutations and never change VERIFIED
147707
+ - verify-records reads only the paths named on its command line; it never discovers records, contacts remotes, or changes release state
147024
147708
  - publish consumes frozen Git/npm artifacts, never the live workspace
147025
147709
  - existing remote objects and uncertain checks stop for human intervention
147026
147710
  - production-equivalent protocol sandbox is verified; a real remote canary is not
@@ -147092,7 +147776,8 @@ if (!command || command === "help") {
147092
147776
  claude: "\u58F0\u660E claude-plugin distribution \u65F6\u5FC5\u987B\u53EF\u7528",
147093
147777
  codex: "\u58F0\u660E codex-plugin distribution \u65F6\u5FC5\u987B\u53EF\u7528",
147094
147778
  kimi: "\u53D1\u5E03\u540E\u4EBA\u5DE5\u5B89\u88C5\u5F85\u529E\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6\uFF0C\u7CFB\u7EDF\u4E0D\u6838\u9A8C",
147095
- codebuddy: "\u53D1\u5E03\u540E\u4EBA\u5DE5\u5B89\u88C5\u5F85\u529E\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6\uFF0C\u7CFB\u7EDF\u4E0D\u6838\u9A8C"
147779
+ codebuddy: "\u53D1\u5E03\u540E\u4EBA\u5DE5\u5B89\u88C5\u5F85\u529E\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6\uFF0C\u7CFB\u7EDF\u4E0D\u6838\u9A8C",
147780
+ qoder: "\u53D1\u5E03\u540E\u53EF\u9009\u672C\u673A\u66F4\u65B0\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6\uFF0C\u4E0D\u662F release distribution checkpoint"
147096
147781
  }
147097
147782
  }
147098
147783
  },
@@ -147106,7 +147791,7 @@ if (!command || command === "help") {
147106
147791
  onlinePrepare: "previous-public-baseline observation available; production mode freezes publish artifacts and fails closed on drift or unknown state",
147107
147792
  publish: "GitHub/npm plus automated Claude/Codex consumer checkpoints are protocol-tested without an OS/network sandbox; one approval is required and the internal plan digest is checked automatically",
147108
147793
  reconcile: "PARTIAL recovery is protocol-tested without an OS/network sandbox; remote conflicts require human intervention",
147109
- verify: "fresh exact npm and Claude/Codex consumer installation checks are protocol-tested without an OS/network sandbox; Kimi/CodeBuddy are unverified manual follow-ups; command invocation authorizes configured gates"
147794
+ verify: "fresh exact npm and Claude/Codex consumer installation checks are protocol-tested without an OS/network sandbox; Kimi/CodeBuddy are unverified manual follow-ups and Qoder is optional post-release local work; command invocation authorizes configured gates"
147110
147795
  },
147111
147796
  recommendations: []
147112
147797
  };
@@ -147238,6 +147923,92 @@ For parallel or cross-session versions, use .release-skill/ships/<version>.json.
147238
147923
  }
147239
147924
  await exitAfterFlush(0);
147240
147925
  }
147926
+ if (command === "verify-records" && (args.includes("--help") || args.includes("-h"))) {
147927
+ console.log(`release-skill verify-records - Verify explicit historical release records offline
147928
+
147929
+ Usage:
147930
+ release-skill verify-records --plan <path> --approval <path> --target-run <path> --source-run <path>... --unit <id> --target-version <version> --json
147931
+
147932
+ Options:
147933
+ --plan <path> Explicit release-plan JSON file
147934
+ --approval <path> Explicit approval-record JSON file
147935
+ --target-run <path> Explicit target release-run JSON file
147936
+ --source-run <path> Explicit predecessor release-run JSON file (repeatable)
147937
+ --unit <id> Expected release unit id
147938
+ --target-version <version> Expected release unit version
147939
+ --json Required; output one JSON result
147940
+ -h, --help Show this help message and exit
147941
+
147942
+ The command reads only these explicit paths. It never scans .release-skill,
147943
+ follows paths carried inside records, contacts a remote, writes a file, or
147944
+ changes release state.`);
147945
+ await exitAfterFlush(0);
147946
+ }
147947
+ if (command === "verify-records") {
147948
+ const value = /* @__PURE__ */ __name((flag) => {
147949
+ const index = args.indexOf(flag);
147950
+ return index !== -1 && args[index + 1] && !args[index + 1].startsWith("--") ? args[index + 1] : void 0;
147951
+ }, "value");
147952
+ const values = /* @__PURE__ */ __name((flag) => {
147953
+ const selected = [];
147954
+ for (let index = 0; index < args.length; index += 1) {
147955
+ if (args[index] === flag && args[index + 1] && !args[index + 1].startsWith("--")) {
147956
+ selected.push(args[index + 1]);
147957
+ }
147958
+ }
147959
+ return selected;
147960
+ }, "values");
147961
+ const planPath = value("--plan");
147962
+ const approvalPath = value("--approval");
147963
+ const targetRunPath = value("--target-run");
147964
+ const sourceRunPaths = values("--source-run");
147965
+ const unitId = value("--unit");
147966
+ const targetVersion = value("--target-version");
147967
+ if (!hasJson) {
147968
+ console.log(JSON.stringify({
147969
+ status: "INSUFFICIENT",
147970
+ unitId: unitId ?? null,
147971
+ targetVersion: targetVersion ?? null,
147972
+ historicalTerminalStatus: null,
147973
+ inputs: {},
147974
+ digests: {},
147975
+ findings: [{
147976
+ code: "INPUT_MISSING",
147977
+ role: "cli",
147978
+ message: "--json is required for verify-records"
147979
+ }]
147980
+ }));
147981
+ await exitAfterFlush(2);
147982
+ }
147983
+ const readInput = /* @__PURE__ */ __name(async (path40) => {
147984
+ if (!path40) return void 0;
147985
+ try {
147986
+ return { bytes: await readFile57(path40), source: path40 };
147987
+ } catch {
147988
+ return { source: path40 };
147989
+ }
147990
+ }, "readInput");
147991
+ const [plan, approval, targetRun, ...sourceRuns] = await Promise.all([
147992
+ readInput(planPath),
147993
+ readInput(approvalPath),
147994
+ readInput(targetRunPath),
147995
+ ...sourceRunPaths.map((path40) => readInput(path40))
147996
+ ]);
147997
+ const {
147998
+ VERIFY_RECORDS_EXIT_CODES: VERIFY_RECORDS_EXIT_CODES2,
147999
+ verifyReleaseRecords: verifyReleaseRecords2
148000
+ } = await init_verify_records().then(() => verify_records_exports);
148001
+ const result2 = verifyReleaseRecords2({
148002
+ plan,
148003
+ approval,
148004
+ targetRun,
148005
+ sourceRuns,
148006
+ unitId,
148007
+ targetVersion
148008
+ });
148009
+ console.log(JSON.stringify(result2));
148010
+ await exitAfterFlush(VERIFY_RECORDS_EXIT_CODES2[result2.status]);
148011
+ }
147241
148012
  if (command === "setup") {
147242
148013
  const rootIdx = args.indexOf("--root");
147243
148014
  const rawRoot = rootIdx !== -1 && args[rootIdx + 1] ? args[rootIdx + 1] : process.cwd();
@@ -148166,6 +148937,23 @@ This command creates an independent postVerify run and never reads or writes shi
148166
148937
  root,
148167
148938
  ...postpublishApprovalPaths.length > 0 ? { postpublishApprovalPaths } : {}
148168
148939
  });
148940
+ if (result2.status === "DISTRIBUTED") {
148941
+ const {
148942
+ derivePostReleaseChecklist: derivePostReleaseChecklist2,
148943
+ unavailablePostReleaseChecklist: unavailablePostReleaseChecklist2
148944
+ } = await init_post_release_local().then(() => post_release_local_exports);
148945
+ let plan;
148946
+ try {
148947
+ plan = JSON.parse(await readFile57(resolve42(planPath), "utf8"));
148948
+ result2.postRelease = derivePostReleaseChecklist2(plan, {
148949
+ root,
148950
+ runPath: result2.runPath,
148951
+ postVerifyComplete: true
148952
+ });
148953
+ } catch (error) {
148954
+ result2.postRelease = unavailablePostReleaseChecklist2(plan, error);
148955
+ }
148956
+ }
148169
148957
  if (hasJson) {
148170
148958
  console.log(JSON.stringify(result2, null, 2));
148171
148959
  } else {
@@ -148174,6 +148962,17 @@ This command creates an independent postVerify run and never reads or writes shi
148174
148962
  console.log(` ${cp4.actionId}: ${cp4.status}`);
148175
148963
  }
148176
148964
  if (result2.runPath) console.log(`PostVerify run: ${result2.runPath}`);
148965
+ if (result2.postRelease?.status === "UNAVAILABLE") {
148966
+ console.warn(`Post-release checklist unavailable: ${result2.postRelease.diagnostic?.message ?? "unknown error"}`);
148967
+ } else if (result2.postRelease?.localHostUpdate?.promptRequired === true) {
148968
+ const localHostUpdate = result2.postRelease.localHostUpdate;
148969
+ console.log(`Post-release: ask whether to update local host plugins (${localHostUpdate.hosts.join(", ")}).`);
148970
+ if (localHostUpdate.available === true && typeof localHostUpdate.runPath === "string") {
148971
+ console.log(`Post-release command: release-skill post-release --plan ${resolve42(planPath)} --run ${localHostUpdate.runPath}`);
148972
+ console.log("Choose --hosts before adding --update-local-hosts to perform a local update.");
148973
+ }
148974
+ printHubManualTargets(localHostUpdate.targets);
148975
+ }
148177
148976
  }
148178
148977
  await exitAfterFlush(result2.status === "DISTRIBUTED" ? 0 : 1);
148179
148978
  } catch (err) {