@bridge_gpt/mcp-server 0.2.59 → 0.2.60
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.
- package/README.md +173 -51
- package/build/agent-guidance.generated.js +1 -1
- package/build/agent-notes.js +7 -1
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +53 -2
- package/build/conductor/doctor.js +255 -53
- package/build/conductor/epic-runtime.js +11 -2
- package/build/conductor/install-doctor.js +24 -2
- package/build/conductor/local-merge.js +319 -26
- package/build/conductor/merge-execution-contract.js +61 -0
- package/build/conductor/merge-execution-eligibility.js +129 -0
- package/build/conductor/readiness-sections.js +159 -2
- package/build/conductor/readiness.js +95 -14
- package/build/conductor/recovery-operations.js +8 -2
- package/build/connect-bitbucket-api.js +6 -1
- package/build/connect-bitbucket.js +398 -88
- package/build/credential-store.js +16 -0
- package/build/docs.generated.js +2 -2
- package/build/doctor.js +183 -9
- package/build/drive-epic.js +23 -0
- package/build/epic-implementer-bin.js +10 -10
- package/build/epic-implementer-claude-hook-bin.js +1 -1
- package/build/epic-implementer-env.js +43 -14
- package/build/executor/ci-rerun-job.js +269 -0
- package/build/executor/cli.js +31 -0
- package/build/executor/credentials.js +11 -0
- package/build/executor/env.js +60 -0
- package/build/executor/index.js +21 -4
- package/build/executor/job-runner.js +126 -0
- package/build/executor/job-types.js +12 -2
- package/build/executor/merge-job.js +11 -2
- package/build/executor/service-lifecycle.js +136 -0
- package/build/executor/service-unit.js +427 -9
- package/build/executor/types.js +5 -0
- package/build/index.js +194 -179
- package/build/install-bridge-conductor.js +201 -8
- package/build/install-bridge.js +356 -30
- package/build/mcp-install-state.js +42 -4
- package/build/merge-pull-request.js +103 -10
- package/build/merge-workflow-codeowners.js +224 -0
- package/build/merge-workflow-config.js +133 -0
- package/build/merge-workflow-drift.js +66 -0
- package/build/merge-workflow-install.js +638 -0
- package/build/merge-workflow.js +572 -0
- package/build/pipelines.generated.js +2 -2
- package/build/plane/cli.js +24 -2
- package/build/plane/preflight.js +68 -0
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +163 -9
- package/build/version.generated.js +3 -3
- package/package.json +3 -3
- package/build/conduct-epic/bridge-client.js +0 -773
- package/build/conduct-epic/checkpoint-store.js +0 -542
- package/build/conduct-epic/cli.js +0 -3145
- package/build/conduct-epic/cut-protocol.js +0 -392
- package/build/conduct-epic/lock.js +0 -302
- package/build/conduct-epic/pr-state.js +0 -286
- package/build/conduct-epic/spawn.js +0 -113
- package/build/conductor/cli.js +0 -1278
- package/build/conductor-bundle-cli.js +0 -256
- package/build/direct-ticket-tools.js +0 -70
- package/build/executor/reconcile-overlap-governance.js +0 -129
- package/build/implement-epic/bridge-client.js +0 -773
- package/build/implement-epic/checkpoint-store.js +0 -542
- package/build/implement-epic/cli.js +0 -3158
- package/build/implement-epic/cut-protocol.js +0 -392
- package/build/implement-epic/lock.js +0 -302
- package/build/implement-epic/pr-state.js +0 -286
- package/build/implement-epic/spawn.js +0 -113
- package/build/plan-epic-conductor-eligibility.js +0 -213
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
import { MANAGED_HOOK_NAMES } from "./git-hooks.js";
|
|
37
37
|
import { MCP_PACKAGE_NAME } from "../mcp-identity.js";
|
|
38
38
|
import { CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION, summarizeClaudeReviewWorkflowDrift, } from "../claude-review-workflow-drift.js";
|
|
39
|
-
import { describeNativeLedgerAvailability } from "./doctor.js";
|
|
39
|
+
import { describeNativeLedgerAvailability, GH_ACTIONS_MERGE_AUTH_REMEDIATION, GH_ACTIONS_MERGE_SKIPPED_DETAIL, GH_ACTIONS_MERGE_UNOBSERVABLE_REMEDIATION, } from "./doctor.js";
|
|
40
|
+
import { classifyMergeExecutionEligibility, MERGE_EXECUTION_UNVERIFIABLE_REMEDIATION, } from "./merge-execution-eligibility.js";
|
|
41
|
+
import { MERGE_WORKFLOW_EXPECTED_VERSION, MERGE_WORKFLOW_PATH, } from "./merge-execution-contract.js";
|
|
40
42
|
import { classifyDatabaseMigration, classifySingleTransitionAuthority, classifyWorkerPresent, } from "./bring-up-facts.js";
|
|
41
43
|
/** Operator runbook pointer surfaced beside the executor gap. */
|
|
42
44
|
export const CONDUCTOR_OPERATOR_RUNBOOK_POINTER = "docs/claude/epic-implementer-v2-operator-runbook.md";
|
|
@@ -71,7 +73,7 @@ export const CONDUCTOR_HOOK_TARGET_REMEDIATION = "re-run `install conductor` and
|
|
|
71
73
|
export const CONDUCTOR_LEDGER_LOADABILITY_REMEDIATION = "`better-sqlite3` is an optionalDependency npm silently skips on a build failure — " +
|
|
72
74
|
"reinstall it for this Node runtime to restore local conductor observability.";
|
|
73
75
|
/** Remediation surfaced whenever no healthy generated service unit is detected. */
|
|
74
|
-
export const EXECUTOR_INSTALL_SERVICE_REMEDIATION = "generate a persistent unit with `install conductor --executor-id <id
|
|
76
|
+
export const EXECUTOR_INSTALL_SERVICE_REMEDIATION = "generate a persistent unit with `install conductor --executor-id <id> --repo-wide` or " +
|
|
75
77
|
"`executor install-service` (or run the executor by hand) — " +
|
|
76
78
|
`see ${CONDUCTOR_OPERATOR_RUNBOOK_POINTER}.`;
|
|
77
79
|
/** Remediation for a unit that exists but whose service is not running. */
|
|
@@ -216,6 +218,94 @@ export function githubActionsSection(readiness) {
|
|
|
216
218
|
remediation: "grant the GitHub App `actions: write`; the conductor's workflow rerun lane fails open on 403 without it.",
|
|
217
219
|
};
|
|
218
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* The "Merge execution eligibility" section (BAPI-1135).
|
|
223
|
+
*
|
|
224
|
+
* A DEDICATED section rather than extra rows on the GitHub App block, because it
|
|
225
|
+
* is a different safety diagnostic about a different principal: the App block
|
|
226
|
+
* describes the installation's dispatch permission, this one describes whether
|
|
227
|
+
* the repository's merge workflow can actually merge.
|
|
228
|
+
*
|
|
229
|
+
* Rows are rendered in DEPENDENCY order — probe/readability, presence, enabled
|
|
230
|
+
* state, exact version, App `actions: write`, workflow-token write authority,
|
|
231
|
+
* branch-policy admission — so a reader meets the fact that decided the verdict
|
|
232
|
+
* before the facts that were never reached.
|
|
233
|
+
*
|
|
234
|
+
* Three rendering rules this section holds to:
|
|
235
|
+
*
|
|
236
|
+
* * an absent older-server block and a failed probe are explicit UNKNOWN rows,
|
|
237
|
+
* never an omitted section and never a pass. "Not reported" is not "healthy";
|
|
238
|
+
* omitting the section would make an unknown look like a clean bill of health.
|
|
239
|
+
* * confirmed policy denial stays verbally distinct from unreadability. The
|
|
240
|
+
* status word (`degraded` for unknown, `fatal`-adjacent `degraded` with
|
|
241
|
+
* different copy for confirmed) is carried in TEXT, not by colour alone, and
|
|
242
|
+
* the detail line opens with `could not verify:` or `not eligible:`.
|
|
243
|
+
* * command strings, workflow paths, versions, and reason codes are rendered as
|
|
244
|
+
* backticked monospace through the established section conventions, and are
|
|
245
|
+
* allowed to wrap rather than being truncated — a truncated remedy command is
|
|
246
|
+
* not a remedy.
|
|
247
|
+
*/
|
|
248
|
+
export function mergeExecutionSections(readiness) {
|
|
249
|
+
const verdict = classifyMergeExecutionEligibility(readiness);
|
|
250
|
+
const m = readiness.merge_execution;
|
|
251
|
+
if (m === null || m === undefined) {
|
|
252
|
+
// One row, not seven. Fabricating six more unknown rows from a block that
|
|
253
|
+
// was never reported would imply the server answered six questions.
|
|
254
|
+
return [
|
|
255
|
+
{
|
|
256
|
+
id: "merge-execution-eligibility",
|
|
257
|
+
label: "Merge execution eligibility",
|
|
258
|
+
status: "degraded",
|
|
259
|
+
detail: `could not verify: ${verdict.detail}`,
|
|
260
|
+
remediation: verdict.remediation ?? MERGE_EXECUTION_UNVERIFIABLE_REMEDIATION,
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
}
|
|
264
|
+
const decisive = {
|
|
265
|
+
id: "merge-execution-eligibility",
|
|
266
|
+
label: "Merge execution eligibility",
|
|
267
|
+
status: verdict.status === "eligible" ? "ok" : "degraded",
|
|
268
|
+
detail: verdict.status === "eligible"
|
|
269
|
+
? `eligible: ${verdict.detail}`
|
|
270
|
+
: verdict.status === "could_not_verify"
|
|
271
|
+
? `could not verify: ${verdict.detail}`
|
|
272
|
+
: `not eligible: ${verdict.detail} (\`${verdict.reason}\`)`,
|
|
273
|
+
...(verdict.remediation ? { remediation: verdict.remediation } : {}),
|
|
274
|
+
};
|
|
275
|
+
// The supporting facts, in dependency order. Each is a plain readable row — no
|
|
276
|
+
// remediation of its own, because the decisive row above already names the one
|
|
277
|
+
// action, and repeating it seven times would bury it.
|
|
278
|
+
const fact = (id, label, ok, detail) => ({
|
|
279
|
+
id,
|
|
280
|
+
label,
|
|
281
|
+
status: ok ? "ok" : "degraded",
|
|
282
|
+
detail,
|
|
283
|
+
});
|
|
284
|
+
return [
|
|
285
|
+
decisive,
|
|
286
|
+
fact("merge-execution-branch-policy-readable", "Branch policy readable", m.branch_protection_probe_succeeded, m.branch_protection_probe_succeeded
|
|
287
|
+
? `read on the ${m.branch_scope.replace("_", " ")}`
|
|
288
|
+
: "could not verify: the branch policy could not be read"),
|
|
289
|
+
fact("merge-execution-workflow-present", "Merge workflow present", m.merge_workflow_probe_succeeded && m.merge_workflow_present, !m.merge_workflow_probe_succeeded
|
|
290
|
+
? "could not verify: the workflow metadata could not be read"
|
|
291
|
+
: m.merge_workflow_present
|
|
292
|
+
? `\`${MERGE_WORKFLOW_PATH}\` present`
|
|
293
|
+
: `not eligible: \`${MERGE_WORKFLOW_PATH}\` is absent`),
|
|
294
|
+
fact("merge-execution-workflow-enabled", "Merge workflow enabled", m.merge_workflow_enabled, m.merge_workflow_enabled ? "enabled" : "not eligible: disabled or unknown"),
|
|
295
|
+
fact("merge-execution-workflow-version", "Merge workflow version", m.merge_workflow_version === MERGE_WORKFLOW_EXPECTED_VERSION, `\`${m.merge_workflow_version ?? "unset"}\` (expected \`${MERGE_WORKFLOW_EXPECTED_VERSION}\`)`),
|
|
296
|
+
fact("merge-execution-app-actions-write", "GitHub App `actions: write`", readiness.github.actions_write, readiness.github.actions_probe_succeeded
|
|
297
|
+
? `actions: \`${readiness.github.actions_permission_level}\``
|
|
298
|
+
: "could not verify: the App permission could not be checked"),
|
|
299
|
+
fact("merge-execution-workflow-token-write", "Workflow token write authority", m.merge_workflow_token_write_authority, m.merge_workflow_token_write_authority
|
|
300
|
+
? "the merge workflow's own token holds write authority"
|
|
301
|
+
: "not eligible: the merge workflow's own token does not hold write authority"),
|
|
302
|
+
fact("merge-execution-actor-admitted", "Branch policy admits the workflow actor", m.branch_protection_actions_bot_admitted, m.branch_protection_probe_succeeded
|
|
303
|
+
? m.branch_protection_actions_bot_admitted
|
|
304
|
+
? "admitted"
|
|
305
|
+
: "not eligible: the workflow actor is excluded"
|
|
306
|
+
: "could not verify: the branch policy could not be read"),
|
|
307
|
+
];
|
|
308
|
+
}
|
|
219
309
|
export function reconcilerSection(readiness) {
|
|
220
310
|
const r = readiness.reconciler;
|
|
221
311
|
if (!r.liveness_readable) {
|
|
@@ -630,6 +720,73 @@ export function conductorHookTargetsSection(legacyConductor, conductorContext) {
|
|
|
630
720
|
* sanitized failure kind, because the common cause is npm silently skipping an
|
|
631
721
|
* optionalDependency build and that is undiagnosable without the ABI.
|
|
632
722
|
*/
|
|
723
|
+
/**
|
|
724
|
+
* Project the doctor's read-only `github_actions` merge OBSERVABILITY inspection
|
|
725
|
+
* into this layer's sections (BAPI-1135).
|
|
726
|
+
*
|
|
727
|
+
* Projected here rather than formatted inside installation code, for the same
|
|
728
|
+
* reason every other doctor fact is: the install-time doctor and the advisory
|
|
729
|
+
* readiness gate must render one classification, and a second formatter in the
|
|
730
|
+
* installer is how the two come to describe the same probe differently.
|
|
731
|
+
*
|
|
732
|
+
* Two sections, never one. `gh` authentication and endpoint reachability are
|
|
733
|
+
* independently actionable, and a collapsed row would hide which of the two the
|
|
734
|
+
* operator has to fix.
|
|
735
|
+
*
|
|
736
|
+
* These describe THIS HOST's ability to observe — never whether the capability
|
|
737
|
+
* is sufficient. {@link mergeExecutionSections} owns that, from the server's own
|
|
738
|
+
* report. The wording keeps them apart deliberately: `not observable` and `could
|
|
739
|
+
* not verify` here, versus `not eligible` there.
|
|
740
|
+
*/
|
|
741
|
+
export function gitHubActionsMergeObservabilitySections(legacyConductor) {
|
|
742
|
+
const authBase = {
|
|
743
|
+
id: "gh-actions-merge-auth",
|
|
744
|
+
label: "GitHub Actions merge: local observation prerequisites",
|
|
745
|
+
};
|
|
746
|
+
const observeBase = {
|
|
747
|
+
id: "gh-actions-merge-observable",
|
|
748
|
+
label: "GitHub Actions merge: capability observability",
|
|
749
|
+
};
|
|
750
|
+
const inspection = legacyConductor?.github_actions_merge;
|
|
751
|
+
if (inspection === undefined || inspection === null) {
|
|
752
|
+
// The doctor was not collected, or predates this inspection. Both rows say
|
|
753
|
+
// so rather than being omitted — an absent row reads as "fine".
|
|
754
|
+
const detail = "could not verify — the epic-implementer doctor reported no observation";
|
|
755
|
+
return [
|
|
756
|
+
{ ...authBase, status: "degraded", detail },
|
|
757
|
+
{ ...observeBase, status: "degraded", detail },
|
|
758
|
+
];
|
|
759
|
+
}
|
|
760
|
+
return [
|
|
761
|
+
inspection.gh_authed
|
|
762
|
+
? { ...authBase, status: "ok", detail: "eligible to observe — `gh` is authenticated" }
|
|
763
|
+
: {
|
|
764
|
+
...authBase,
|
|
765
|
+
status: "degraded",
|
|
766
|
+
detail: "not observable — no authenticated `gh` identity on this host",
|
|
767
|
+
remediation: GH_ACTIONS_MERGE_AUTH_REMEDIATION,
|
|
768
|
+
},
|
|
769
|
+
!inspection.gh_authed
|
|
770
|
+
? {
|
|
771
|
+
...observeBase,
|
|
772
|
+
status: "degraded",
|
|
773
|
+
detail: GH_ACTIONS_MERGE_SKIPPED_DETAIL,
|
|
774
|
+
remediation: GH_ACTIONS_MERGE_AUTH_REMEDIATION,
|
|
775
|
+
}
|
|
776
|
+
: inspection.capability_observable
|
|
777
|
+
? {
|
|
778
|
+
...observeBase,
|
|
779
|
+
status: "ok",
|
|
780
|
+
detail: "eligible to observe — the workflow metadata endpoint answered",
|
|
781
|
+
}
|
|
782
|
+
: {
|
|
783
|
+
...observeBase,
|
|
784
|
+
status: "degraded",
|
|
785
|
+
detail: "could not verify — the workflow metadata endpoint could not be read",
|
|
786
|
+
remediation: GH_ACTIONS_MERGE_UNOBSERVABLE_REMEDIATION,
|
|
787
|
+
},
|
|
788
|
+
];
|
|
789
|
+
}
|
|
633
790
|
export function conductorLedgerLoadabilitySection(legacyConductor) {
|
|
634
791
|
const base = {
|
|
635
792
|
id: "epic-implementer-ledger-loadability",
|
|
@@ -47,19 +47,28 @@
|
|
|
47
47
|
* a local collector, and the Bridge API owner for a server-side collector that
|
|
48
48
|
* keeps timing out or answering unreadably.
|
|
49
49
|
*
|
|
50
|
-
* ##
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
50
|
+
* ## RETIRED deferral: local `gh` auth (AC-7, retired by BAPI-1136)
|
|
51
|
+
*
|
|
52
|
+
* This deferral named its retirement owner as epic A2, and A2 has now landed.
|
|
53
|
+
* It is recorded here rather than deleted, because the point of naming an owner
|
|
54
|
+
* is being able to check afterwards that the owner actually did the work.
|
|
55
|
+
*
|
|
56
|
+
* **What it used to be.** Detection was `conductor.gh-cli` and
|
|
57
|
+
* `conductor.gh-auth`: the merge ran under this host's own `gh` session, because
|
|
58
|
+
* the GitHub App token is `contents: read` and structurally cannot merge (see
|
|
59
|
+
* `mcp_server/src/executor/merge-job.ts`). Until A2 shipped, that ambient-session
|
|
60
|
+
* check was load-bearing.
|
|
61
|
+
*
|
|
62
|
+
* **What replaced it.** BAPI-1136 de-personalized the merge: it now authenticates
|
|
63
|
+
* with one explicitly configured, customer-held credential
|
|
64
|
+
* (`BAPI_LOCAL_MERGE_TOKEN` or `BAPI_MACHINE_MERGE_TOKEN`) and never with the
|
|
65
|
+
* ambient session. So the second check was RE-POINTED, not removed —
|
|
66
|
+
* `conductor.gh-cli` still asks whether `gh` exists, and
|
|
67
|
+
* `conductor.merge-credential` asks whether a credential is configured. Removing
|
|
68
|
+
* it would have been a silent downgrade; leaving it would have been worse, since
|
|
69
|
+
* it reported green on exactly the hosts about to fail closed. **Containment** and
|
|
70
|
+
* **escalation** are unchanged: the merge refuses with a named reason rather than
|
|
71
|
+
* merging without a credential, and the invoking operator is the first target.
|
|
63
72
|
*
|
|
64
73
|
* ## Deferred retirement: native SQLite ledger (AC-7)
|
|
65
74
|
*
|
|
@@ -89,6 +98,7 @@ import { mapPlanePreflightToReadinessChecks, } from "../plane/preflight.js";
|
|
|
89
98
|
import { runConductorInstallDoctor, } from "./install-doctor.js";
|
|
90
99
|
import { BRING_UP_SECTION_LABELS } from "./readiness-sections.js";
|
|
91
100
|
import { classifyBringUpFacts } from "./bring-up-facts.js";
|
|
101
|
+
import { classifyMergeExecutionEligibility, MERGE_EXECUTION_INELIGIBLE_REMEDIATION, MERGE_EXECUTION_UNVERIFIABLE_REMEDIATION, } from "./merge-execution-eligibility.js";
|
|
92
102
|
import { createReadinessCheck, createReadinessCheckSafely, summarizeReadinessChecks, validateReadinessChecks, } from "../readiness-check.js";
|
|
93
103
|
/** This report's own schema version. See the module docstring. */
|
|
94
104
|
export const CONDUCTOR_READINESS_GATE_SCHEMA_VERSION = 1;
|
|
@@ -110,6 +120,13 @@ export const SERVER_READINESS_DESCRIPTORS = [
|
|
|
110
120
|
{ id: "review-policy", label: "Review policy" },
|
|
111
121
|
{ id: "github-credentials", label: "GitHub App credentials" },
|
|
112
122
|
{ id: "github-actions-permission", label: "GitHub App actions permission" },
|
|
123
|
+
// BAPI-1135 — merge-execution eligibility, BESIDE the App permission row and
|
|
124
|
+
// never replacing it: that row is the installation's dispatch/rerun
|
|
125
|
+
// permission, these two are the merge workflow and the branch policy that
|
|
126
|
+
// governs it. Two rows rather than one, because a workflow that is absent and
|
|
127
|
+
// a branch policy that excludes the actor are separately actionable.
|
|
128
|
+
{ id: "merge-execution-workflow", label: "Merge workflow eligibility" },
|
|
129
|
+
{ id: "merge-execution-branch-policy", label: "Merge branch policy" },
|
|
113
130
|
// BAPI-1153 — the "Bring-up safety" group: deployment-wide facts that decide
|
|
114
131
|
// whether a plane can drive a run at all, ahead of the repository-scoped
|
|
115
132
|
// reconciler/executor rows. Labels come from the shared section layer.
|
|
@@ -163,7 +180,7 @@ const SERVER_REMEDIATIONS = {
|
|
|
163
180
|
reconcilerUnreadable: "retry once the Bridge API is reachable; reconciler liveness is unknown here, not confirmed stale.",
|
|
164
181
|
reconcilerStale: "start the reconciler for this repository (the `epic-implementer epic-tick` schedule) so ticks resume.",
|
|
165
182
|
executorUnreadable: "retry once the Bridge API is reachable; no executor readiness claim is ever made from local state.",
|
|
166
|
-
executorNeverSeen: "provision and start an executor (`install conductor --executor-id <id
|
|
183
|
+
executorNeverSeen: "provision and start an executor (`install conductor --executor-id <id> --repo-wide` or `executor install-service`); readiness comes only from a live server observation.",
|
|
167
184
|
executorStale: "restart the executor service; it was observed once and has gone quiet past the server's staleness threshold.",
|
|
168
185
|
reviewWorkflowUnknown: "re-run; the default branch's review workflow could not be evaluated, which is not the same as it being absent.",
|
|
169
186
|
reviewWorkflowIncomplete: "install the claude-review workflow template on the default branch so it emits both the run id and the reviewed SHA the verdict protocol reads.",
|
|
@@ -175,6 +192,11 @@ const SERVER_REMEDIATIONS = {
|
|
|
175
192
|
unattendedConsent: "set `unattended_epic_implementer_allowed` for this repository (docs/claude/account-settings-operator-runbook.md), or run setup with --attended.",
|
|
176
193
|
unattendedNotifyDefault: "supply a --policy-file declaring notify.webhook_url, or run setup with --attended; an unattended run must have a working escalation channel, and notify.local_sink is not one.",
|
|
177
194
|
repositoryWorkflowReadiness: "run `install-bridge conductor` so the review and conductor-CI workflows are confirmed on the default branch, or run setup with --attended.",
|
|
195
|
+
// BAPI-1135. Both are IMPORTED from the shared classifier rather than written
|
|
196
|
+
// out here, so the remedy command has exactly one spelling across the refusal,
|
|
197
|
+
// the readiness row, and the doctor — see `merge-execution-contract.ts`.
|
|
198
|
+
mergeExecutionUnverifiable: MERGE_EXECUTION_UNVERIFIABLE_REMEDIATION,
|
|
199
|
+
mergeExecutionIneligible: MERGE_EXECUTION_INELIGIBLE_REMEDIATION,
|
|
178
200
|
};
|
|
179
201
|
/**
|
|
180
202
|
* Project the validated server readiness response into `server.*` checks.
|
|
@@ -279,6 +301,7 @@ export function mapServerReadinessToChecks(readiness, failure = "unreachable") {
|
|
|
279
301
|
remediation: SERVER_REMEDIATIONS.githubCredentials,
|
|
280
302
|
});
|
|
281
303
|
outcomes.push(githubActionsOutcome(readiness));
|
|
304
|
+
outcomes.push(...mergeExecutionOutcomes(readiness));
|
|
282
305
|
outcomes.push(...bringUpOutcomes(readiness));
|
|
283
306
|
outcomes.push(reconcilerOutcome(readiness));
|
|
284
307
|
outcomes.push(executorOutcome(readiness));
|
|
@@ -460,6 +483,64 @@ function githubActionsOutcome(readiness) {
|
|
|
460
483
|
remediation: SERVER_REMEDIATIONS.actionsMissing,
|
|
461
484
|
};
|
|
462
485
|
}
|
|
486
|
+
/**
|
|
487
|
+
* The two BAPI-1135 merge-execution rows.
|
|
488
|
+
*
|
|
489
|
+
* Both are backed SOLELY by {@link classifyMergeExecutionEligibility}, so this
|
|
490
|
+
* gate, the install doctor's section, the `drive-epic` requirement, and the
|
|
491
|
+
* `setup-epic` refusal cannot disagree about the status or the first blocker.
|
|
492
|
+
* Nothing here re-reads the server block or re-derives a fact.
|
|
493
|
+
*
|
|
494
|
+
* A `could_not_verify` verdict produces a RETRY-oriented row on both, and never
|
|
495
|
+
* claims the capability is absent. A confirmed insufficiency produces a
|
|
496
|
+
* `not eligible` row carrying the exact shared remedy command. The two are
|
|
497
|
+
* deliberately different statuses — `fail` for confirmed, `warn` for unknown —
|
|
498
|
+
* so the visible status label distinguishes them without relying on colour.
|
|
499
|
+
*
|
|
500
|
+
* Which of the two rows carries the blocker is decided by the reason: a branch
|
|
501
|
+
* policy that could not be read or an actor that is not admitted belongs to the
|
|
502
|
+
* branch-policy row; everything else belongs to the workflow row. The other row
|
|
503
|
+
* then reports that it was not reached, rather than showing a misleading pass.
|
|
504
|
+
*/
|
|
505
|
+
function mergeExecutionOutcomes(readiness) {
|
|
506
|
+
const verdict = classifyMergeExecutionEligibility(readiness);
|
|
507
|
+
const branchScoped = verdict.reason === "branch_policy_unreadable" || verdict.reason === "actor_not_admitted";
|
|
508
|
+
if (verdict.status === "eligible") {
|
|
509
|
+
return [
|
|
510
|
+
{
|
|
511
|
+
id: "merge-execution-workflow",
|
|
512
|
+
status: "pass",
|
|
513
|
+
detail: verdict.detail,
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
id: "merge-execution-branch-policy",
|
|
517
|
+
status: "pass",
|
|
518
|
+
detail: "the branch policy admits the workflow actor",
|
|
519
|
+
},
|
|
520
|
+
];
|
|
521
|
+
}
|
|
522
|
+
const status = verdict.status === "could_not_verify" ? "warn" : "fail";
|
|
523
|
+
const remediation = verdict.status === "could_not_verify"
|
|
524
|
+
? SERVER_REMEDIATIONS.mergeExecutionUnverifiable
|
|
525
|
+
: SERVER_REMEDIATIONS.mergeExecutionIneligible;
|
|
526
|
+
const blocked = {
|
|
527
|
+
id: branchScoped ? "merge-execution-branch-policy" : "merge-execution-workflow",
|
|
528
|
+
status,
|
|
529
|
+
detail: verdict.status === "could_not_verify"
|
|
530
|
+
? `could not verify: ${verdict.detail}`
|
|
531
|
+
: `not eligible: ${verdict.detail}`,
|
|
532
|
+
remediation,
|
|
533
|
+
};
|
|
534
|
+
// The row that did not decide reports exactly that. Showing it as a pass would
|
|
535
|
+
// claim evidence the classifier stopped short of gathering.
|
|
536
|
+
const notReached = {
|
|
537
|
+
id: branchScoped ? "merge-execution-workflow" : "merge-execution-branch-policy",
|
|
538
|
+
status: "warn",
|
|
539
|
+
detail: "could not verify: not evaluated — an earlier merge-eligibility fact decided first",
|
|
540
|
+
remediation,
|
|
541
|
+
};
|
|
542
|
+
return branchScoped ? [notReached, blocked] : [blocked, notReached];
|
|
543
|
+
}
|
|
463
544
|
/** Reconciler liveness, preserving the server's own `stale` conclusion. */
|
|
464
545
|
function reconcilerOutcome(readiness) {
|
|
465
546
|
const r = readiness.reconciler;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { randomUUID } from "crypto";
|
|
21
21
|
import { adoptCurrentHeadAndUnparkTicket, completeMergeChild, ConductorBridgeApiError, dispatchRemediation, fetchEpicRunState, mergeChild, patchPolicy, resumeRun, safeDiagnosticMessage, scopeCatchUp, scopeReparse, stopEpicRun, unparkEpicTicket, updateEpicRunStatus, } from "./bridge-api-client.js";
|
|
22
22
|
import { DEFAULT_GATE_NAME } from "./git-ci-types.js";
|
|
23
|
-
import { resolveLocalMergeMethod, runApprovedLocalMerge } from "./local-merge.js";
|
|
23
|
+
import { resolveLocalMergeCredential, resolveLocalMergeMethod, runApprovedLocalMerge, } from "./local-merge.js";
|
|
24
24
|
/**
|
|
25
25
|
* Bounded total attempts for a ticket-recovery CAS retry loop. Each attempt
|
|
26
26
|
* begins with a fresh, authoritative state read — so this bounds full
|
|
@@ -312,7 +312,13 @@ export async function mergeChildRepair(access, options, deps = {}) {
|
|
|
312
312
|
// which is discarded — never the server's action key, which the server
|
|
313
313
|
// recomposes itself at completion.
|
|
314
314
|
action_key: `merge-child:${options.epicRunId}:${options.prNumber}`,
|
|
315
|
-
}, { method: resolveLocalMergeMethod(local.merge_method) },
|
|
315
|
+
}, { method: resolveLocalMergeMethod(local.merge_method) },
|
|
316
|
+
// BAPI-1136: an explicitly configured local credential, never the ambient
|
|
317
|
+
// login. `env` remains the source of the allowlisted operational base only.
|
|
318
|
+
{
|
|
319
|
+
env: deps.env ?? process.env,
|
|
320
|
+
credential: resolveLocalMergeCredential(deps.env ?? process.env, "local"),
|
|
321
|
+
});
|
|
316
322
|
succeeded = localResult.status === "succeeded";
|
|
317
323
|
alreadyMerged = succeeded && localResult.reason === "already_merged";
|
|
318
324
|
failureReason = boundedLocalMergeReason(localResult.reason);
|
|
@@ -138,7 +138,12 @@ export async function mintBitbucketConnection(deps, repoName) {
|
|
|
138
138
|
if (!body || !state || !installUrl || typeof ttlSeconds !== "number") {
|
|
139
139
|
return { ok: false, kind: "malformed" };
|
|
140
140
|
}
|
|
141
|
-
|
|
141
|
+
// Optional and permissive ON PURPOSE, and only here: absent, null, empty, or any
|
|
142
|
+
// non-string collapses to null through the same nullable helper the candidate parser
|
|
143
|
+
// uses. Required-field strictness above is untouched — an optional field must not
|
|
144
|
+
// become a new way for a server change to fail a mint.
|
|
145
|
+
const redeemUrlTemplate = asNullableString(body.redeem_url_template);
|
|
146
|
+
return { ok: true, value: { state, installUrl, ttlSeconds, redeemUrlTemplate } };
|
|
142
147
|
}
|
|
143
148
|
/** Strictly parse candidates. A candidate missing a usable identity fails the whole list. */
|
|
144
149
|
function parseCandidates(value) {
|