patchrelay 0.59.1 → 0.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "service": "patchrelay",
3
- "version": "0.59.1",
4
- "commit": "1e520e0829e3",
5
- "builtAt": "2026-05-02T23:50:20.540Z"
3
+ "version": "0.60.0",
4
+ "commit": "116496455e0c",
5
+ "builtAt": "2026-05-04T22:16:11.428Z"
6
6
  }
@@ -336,6 +336,10 @@ async function evaluateGitHubIssueHealth(snapshot, config, runCommand, reviewQui
336
336
  if (issue.delegatedToPatchRelay
337
337
  && gateCheckStatus === "failure"
338
338
  && issue.factoryState !== "repairing_ci"
339
+ // Plan §6.1 / §4.3: branch CI failures while In Deploy are
340
+ // metadata only — the lander's spec CI is the gate. Don't flag
341
+ // these as a missing-ci-repair condition.
342
+ && issue.factoryState !== "awaiting_queue"
339
343
  && issue.activeRunId === undefined
340
344
  && ageMs >= RECONCILIATION_GRACE_MS) {
341
345
  return {
@@ -58,9 +58,29 @@ const TRANSITION_RULES = [
58
58
  { event: "check_passed",
59
59
  guard: (s) => s === "repairing_ci",
60
60
  to: (_, ctx) => ctx.prReviewState === "approved" ? "awaiting_queue" : "pr_open" },
61
- // CI failure when no run is active triggers repair.
61
+ // CI failure when no run is active. Plan §4.3: classification by
62
+ // failureSource determines whether we route to queue or branch repair.
63
+ // While In Deploy (`awaiting_queue`), branch CI failures are metadata
64
+ // only — the lander handles the spec; we don't initiate a ci_repair.
62
65
  { event: "check_failed",
63
- guard: (s, ctx) => isOpen(s) && ctx.activeRunId === undefined,
66
+ guard: (s, ctx) => isOpen(s)
67
+ && ctx.activeRunId === undefined
68
+ && ctx.failureSource === "queue_eviction",
69
+ to: "repairing_queue" },
70
+ { event: "check_failed",
71
+ guard: (s, ctx) => isOpen(s)
72
+ && ctx.activeRunId === undefined
73
+ && ctx.failureSource === "branch_ci"
74
+ && s !== "awaiting_queue",
75
+ to: "repairing_ci" },
76
+ // Backward-compat fallback for callers that haven't classified the
77
+ // failure (older code paths or test fixtures). Behaves like today's
78
+ // single rule but still skips while In Deploy.
79
+ { event: "check_failed",
80
+ guard: (s, ctx) => isOpen(s)
81
+ && ctx.activeRunId === undefined
82
+ && ctx.failureSource === undefined
83
+ && s !== "awaiting_queue",
64
84
  to: "repairing_ci" },
65
85
  // pr_synchronize: no rule → no transition (resets counters only)
66
86
  // merge_group events: not used — merge queue is handled by external steward
@@ -1,6 +1,5 @@
1
1
  import { resolveFactoryStateFromGitHub } from "./factory-state.js";
2
2
  import { resolveMergeQueueProtocol, } from "./merge-queue-protocol.js";
3
- import { isIssueTerminal } from "./pr-state.js";
4
3
  const DEFAULT_GATE_CHECK_NAMES = ["verify", "tests"];
5
4
  /**
6
5
  * GitHub sends both check_run and check_suite completion events.
@@ -84,16 +83,16 @@ export function resolveGitHubFactoryStateForEvent(issue, event, project) {
84
83
  && (event.prState === "open" || event.prNumber !== undefined)
85
84
  ? "pr_open"
86
85
  : issue.factoryState;
87
- if (event.triggerEvent === "check_failed"
88
- && isQueueEvictionFailure(issue, event, project)
89
- && issue.prState === "open"
90
- && issue.activeRunId === undefined
91
- && !isIssueTerminal(issue)) {
92
- return "repairing_queue";
93
- }
86
+ // Classify check_failed events so the rule table can route them.
87
+ // The duplicate short-circuit that lived here before is gone — the
88
+ // table now handles queue_eviction via failureSource (plan §4.3).
89
+ const failureSource = event.triggerEvent === "check_failed"
90
+ ? (isQueueEvictionFailure(issue, event, project) ? "queue_eviction" : "branch_ci")
91
+ : undefined;
94
92
  const resolved = resolveFactoryStateFromGitHub(event.triggerEvent, effectiveCurrentState, {
95
93
  prReviewState: issue.prReviewState,
96
94
  activeRunId: issue.activeRunId,
95
+ failureSource,
97
96
  });
98
97
  if (resolved !== undefined) {
99
98
  return resolved;
@@ -49,6 +49,22 @@ export async function maybeEnqueueGitHubReactiveRun(params) {
49
49
  }
50
50
  async function handleCheckFailedEvent(params) {
51
51
  const { db, logger, feed, enqueueIssue, issue, event, project, failureContextResolver } = params;
52
+ // Plan §4.3: while In Deploy (`awaiting_queue`), branch CI is metadata
53
+ // only — the lander owns admission, and its spec CI on the integration
54
+ // tree is the gate. Queue eviction failures still flow through (they're
55
+ // how the lander signals a real integration regression).
56
+ if (issue.factoryState === "awaiting_queue" && !isQueueEvictionFailure(issue, event, project)) {
57
+ feed?.publish({
58
+ level: "info",
59
+ kind: "github",
60
+ issueKey: issue.issueKey,
61
+ projectId: issue.projectId,
62
+ stage: issue.factoryState,
63
+ status: "branch_ci_metadata_in_deploy",
64
+ summary: `Ignored ${event.checkName ?? "branch CI"} failure while In Deploy; lander owns admission`,
65
+ });
66
+ return;
67
+ }
52
68
  if (isQueueEvictionFailure(issue, event, project)) {
53
69
  const queueRepairContext = buildQueueRepairContextFromEvent(event);
54
70
  const failureContext = buildGitHubQueueFailureContext(event, project, queueRepairContext);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.59.1",
3
+ "version": "0.60.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {