@deftai/directive-runtime 0.129.3 → 0.129.5

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 (192) hide show
  1. package/Taskfile.yml +1 -1
  2. package/dist/hook-dispatch.js +8 -3
  3. package/dist/pre-pr-run.d.ts +7 -1
  4. package/dist/pre-pr-run.js +25 -1
  5. package/dist/verify-ac.d.ts +7 -2
  6. package/dist/verify-ac.js +13 -5
  7. package/docs/scope-provenance.md +4 -0
  8. package/package.json +1 -1
  9. package/packs/skills/skills-pack-0.1.json +3 -3
  10. package/skills/deft-directive-pre-pr/SKILL.md +27 -33
  11. package/skills/deft-directive-release/SKILL.md +23 -0
  12. package/tasks/materialize.cjs +106 -5
  13. package/tasks/materialize.test.cjs +108 -0
  14. package/tasks/materialize.yml +14 -2
  15. package/templates/agents-entry.md +1 -1
  16. package/vendor/directive-content/Taskfile.yml +1 -1
  17. package/vendor/directive-content/docs/scope-provenance.md +4 -0
  18. package/vendor/directive-content/packs/skills/skills-pack-0.1.json +3 -3
  19. package/vendor/directive-content/skills/deft-directive-pre-pr/SKILL.md +27 -33
  20. package/vendor/directive-content/skills/deft-directive-release/SKILL.md +23 -0
  21. package/vendor/directive-content/tasks/materialize.cjs +106 -5
  22. package/vendor/directive-content/tasks/materialize.test.cjs +108 -0
  23. package/vendor/directive-content/tasks/materialize.yml +14 -2
  24. package/vendor/directive-content/templates/agents-entry.md +1 -1
  25. package/vendor/directive-core/dist/.tsbuildinfo +1 -1
  26. package/vendor/directive-core/dist/authz/classify.d.ts +6 -0
  27. package/vendor/directive-core/dist/authz/classify.d.ts.map +1 -1
  28. package/vendor/directive-core/dist/authz/classify.js +156 -2
  29. package/vendor/directive-core/dist/authz/classify.js.map +1 -1
  30. package/vendor/directive-core/dist/authz/evaluate.d.ts +5 -0
  31. package/vendor/directive-core/dist/authz/evaluate.d.ts.map +1 -1
  32. package/vendor/directive-core/dist/authz/evaluate.js +15 -5
  33. package/vendor/directive-core/dist/authz/evaluate.js.map +1 -1
  34. package/vendor/directive-core/dist/check/cached-orchestrator.d.ts.map +1 -1
  35. package/vendor/directive-core/dist/check/cached-orchestrator.js +101 -2
  36. package/vendor/directive-core/dist/check/cached-orchestrator.js.map +1 -1
  37. package/vendor/directive-core/dist/check/context.d.ts +199 -0
  38. package/vendor/directive-core/dist/check/context.d.ts.map +1 -1
  39. package/vendor/directive-core/dist/check/context.js +417 -2
  40. package/vendor/directive-core/dist/check/context.js.map +1 -1
  41. package/vendor/directive-core/dist/check/gate-lists.d.ts +42 -0
  42. package/vendor/directive-core/dist/check/gate-lists.d.ts.map +1 -1
  43. package/vendor/directive-core/dist/check/gate-lists.js +226 -0
  44. package/vendor/directive-core/dist/check/gate-lists.js.map +1 -1
  45. package/vendor/directive-core/dist/check/index.d.ts +1 -0
  46. package/vendor/directive-core/dist/check/index.d.ts.map +1 -1
  47. package/vendor/directive-core/dist/check/index.js +1 -0
  48. package/vendor/directive-core/dist/check/index.js.map +1 -1
  49. package/vendor/directive-core/dist/check/named-cause.d.ts.map +1 -1
  50. package/vendor/directive-core/dist/check/named-cause.js +1 -0
  51. package/vendor/directive-core/dist/check/named-cause.js.map +1 -1
  52. package/vendor/directive-core/dist/check/orchestrator.d.ts +1 -0
  53. package/vendor/directive-core/dist/check/orchestrator.d.ts.map +1 -1
  54. package/vendor/directive-core/dist/check/orchestrator.js +69 -4
  55. package/vendor/directive-core/dist/check/orchestrator.js.map +1 -1
  56. package/vendor/directive-core/dist/eval/health.d.ts.map +1 -1
  57. package/vendor/directive-core/dist/eval/health.js +75 -6
  58. package/vendor/directive-core/dist/eval/health.js.map +1 -1
  59. package/vendor/directive-core/dist/finish-loop/pr-finish-loop.d.ts +7 -0
  60. package/vendor/directive-core/dist/finish-loop/pr-finish-loop.d.ts.map +1 -1
  61. package/vendor/directive-core/dist/finish-loop/pr-finish-loop.js +42 -0
  62. package/vendor/directive-core/dist/finish-loop/pr-finish-loop.js.map +1 -1
  63. package/vendor/directive-core/dist/forward-coverage/diff-coverage.d.ts +2 -1
  64. package/vendor/directive-core/dist/forward-coverage/diff-coverage.d.ts.map +1 -1
  65. package/vendor/directive-core/dist/forward-coverage/diff-coverage.js +2 -1
  66. package/vendor/directive-core/dist/forward-coverage/diff-coverage.js.map +1 -1
  67. package/vendor/directive-core/dist/forward-coverage/evaluate.d.ts +4 -2
  68. package/vendor/directive-core/dist/forward-coverage/evaluate.d.ts.map +1 -1
  69. package/vendor/directive-core/dist/forward-coverage/evaluate.js +11 -4
  70. package/vendor/directive-core/dist/forward-coverage/evaluate.js.map +1 -1
  71. package/vendor/directive-core/dist/hooks/classify/host-session-identity.d.ts +15 -2
  72. package/vendor/directive-core/dist/hooks/classify/host-session-identity.d.ts.map +1 -1
  73. package/vendor/directive-core/dist/hooks/classify/host-session-identity.js +46 -5
  74. package/vendor/directive-core/dist/hooks/classify/host-session-identity.js.map +1 -1
  75. package/vendor/directive-core/dist/hooks/dispatcher.d.ts.map +1 -1
  76. package/vendor/directive-core/dist/hooks/dispatcher.js +61 -10
  77. package/vendor/directive-core/dist/hooks/dispatcher.js.map +1 -1
  78. package/vendor/directive-core/dist/hooks/scope.d.ts +38 -9
  79. package/vendor/directive-core/dist/hooks/scope.d.ts.map +1 -1
  80. package/vendor/directive-core/dist/hooks/scope.js +102 -16
  81. package/vendor/directive-core/dist/hooks/scope.js.map +1 -1
  82. package/vendor/directive-core/dist/install-health/cheap.d.ts +11 -0
  83. package/vendor/directive-core/dist/install-health/cheap.d.ts.map +1 -1
  84. package/vendor/directive-core/dist/install-health/cheap.js +36 -2
  85. package/vendor/directive-core/dist/install-health/cheap.js.map +1 -1
  86. package/vendor/directive-core/dist/install-health/index.d.ts +1 -1
  87. package/vendor/directive-core/dist/install-health/index.d.ts.map +1 -1
  88. package/vendor/directive-core/dist/install-health/index.js +1 -1
  89. package/vendor/directive-core/dist/install-health/index.js.map +1 -1
  90. package/vendor/directive-core/dist/policy/write-fence.d.ts +7 -0
  91. package/vendor/directive-core/dist/policy/write-fence.d.ts.map +1 -1
  92. package/vendor/directive-core/dist/policy/write-fence.js +81 -14
  93. package/vendor/directive-core/dist/policy/write-fence.js.map +1 -1
  94. package/vendor/directive-core/dist/pr-closing-keywords/main.d.ts +19 -0
  95. package/vendor/directive-core/dist/pr-closing-keywords/main.d.ts.map +1 -1
  96. package/vendor/directive-core/dist/pr-closing-keywords/main.js +31 -16
  97. package/vendor/directive-core/dist/pr-closing-keywords/main.js.map +1 -1
  98. package/vendor/directive-core/dist/pr-merge-readiness/compute.d.ts +8 -0
  99. package/vendor/directive-core/dist/pr-merge-readiness/compute.d.ts.map +1 -1
  100. package/vendor/directive-core/dist/pr-merge-readiness/compute.js +54 -2
  101. package/vendor/directive-core/dist/pr-merge-readiness/compute.js.map +1 -1
  102. package/vendor/directive-core/dist/pr-merge-readiness/gh.d.ts +7 -0
  103. package/vendor/directive-core/dist/pr-merge-readiness/gh.d.ts.map +1 -1
  104. package/vendor/directive-core/dist/pr-merge-readiness/gh.js +180 -49
  105. package/vendor/directive-core/dist/pr-merge-readiness/gh.js.map +1 -1
  106. package/vendor/directive-core/dist/pr-merge-readiness/main.d.ts +2 -0
  107. package/vendor/directive-core/dist/pr-merge-readiness/main.d.ts.map +1 -1
  108. package/vendor/directive-core/dist/pr-merge-readiness/main.js +1 -0
  109. package/vendor/directive-core/dist/pr-merge-readiness/main.js.map +1 -1
  110. package/vendor/directive-core/dist/pr-monitor/monitor.d.ts.map +1 -1
  111. package/vendor/directive-core/dist/pr-monitor/monitor.js +1 -0
  112. package/vendor/directive-core/dist/pr-monitor/monitor.js.map +1 -1
  113. package/vendor/directive-core/dist/pr-monitor/readiness.d.ts.map +1 -1
  114. package/vendor/directive-core/dist/pr-monitor/readiness.js +1 -0
  115. package/vendor/directive-core/dist/pr-monitor/readiness.js.map +1 -1
  116. package/vendor/directive-core/dist/pr-monitor/types.d.ts +2 -0
  117. package/vendor/directive-core/dist/pr-monitor/types.d.ts.map +1 -1
  118. package/vendor/directive-core/dist/pr-wait-mergeable/cascade.d.ts +6 -0
  119. package/vendor/directive-core/dist/pr-wait-mergeable/cascade.d.ts.map +1 -1
  120. package/vendor/directive-core/dist/pr-wait-mergeable/cascade.js +27 -0
  121. package/vendor/directive-core/dist/pr-wait-mergeable/cascade.js.map +1 -1
  122. package/vendor/directive-core/dist/pr-wait-mergeable/constants.d.ts +15 -0
  123. package/vendor/directive-core/dist/pr-wait-mergeable/constants.d.ts.map +1 -1
  124. package/vendor/directive-core/dist/pr-wait-mergeable/constants.js +18 -0
  125. package/vendor/directive-core/dist/pr-wait-mergeable/constants.js.map +1 -1
  126. package/vendor/directive-core/dist/pr-wait-mergeable/index.d.ts +2 -1
  127. package/vendor/directive-core/dist/pr-wait-mergeable/index.d.ts.map +1 -1
  128. package/vendor/directive-core/dist/pr-wait-mergeable/index.js +2 -1
  129. package/vendor/directive-core/dist/pr-wait-mergeable/index.js.map +1 -1
  130. package/vendor/directive-core/dist/pr-wait-mergeable/semantic-green.d.ts +81 -0
  131. package/vendor/directive-core/dist/pr-wait-mergeable/semantic-green.d.ts.map +1 -1
  132. package/vendor/directive-core/dist/pr-wait-mergeable/semantic-green.js +687 -3
  133. package/vendor/directive-core/dist/pr-wait-mergeable/semantic-green.js.map +1 -1
  134. package/vendor/directive-core/dist/pre-pr-controller/controller.d.ts +59 -1
  135. package/vendor/directive-core/dist/pre-pr-controller/controller.d.ts.map +1 -1
  136. package/vendor/directive-core/dist/pre-pr-controller/controller.js +168 -5
  137. package/vendor/directive-core/dist/pre-pr-controller/controller.js.map +1 -1
  138. package/vendor/directive-core/dist/pre-pr-controller/index.d.ts +1 -1
  139. package/vendor/directive-core/dist/pre-pr-controller/index.d.ts.map +1 -1
  140. package/vendor/directive-core/dist/pre-pr-controller/index.js +1 -1
  141. package/vendor/directive-core/dist/pre-pr-controller/index.js.map +1 -1
  142. package/vendor/directive-core/dist/pre-pr-controller/phases.d.ts +7 -0
  143. package/vendor/directive-core/dist/pre-pr-controller/phases.d.ts.map +1 -1
  144. package/vendor/directive-core/dist/pre-pr-controller/phases.js +14 -3
  145. package/vendor/directive-core/dist/pre-pr-controller/phases.js.map +1 -1
  146. package/vendor/directive-core/dist/pre-pr-controller/types.d.ts +1 -1
  147. package/vendor/directive-core/dist/pre-pr-controller/types.d.ts.map +1 -1
  148. package/vendor/directive-core/dist/pre-pr-controller/types.js.map +1 -1
  149. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts.map +1 -1
  150. package/vendor/directive-core/dist/preflight-gh/pre-push.js +58 -2
  151. package/vendor/directive-core/dist/preflight-gh/pre-push.js.map +1 -1
  152. package/vendor/directive-core/dist/product-first-done-gate/acceptance-resolver.d.ts +1 -1
  153. package/vendor/directive-core/dist/product-first-done-gate/acceptance-resolver.d.ts.map +1 -1
  154. package/vendor/directive-core/dist/product-first-done-gate/acceptance-resolver.js +1 -1
  155. package/vendor/directive-core/dist/product-first-done-gate/acceptance-resolver.js.map +1 -1
  156. package/vendor/directive-core/dist/product-first-done-gate/index.d.ts +1 -1
  157. package/vendor/directive-core/dist/product-first-done-gate/index.d.ts.map +1 -1
  158. package/vendor/directive-core/dist/product-first-done-gate/index.js +1 -1
  159. package/vendor/directive-core/dist/product-first-done-gate/index.js.map +1 -1
  160. package/vendor/directive-core/dist/release/pipeline.d.ts.map +1 -1
  161. package/vendor/directive-core/dist/release/pipeline.js +22 -23
  162. package/vendor/directive-core/dist/release/pipeline.js.map +1 -1
  163. package/vendor/directive-core/dist/release/preflight.d.ts +11 -0
  164. package/vendor/directive-core/dist/release/preflight.d.ts.map +1 -1
  165. package/vendor/directive-core/dist/release/preflight.js +68 -11
  166. package/vendor/directive-core/dist/release/preflight.js.map +1 -1
  167. package/vendor/directive-core/dist/release/suite-stamp.d.ts +5 -2
  168. package/vendor/directive-core/dist/release/suite-stamp.d.ts.map +1 -1
  169. package/vendor/directive-core/dist/release/suite-stamp.js +12 -1
  170. package/vendor/directive-core/dist/release/suite-stamp.js.map +1 -1
  171. package/vendor/directive-core/dist/release/types.d.ts +10 -6
  172. package/vendor/directive-core/dist/release/types.d.ts.map +1 -1
  173. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts +37 -0
  174. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts.map +1 -1
  175. package/vendor/directive-core/dist/scope/delivery-evidence.js +106 -6
  176. package/vendor/directive-core/dist/scope/delivery-evidence.js.map +1 -1
  177. package/vendor/directive-core/dist/scope/transition.d.ts +3 -1
  178. package/vendor/directive-core/dist/scope/transition.d.ts.map +1 -1
  179. package/vendor/directive-core/dist/scope/transition.js +4 -0
  180. package/vendor/directive-core/dist/scope/transition.js.map +1 -1
  181. package/vendor/directive-core/dist/ts-check-lane/run-lane.d.ts +6 -4
  182. package/vendor/directive-core/dist/ts-check-lane/run-lane.d.ts.map +1 -1
  183. package/vendor/directive-core/dist/ts-check-lane/run-lane.js +10 -6
  184. package/vendor/directive-core/dist/ts-check-lane/run-lane.js.map +1 -1
  185. package/vendor/directive-core/dist/verify-ac/evaluate.d.ts +52 -0
  186. package/vendor/directive-core/dist/verify-ac/evaluate.d.ts.map +1 -1
  187. package/vendor/directive-core/dist/verify-ac/evaluate.js +361 -1
  188. package/vendor/directive-core/dist/verify-ac/evaluate.js.map +1 -1
  189. package/vendor/directive-core/dist/verify-ac/index.d.ts +1 -1
  190. package/vendor/directive-core/dist/verify-ac/index.d.ts.map +1 -1
  191. package/vendor/directive-core/dist/verify-ac/index.js +1 -1
  192. package/vendor/directive-core/dist/verify-ac/index.js.map +1 -1
@@ -1,7 +1,12 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parseSkippedTestsBlock } from "../check/gate-lists.js";
4
+ import { readPlanPolicy } from "../policy/plan-extensions.js";
1
5
  import { resolveLifecycleDirty, shasMatch, } from "../pr-closeout-attestable/pr-head-assert.js";
2
6
  import { buildCiSummaryLine, evaluateCiGate } from "../pr-merge-readiness/ci-gate.js";
3
7
  import { defaultRunGh, fetchCheckRunsRest } from "../pr-merge-readiness/gh.js";
4
- import { EXIT_CONFIG_ERROR, EXIT_TIMEOUT_OR_ESCALATION } from "./constants.js";
8
+ import { DEFAULT_MAX_WAIT_MINUTES } from "../pr-watch/constants.js";
9
+ import { EXIT_CONFIG_ERROR, EXIT_TIMEOUT_OR_ESCALATION, FIRST_HOTFIX_BOOTSTRAP_RECOVERY, HOTFIX_LABEL, HOTFIX_SHAPE_A_RE, HOTFIX_SHAPE_B_ISSUE_REPO, HOTFIX_SHAPE_B_RE, MERGE_GATE_TASK_CHECK_NAME, OUTCOME_FIRST_HOTFIX_BOOTSTRAP, OUTCOME_HOTFIX_ACTOR_DENIED, OUTCOME_HOTFIX_SHAPE_INVALID, OUTCOME_MASTER_MERGE_GATE_RED, } from "./constants.js";
5
10
  /** Machine-checkable candidate validation attestation (#4888 Prefer-A Bound). */
6
11
  export const CANDIDATE_VALIDATION_SCHEMA = "deft.semantic-green.candidate-validation.v1";
7
12
  /** Named merge-chokepoint probes admitted for candidate attestation (#4888). */
@@ -63,6 +68,7 @@ function emptyPayload() {
63
68
  target_head_sha: null,
64
69
  master_ci: {},
65
70
  candidate_validation: null,
71
+ named_merge_gate: null,
66
72
  };
67
73
  }
68
74
  function fetchPrShas(prNumber, repo, runGh) {
@@ -427,15 +433,50 @@ function evaluateCandidateValidationLimb(prNumber, repo, options, runGh) {
427
433
  }
428
434
  /** Pre-merge semantic-green gate for merge cascades (#2385) and non-cascade candidate validation (#4888). */
429
435
  export function evaluateSemanticGreen(prNumber, repo, options = {}) {
436
+ const runGh = options.runGh ?? defaultRunGh;
437
+ // #6071 always-on named Merge gate — independent of cascadeMode / requireMasterCiGreen.
438
+ if (options.skipNamedMergeGate !== true) {
439
+ const named = evaluateAlwaysOnNamedMergeGate(prNumber, repo, {
440
+ runGh,
441
+ baseBranch: options.baseBranch,
442
+ pendingTimeoutMinutes: options.namedMergeGatePendingTimeoutMinutes,
443
+ nowMs: options.namedMergeGateNowMs,
444
+ tipSha: options.namedMergeGateTipSha,
445
+ checkRuns: options.namedMergeGateCheckRuns,
446
+ hotfixAdmission: options.hotfixAdmission,
447
+ projectRoot: options.projectRoot,
448
+ maintainerLogins: options.maintainerLogins,
449
+ env: options.env,
450
+ firstHotfixBootstrap: options.firstHotfixBootstrap,
451
+ });
452
+ if (!named.ok) {
453
+ return {
454
+ ok: false,
455
+ outcome: named.outcome,
456
+ exitCode: named.exitCode,
457
+ error: named.error,
458
+ payload: {
459
+ ...emptyPayload(),
460
+ target_head_sha: named.status.tipSha,
461
+ named_merge_gate: {
462
+ disposition: named.status.disposition,
463
+ tip_sha: named.status.tipSha,
464
+ check_name: named.status.checkName,
465
+ status: named.status.status,
466
+ conclusion: named.status.conclusion,
467
+ hotfix: named.hotfix,
468
+ },
469
+ },
470
+ };
471
+ }
472
+ }
430
473
  // Non-cascade candidate-validation limb (#4888). Does not replace cascade absolute refusal.
431
474
  if (options.cascadeMode !== true && options.requireCandidateValidation === true) {
432
- const runGh = options.runGh ?? defaultRunGh;
433
475
  return evaluateCandidateValidationLimb(prNumber, repo, options, runGh);
434
476
  }
435
477
  if (options.cascadeMode !== true) {
436
478
  return { ok: true, outcome: null, exitCode: null, error: null, payload: emptyPayload() };
437
479
  }
438
- const runGh = options.runGh ?? defaultRunGh;
439
480
  const prBase = fetchPrShas(prNumber, repo, runGh);
440
481
  if (prBase.error !== null) {
441
482
  return {
@@ -554,4 +595,647 @@ export function evaluateNonCascadeCandidateGreen(prNumber, repo, options = {}) {
554
595
  requireCandidateValidation: true,
555
596
  });
556
597
  }
598
+ const PENDING_NAMED_STATUSES = new Set(["queued", "in_progress"]);
599
+ const HOTFIX_MAINTAINER_ASSOCIATIONS = new Set(["OWNER", "MEMBER"]);
600
+ function emptyNamedStatus(tipSha = null) {
601
+ return {
602
+ disposition: "api_fail",
603
+ tipSha,
604
+ checkName: MERGE_GATE_TASK_CHECK_NAME,
605
+ status: null,
606
+ conclusion: null,
607
+ error: null,
608
+ pendingAgeMs: null,
609
+ };
610
+ }
611
+ function isNoNetwork(env) {
612
+ return (env ?? process.env).DEFT_NO_NETWORK === "1";
613
+ }
614
+ /** Pick the latest check-run for the exact named gate (#6071). */
615
+ export function selectNamedMergeGateRun(checkRuns) {
616
+ let best = null;
617
+ let bestTs = -1;
618
+ for (const run of checkRuns) {
619
+ if (run.name !== MERGE_GATE_TASK_CHECK_NAME) {
620
+ continue;
621
+ }
622
+ const tsRaw = run.started_at ?? run.created_at ?? null;
623
+ const ts = tsRaw !== null ? Date.parse(tsRaw) : 0;
624
+ const score = Number.isFinite(ts) ? ts : 0;
625
+ if (best === null || score >= bestTs) {
626
+ best = run;
627
+ bestTs = score;
628
+ }
629
+ }
630
+ return best;
631
+ }
632
+ export function evaluateNamedMergeGateFromRuns(tipSha, checkRuns, options = {}) {
633
+ const run = selectNamedMergeGateRun(checkRuns);
634
+ if (run === null) {
635
+ return {
636
+ disposition: "missing",
637
+ tipSha,
638
+ checkName: MERGE_GATE_TASK_CHECK_NAME,
639
+ status: null,
640
+ conclusion: null,
641
+ error: `Exact check name ${JSON.stringify(MERGE_GATE_TASK_CHECK_NAME)} absent on tip ${tipSha}; fail closed (#6071).`,
642
+ pendingAgeMs: null,
643
+ };
644
+ }
645
+ if (run.conclusion === "skipped" && run.status === "completed") {
646
+ return {
647
+ disposition: "skipped",
648
+ tipSha,
649
+ checkName: MERGE_GATE_TASK_CHECK_NAME,
650
+ status: run.status,
651
+ conclusion: run.conclusion,
652
+ error: `${MERGE_GATE_TASK_CHECK_NAME} concluded skipped on tip ${tipSha}; fail closed (#6071).`,
653
+ pendingAgeMs: null,
654
+ };
655
+ }
656
+ const pending = PENDING_NAMED_STATUSES.has(run.status) ||
657
+ (run.status !== "completed" && (run.conclusion === "none" || run.conclusion === ""));
658
+ if (pending) {
659
+ const startRaw = run.started_at ?? run.created_at ?? null;
660
+ const startMs = startRaw !== null ? Date.parse(startRaw) : NaN;
661
+ const nowMs = options.nowMs ?? Date.now();
662
+ const ageMs = Number.isFinite(startMs) ? Math.max(0, nowMs - startMs) : null;
663
+ const budgetMs = (options.pendingTimeoutMinutes ?? DEFAULT_MAX_WAIT_MINUTES) * 60_000;
664
+ if (ageMs === null || ageMs >= budgetMs) {
665
+ return {
666
+ disposition: "pending",
667
+ tipSha,
668
+ checkName: MERGE_GATE_TASK_CHECK_NAME,
669
+ status: run.status,
670
+ conclusion: run.conclusion,
671
+ error: `${MERGE_GATE_TASK_CHECK_NAME} still pending past timeout on tip ${tipSha}; fail closed (#6071).`,
672
+ pendingAgeMs: ageMs,
673
+ };
674
+ }
675
+ return {
676
+ disposition: "pending",
677
+ tipSha,
678
+ checkName: MERGE_GATE_TASK_CHECK_NAME,
679
+ status: run.status,
680
+ conclusion: run.conclusion,
681
+ error: `${MERGE_GATE_TASK_CHECK_NAME} still pending on tip ${tipSha}; fail closed until completed success (#6071).`,
682
+ pendingAgeMs: ageMs,
683
+ };
684
+ }
685
+ if (run.status === "completed" && run.conclusion === "success") {
686
+ return {
687
+ disposition: "green",
688
+ tipSha,
689
+ checkName: MERGE_GATE_TASK_CHECK_NAME,
690
+ status: run.status,
691
+ conclusion: run.conclusion,
692
+ error: null,
693
+ pendingAgeMs: null,
694
+ };
695
+ }
696
+ return {
697
+ disposition: "red",
698
+ tipSha,
699
+ checkName: MERGE_GATE_TASK_CHECK_NAME,
700
+ status: run.status,
701
+ conclusion: run.conclusion,
702
+ error: `${MERGE_GATE_TASK_CHECK_NAME} is ${run.status}/${run.conclusion} on tip ${tipSha}; ` +
703
+ "ordinary merges refused until green or an admitted hotfix A/B (#6071).",
704
+ pendingAgeMs: null,
705
+ };
706
+ }
707
+ export function parseHotfixShape(body) {
708
+ // Fresh global regexes — .test/.exec mutate lastIndex on shared constants (#6071).
709
+ const aMatches = body.match(new RegExp(HOTFIX_SHAPE_A_RE.source, "gm")) ?? [];
710
+ const bMatches = [...body.matchAll(new RegExp(HOTFIX_SHAPE_B_RE.source, "gm"))];
711
+ const total = aMatches.length + bMatches.length;
712
+ if (total === 0) {
713
+ return { shape: null, skipIssue: null, ambiguous: false };
714
+ }
715
+ if (total > 1) {
716
+ return { shape: null, skipIssue: null, ambiguous: true };
717
+ }
718
+ if (aMatches.length === 1) {
719
+ return { shape: "A", skipIssue: null, ambiguous: false };
720
+ }
721
+ const issue = bMatches[0]?.[1];
722
+ if (issue !== undefined) {
723
+ return { shape: "B", skipIssue: Number(issue), ambiguous: false };
724
+ }
725
+ return { shape: null, skipIssue: null, ambiguous: false };
726
+ }
727
+ function isBotActor(actor) {
728
+ const type = (actor.type ?? "").toLowerCase();
729
+ if (type === "bot") {
730
+ return true;
731
+ }
732
+ const login = actor.login ?? "";
733
+ return /\[bot\]$/i.test(login) || login.toLowerCase().endsWith("-bot");
734
+ }
735
+ export function loadMaintainerLogins(projectRoot) {
736
+ try {
737
+ const path = join(projectRoot, "xbrief", "PROJECT-DEFINITION.xbrief.json");
738
+ const raw = JSON.parse(readFileSync(path, "utf8"));
739
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
740
+ return [];
741
+ }
742
+ const plan = raw.plan;
743
+ const policy = readPlanPolicy(plan);
744
+ if (policy === null || typeof policy !== "object" || Array.isArray(policy)) {
745
+ return [];
746
+ }
747
+ const list = policy.maintainerLogins;
748
+ if (!Array.isArray(list)) {
749
+ return [];
750
+ }
751
+ return list
752
+ .filter((v) => typeof v === "string" && v.trim().length > 0)
753
+ .map((v) => v.trim());
754
+ }
755
+ catch {
756
+ return [];
757
+ }
758
+ }
759
+ function isMaintainerClassActor(association, login, maintainerLogins) {
760
+ const assoc = (association ?? "").toUpperCase();
761
+ if (HOTFIX_MAINTAINER_ASSOCIATIONS.has(assoc)) {
762
+ return true;
763
+ }
764
+ if (login === null || login === undefined || login.length === 0) {
765
+ return false;
766
+ }
767
+ const needle = login.toLowerCase();
768
+ return maintainerLogins.some((m) => m.toLowerCase() === needle);
769
+ }
770
+ function fetchCollaboratorAssociation(repo, login, runGh) {
771
+ const rc = runGh(["gh", "api", `repos/${repo}/collaborators/${login}/permission`]);
772
+ if (rc.returncode !== 0 || !rc.stdout.trim())
773
+ return null;
774
+ try {
775
+ const payload = JSON.parse(rc.stdout);
776
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload))
777
+ return null;
778
+ const permission = payload.permission;
779
+ if (typeof permission !== "string")
780
+ return null;
781
+ const p = permission.toLowerCase();
782
+ if (p === "admin" || p === "maintain")
783
+ return "MEMBER";
784
+ if (p === "write")
785
+ return "COLLABORATOR";
786
+ return null;
787
+ }
788
+ catch {
789
+ return null;
790
+ }
791
+ }
792
+ function fetchLatestHotfixLabelActor(prNumber, repo, runGh) {
793
+ // Paginate issue events — busy PRs can push the current hotfix label past page 1.
794
+ // Inline page cap — avoid new numeric-const FACT_KINDS (#4541 / AFK free rewrite).
795
+ const allEvents = [];
796
+ let page = 1;
797
+ while (page <= 20) {
798
+ const rc = runGh([
799
+ "gh",
800
+ "api",
801
+ `repos/${repo}/issues/${prNumber}/events?per_page=100&page=${page}`,
802
+ ]);
803
+ if (rc.returncode !== 0) {
804
+ return { actor: null, error: `could not read labeled-timeline events: ${rc.stderr.trim()}` };
805
+ }
806
+ if (!rc.stdout.trim()) {
807
+ if (page === 1)
808
+ return { actor: null, error: "empty body from issues/<N>/events" };
809
+ break;
810
+ }
811
+ let payload;
812
+ try {
813
+ payload = JSON.parse(rc.stdout);
814
+ }
815
+ catch (exc) {
816
+ const message = exc instanceof Error ? exc.message : String(exc);
817
+ return { actor: null, error: `could not parse events JSON: ${message}` };
818
+ }
819
+ if (!Array.isArray(payload)) {
820
+ return { actor: null, error: "unexpected events JSON shape (not a list)" };
821
+ }
822
+ allEvents.push(...payload);
823
+ if (payload.length < 100)
824
+ break;
825
+ page += 1;
826
+ }
827
+ let latest = null;
828
+ for (const event of allEvents) {
829
+ if (event === null || typeof event !== "object" || Array.isArray(event))
830
+ continue;
831
+ const rec = event;
832
+ if (rec.event !== "labeled")
833
+ continue;
834
+ const label = rec.label;
835
+ if (label === null || typeof label !== "object" || Array.isArray(label))
836
+ continue;
837
+ if (label.name !== HOTFIX_LABEL)
838
+ continue;
839
+ const actor = rec.actor;
840
+ if (actor === null || typeof actor !== "object" || Array.isArray(actor))
841
+ continue;
842
+ const a = actor;
843
+ const login = typeof a.login === "string" ? a.login : "";
844
+ if (login.length === 0)
845
+ continue;
846
+ // issues/<N>/events omits author_association; resolve via collaborators permission.
847
+ const association = typeof rec.author_association === "string"
848
+ ? rec.author_association
849
+ : fetchCollaboratorAssociation(repo, login, runGh);
850
+ latest = {
851
+ login,
852
+ type: typeof a.type === "string" ? a.type : null,
853
+ association,
854
+ };
855
+ }
856
+ return { actor: latest, error: null };
857
+ }
858
+ function fetchPrHotfixSurface(prNumber, repo, runGh) {
859
+ const rc = runGh(["gh", "api", `repos/${repo}/pulls/${prNumber}`]);
860
+ if (rc.returncode !== 0) {
861
+ return { labels: [], body: "", error: `gh api /pulls/${prNumber} failed: ${rc.stderr.trim()}` };
862
+ }
863
+ if (!rc.stdout.trim()) {
864
+ return { labels: [], body: "", error: "empty body from gh api /pulls/<N>" };
865
+ }
866
+ let payload;
867
+ try {
868
+ payload = JSON.parse(rc.stdout);
869
+ }
870
+ catch (exc) {
871
+ const message = exc instanceof Error ? exc.message : String(exc);
872
+ return { labels: [], body: "", error: `could not parse PR JSON: ${message}` };
873
+ }
874
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
875
+ return { labels: [], body: "", error: "unexpected PR JSON shape (not a dict)" };
876
+ }
877
+ const rec = payload;
878
+ const body = typeof rec.body === "string" ? rec.body : "";
879
+ const labels = [];
880
+ if (Array.isArray(rec.labels)) {
881
+ for (const lab of rec.labels) {
882
+ if (lab !== null && typeof lab === "object" && !Array.isArray(lab)) {
883
+ const name = lab.name;
884
+ if (typeof name === "string" && name.length > 0)
885
+ labels.push(name);
886
+ }
887
+ else if (typeof lab === "string" && lab.length > 0) {
888
+ labels.push(lab);
889
+ }
890
+ }
891
+ }
892
+ return { labels, body, error: null };
893
+ }
894
+ /**
895
+ * Admit hotfix escape when label + maintainer-class labeler + exactly one shape.
896
+ * Does not require actor≠author (Recut-2).
897
+ */
898
+ export function evaluateHotfixAdmission(prNumber, repo, options = {}) {
899
+ if (options.hotfixAdmission !== undefined && options.hotfixAdmission !== null) {
900
+ return options.hotfixAdmission;
901
+ }
902
+ if (options.firstHotfixBootstrap === true) {
903
+ return {
904
+ admitted: false,
905
+ shape: null,
906
+ skipIssue: null,
907
+ actorLogin: null,
908
+ reason: FIRST_HOTFIX_BOOTSTRAP_RECOVERY,
909
+ outcome: OUTCOME_FIRST_HOTFIX_BOOTSTRAP,
910
+ };
911
+ }
912
+ if (isNoNetwork(options.env)) {
913
+ return {
914
+ admitted: false,
915
+ shape: null,
916
+ skipIssue: null,
917
+ actorLogin: null,
918
+ reason: "Hotfix provenance unreadable under DEFT_NO_NETWORK=1; refuse (#6071).",
919
+ outcome: OUTCOME_HOTFIX_ACTOR_DENIED,
920
+ };
921
+ }
922
+ const runGh = options.runGh ?? defaultRunGh;
923
+ const surface = fetchPrHotfixSurface(prNumber, repo, runGh);
924
+ if (surface.error !== null) {
925
+ return {
926
+ admitted: false,
927
+ shape: null,
928
+ skipIssue: null,
929
+ actorLogin: null,
930
+ reason: `Hotfix provenance unreadable: ${surface.error}`,
931
+ outcome: OUTCOME_HOTFIX_ACTOR_DENIED,
932
+ };
933
+ }
934
+ if (!surface.labels.includes(HOTFIX_LABEL)) {
935
+ return {
936
+ admitted: false,
937
+ shape: null,
938
+ skipIssue: null,
939
+ actorLogin: null,
940
+ reason: null,
941
+ outcome: null,
942
+ };
943
+ }
944
+ const parsed = parseHotfixShape(surface.body);
945
+ if (parsed.ambiguous) {
946
+ return {
947
+ admitted: false,
948
+ shape: null,
949
+ skipIssue: null,
950
+ actorLogin: null,
951
+ reason: "Hotfix body matches both shape (A) and (B); refuse (#6071).",
952
+ outcome: OUTCOME_HOTFIX_SHAPE_INVALID,
953
+ };
954
+ }
955
+ if (parsed.shape === null) {
956
+ return {
957
+ admitted: false,
958
+ shape: null,
959
+ skipIssue: null,
960
+ actorLogin: null,
961
+ reason: "hotfix label present without a matching shape (A) or (B) body line; refuse (#6071).",
962
+ outcome: OUTCOME_HOTFIX_SHAPE_INVALID,
963
+ };
964
+ }
965
+ const labeled = fetchLatestHotfixLabelActor(prNumber, repo, runGh);
966
+ if (labeled.error !== null) {
967
+ return {
968
+ admitted: false,
969
+ shape: parsed.shape,
970
+ skipIssue: parsed.skipIssue,
971
+ actorLogin: null,
972
+ reason: `Hotfix labeled-timeline unreadable: ${labeled.error}`,
973
+ outcome: OUTCOME_HOTFIX_ACTOR_DENIED,
974
+ };
975
+ }
976
+ if (labeled.actor === null) {
977
+ return {
978
+ admitted: false,
979
+ shape: parsed.shape,
980
+ skipIssue: parsed.skipIssue,
981
+ actorLogin: null,
982
+ reason: "hotfix label present but labeled-timeline actor provenance missing; refuse (#6071).",
983
+ outcome: OUTCOME_HOTFIX_ACTOR_DENIED,
984
+ };
985
+ }
986
+ if (isBotActor(labeled.actor)) {
987
+ return {
988
+ admitted: false,
989
+ shape: parsed.shape,
990
+ skipIssue: parsed.skipIssue,
991
+ actorLogin: labeled.actor.login,
992
+ reason: `hotfix label actor ${labeled.actor.login} is a Bot; refuse (#6071).`,
993
+ outcome: OUTCOME_HOTFIX_ACTOR_DENIED,
994
+ };
995
+ }
996
+ const maintainerLogins = options.maintainerLogins ?? loadMaintainerLogins(options.projectRoot ?? process.cwd());
997
+ if (!isMaintainerClassActor(labeled.actor.association, labeled.actor.login, maintainerLogins)) {
998
+ return {
999
+ admitted: false,
1000
+ shape: parsed.shape,
1001
+ skipIssue: parsed.skipIssue,
1002
+ actorLogin: labeled.actor.login,
1003
+ reason: `hotfix label actor ${labeled.actor.login} is not maintainer-class ` +
1004
+ "(OWNER/MEMBER or plan.policy.maintainerLogins); refuse (#6071).",
1005
+ outcome: OUTCOME_HOTFIX_ACTOR_DENIED,
1006
+ };
1007
+ }
1008
+ // #6071 Greptile: shape B must live-validate the cited issue (open + skipped-tests).
1009
+ if (parsed.shape === "B") {
1010
+ const skipIssue = parsed.skipIssue;
1011
+ if (skipIssue === null || !Number.isFinite(skipIssue) || skipIssue <= 0) {
1012
+ return {
1013
+ admitted: false,
1014
+ shape: "B",
1015
+ skipIssue: null,
1016
+ actorLogin: labeled.actor.login,
1017
+ reason: "hotfix shape (B) missing deftai/directive#N citation; refuse (#6071).",
1018
+ outcome: OUTCOME_HOTFIX_SHAPE_INVALID,
1019
+ };
1020
+ }
1021
+ // Citations are always deftai/directive#N (HOTFIX_SHAPE_B_RE); do not look up N in the PR repo.
1022
+ const issueRc = runGh([
1023
+ "gh",
1024
+ "api",
1025
+ `repos/${HOTFIX_SHAPE_B_ISSUE_REPO}/issues/${skipIssue}`,
1026
+ "--jq",
1027
+ "{state:.state,body:.body,created_at:.created_at}",
1028
+ ]);
1029
+ if (issueRc.returncode !== 0 || !issueRc.stdout.trim()) {
1030
+ return {
1031
+ admitted: false,
1032
+ shape: "B",
1033
+ skipIssue,
1034
+ actorLogin: labeled.actor.login,
1035
+ reason: `hotfix shape (B) ${HOTFIX_SHAPE_B_ISSUE_REPO}#${skipIssue} unreadable; refuse (#6071).`,
1036
+ outcome: OUTCOME_HOTFIX_SHAPE_INVALID,
1037
+ };
1038
+ }
1039
+ let issue;
1040
+ try {
1041
+ issue = JSON.parse(issueRc.stdout);
1042
+ }
1043
+ catch {
1044
+ return {
1045
+ admitted: false,
1046
+ shape: "B",
1047
+ skipIssue,
1048
+ actorLogin: labeled.actor.login,
1049
+ reason: `hotfix shape (B) issue #${skipIssue} payload unreadable; refuse (#6071).`,
1050
+ outcome: OUTCOME_HOTFIX_SHAPE_INVALID,
1051
+ };
1052
+ }
1053
+ if (issue.state !== "open") {
1054
+ return {
1055
+ admitted: false,
1056
+ shape: "B",
1057
+ skipIssue,
1058
+ actorLogin: labeled.actor.login,
1059
+ reason: `hotfix shape (B) ${HOTFIX_SHAPE_B_ISSUE_REPO}#${skipIssue} is ${String(issue.state)}; refuse (#6071).`,
1060
+ outcome: OUTCOME_HOTFIX_SHAPE_INVALID,
1061
+ };
1062
+ }
1063
+ const titles = parseSkippedTestsBlock(typeof issue.body === "string" ? issue.body : "");
1064
+ if (titles.length === 0) {
1065
+ return {
1066
+ admitted: false,
1067
+ shape: "B",
1068
+ skipIssue,
1069
+ actorLogin: labeled.actor.login,
1070
+ reason: `hotfix shape (B) ${HOTFIX_SHAPE_B_ISSUE_REPO}#${skipIssue} lacks skipped-tests block; refuse (#6071).`,
1071
+ outcome: OUTCOME_HOTFIX_SHAPE_INVALID,
1072
+ };
1073
+ }
1074
+ }
1075
+ return {
1076
+ admitted: true,
1077
+ shape: parsed.shape,
1078
+ skipIssue: parsed.skipIssue,
1079
+ actorLogin: labeled.actor.login,
1080
+ reason: null,
1081
+ outcome: null,
1082
+ };
1083
+ }
1084
+ /**
1085
+ * Always-on named Merge gate evaluation for a PR (or tip-only release path).
1086
+ * When the named check is not green, only an admitted hotfix A/B may proceed.
1087
+ */
1088
+ export function evaluateAlwaysOnNamedMergeGate(prNumber, repo, options = {}) {
1089
+ if (isNoNetwork(options.env)) {
1090
+ const error = "Named Merge gate (task check) status unreadable under DEFT_NO_NETWORK=1; " +
1091
+ "refuse (same closed outcome as API fail) (#6071).";
1092
+ return {
1093
+ ok: false,
1094
+ outcome: OUTCOME_MASTER_MERGE_GATE_RED,
1095
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1096
+ error,
1097
+ status: { ...emptyNamedStatus(options.tipSha ?? null), disposition: "api_fail", error },
1098
+ hotfix: null,
1099
+ };
1100
+ }
1101
+ const runGh = options.runGh ?? defaultRunGh;
1102
+ let tipSha = options.tipSha ?? null;
1103
+ if (tipSha === null || tipSha.length === 0) {
1104
+ const baseBranch = typeof options.baseBranch === "string" && options.baseBranch.length > 0
1105
+ ? options.baseBranch
1106
+ : null;
1107
+ let branch = baseBranch;
1108
+ if (branch === null && prNumber !== null) {
1109
+ const pr = fetchPrShas(prNumber, repo, runGh);
1110
+ if (pr.error !== null) {
1111
+ return {
1112
+ ok: false,
1113
+ outcome: OUTCOME_MASTER_MERGE_GATE_RED,
1114
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1115
+ error: pr.error,
1116
+ status: { ...emptyNamedStatus(null), error: pr.error },
1117
+ hotfix: null,
1118
+ };
1119
+ }
1120
+ branch = pr.baseRef;
1121
+ }
1122
+ if (branch === null || branch.length === 0) {
1123
+ branch = "master";
1124
+ }
1125
+ const head = fetchBranchHeadSha(repo, branch, runGh);
1126
+ if (head.error !== null || head.sha === null) {
1127
+ const error = head.error ?? "could not resolve tip SHA for named Merge gate";
1128
+ return {
1129
+ ok: false,
1130
+ outcome: OUTCOME_MASTER_MERGE_GATE_RED,
1131
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1132
+ error,
1133
+ status: { ...emptyNamedStatus(null), error },
1134
+ hotfix: null,
1135
+ };
1136
+ }
1137
+ tipSha = head.sha;
1138
+ }
1139
+ let checkRuns = options.checkRuns ?? null;
1140
+ if (checkRuns === null) {
1141
+ const fetched = fetchCheckRunsRest(tipSha, repo, runGh);
1142
+ if (fetched.error.length > 0) {
1143
+ const error = `Named Merge gate check-runs read failed on tip ${tipSha}; fail closed (#6071). ` +
1144
+ fetched.error;
1145
+ return {
1146
+ ok: false,
1147
+ outcome: OUTCOME_MASTER_MERGE_GATE_RED,
1148
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1149
+ error,
1150
+ status: { ...emptyNamedStatus(tipSha), disposition: "api_fail", error },
1151
+ hotfix: null,
1152
+ };
1153
+ }
1154
+ checkRuns = fetched.checkRuns;
1155
+ }
1156
+ const status = evaluateNamedMergeGateFromRuns(tipSha, checkRuns, {
1157
+ pendingTimeoutMinutes: options.pendingTimeoutMinutes,
1158
+ nowMs: options.nowMs,
1159
+ });
1160
+ if (status.disposition === "green") {
1161
+ return {
1162
+ ok: true,
1163
+ outcome: null,
1164
+ exitCode: null,
1165
+ error: null,
1166
+ status,
1167
+ hotfix: null,
1168
+ };
1169
+ }
1170
+ // Consumer contract: Directive does not assume consumer CI workflows exist.
1171
+ // Always-on named gate stays fail-closed on deftai/directive; other repos skip
1172
+ // only when the exact check name is absent (red/pending/skipped still refuse).
1173
+ if (status.disposition === "missing" && repo.toLowerCase() !== "deftai/directive") {
1174
+ return {
1175
+ ok: true,
1176
+ outcome: null,
1177
+ exitCode: null,
1178
+ error: null,
1179
+ status: {
1180
+ ...status,
1181
+ error: null,
1182
+ },
1183
+ hotfix: null,
1184
+ };
1185
+ }
1186
+ if (prNumber === null) {
1187
+ if (options.firstHotfixBootstrap === true) {
1188
+ const hotfix = evaluateHotfixAdmission(0, repo, options);
1189
+ return {
1190
+ ok: false,
1191
+ outcome: OUTCOME_FIRST_HOTFIX_BOOTSTRAP,
1192
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1193
+ error: hotfix.reason ?? FIRST_HOTFIX_BOOTSTRAP_RECOVERY,
1194
+ status,
1195
+ hotfix,
1196
+ };
1197
+ }
1198
+ return {
1199
+ ok: false,
1200
+ outcome: OUTCOME_MASTER_MERGE_GATE_RED,
1201
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1202
+ error: status.error,
1203
+ status,
1204
+ hotfix: null,
1205
+ };
1206
+ }
1207
+ const hotfix = evaluateHotfixAdmission(prNumber, repo, options);
1208
+ if (hotfix.admitted) {
1209
+ return { ok: true, outcome: null, exitCode: null, error: null, status, hotfix };
1210
+ }
1211
+ if (hotfix.outcome === OUTCOME_HOTFIX_SHAPE_INVALID) {
1212
+ return {
1213
+ ok: false,
1214
+ outcome: OUTCOME_HOTFIX_SHAPE_INVALID,
1215
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1216
+ error: hotfix.reason ?? status.error,
1217
+ status,
1218
+ hotfix,
1219
+ };
1220
+ }
1221
+ if (hotfix.outcome === OUTCOME_HOTFIX_ACTOR_DENIED ||
1222
+ hotfix.outcome === OUTCOME_FIRST_HOTFIX_BOOTSTRAP) {
1223
+ return {
1224
+ ok: false,
1225
+ outcome: hotfix.outcome,
1226
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1227
+ error: hotfix.reason ?? status.error,
1228
+ status,
1229
+ hotfix,
1230
+ };
1231
+ }
1232
+ return {
1233
+ ok: false,
1234
+ outcome: OUTCOME_MASTER_MERGE_GATE_RED,
1235
+ exitCode: EXIT_TIMEOUT_OR_ESCALATION,
1236
+ error: status.error,
1237
+ status,
1238
+ hotfix,
1239
+ };
1240
+ }
557
1241
  //# sourceMappingURL=semantic-green.js.map