@bridge_gpt/mcp-server 0.2.58 → 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 +231 -113
- package/build/agent-guidance.generated.js +1 -1
- package/build/agent-notes.js +7 -1
- package/build/agent-registry.js +15 -0
- package/build/agent-utils.js +16 -8
- package/build/claude-user-config-doctor.js +1 -35
- package/build/codex-bridge-profile.js +1 -1
- package/build/commands.generated.js +7 -8
- package/build/conductor/bridge-api-client.js +202 -2
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/claude-hook.js +16 -16
- package/build/conductor/doctor.js +279 -72
- package/build/conductor/epic-implementer-cli.js +38 -5
- package/build/conductor/epic-reconcile.js +1 -1
- package/build/conductor/epic-runtime.js +21 -12
- package/build/conductor/errors.js +3 -3
- package/build/conductor/file-scope-guard.js +4 -4
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-inspection.js +2 -2
- package/build/conductor/install-doctor.js +28 -6
- 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/merge-ledger.js +2 -2
- package/build/conductor/pr-ci-producer.js +6 -6
- package/build/conductor/pr-review-producer.js +2 -2
- package/build/conductor/producer-ledger.js +2 -0
- package/build/conductor/readiness-sections.js +166 -9
- package/build/conductor/readiness.js +97 -16
- package/build/conductor/recovery-cli.js +243 -12
- package/build/conductor/recovery-operations.js +64 -6
- package/build/conductor/repair-contract.js +38 -4
- package/build/conductor/spec-review-producer.js +6 -6
- package/build/conductor/store.js +10 -10
- package/build/conductor/supervisor-config.js +14 -14
- package/build/conductor/supervisor-ledger.js +2 -2
- package/build/conductor/supervisor-message-relay.js +2 -2
- package/build/conductor/worker-ledger-cli.js +19 -19
- package/build/conductor-bundle-artifacts.js +2 -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 +199 -303
- package/build/drive-epic.js +88 -71
- package/build/epic-implementer-bin.js +22 -17
- package/build/epic-implementer-claude-hook-bin.js +4 -2
- package/build/epic-implementer-env.js +184 -0
- package/build/epic-integration-pr.js +2 -2
- 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 +79 -8
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/index.js +21 -4
- package/build/executor/job-errors.js +3 -3
- package/build/executor/job-runner.js +127 -1
- package/build/executor/job-types.js +12 -2
- package/build/executor/merge-job.js +46 -5
- package/build/executor/observation.js +1 -1
- package/build/executor/preflight.js +1 -1
- package/build/executor/prompt-spec.js +1 -1
- package/build/executor/reconcile-overlap-job.js +10 -130
- package/build/executor/reconcile-overlap-types.js +1 -1
- package/build/executor/runner.js +1 -1
- 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 +320 -342
- package/build/init.js +20 -28
- package/build/install-bridge-conductor.js +208 -15
- package/build/install-bridge.js +555 -37
- package/build/mcp-host-entry-adapter.js +6 -0
- package/build/mcp-host-targets.js +2 -0
- package/build/mcp-install-state.js +42 -4
- package/build/mcp-invoke.js +5 -5
- package/build/mcp-profile.js +2 -2
- package/build/mcp-provisioning.js +2 -2
- package/build/mcp-server-invocation.js +3 -3
- package/build/merge-pull-request.js +118 -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/model-routing-host.js +25 -0
- package/build/model-routing-preferences.js +99 -0
- package/build/pipelines.generated.js +10 -10
- package/build/{plan-epic-conductor-eligibility.js → plan-epic-implementer-eligibility.js} +18 -16
- package/build/plan-execution-ledger.js +8 -0
- package/build/plane/cli.js +24 -2
- package/build/plane/member-roster.js +5 -5
- package/build/plane/preflight.js +68 -0
- package/build/plane/supervisor.js +1 -1
- package/build/plane/types.js +5 -5
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +4 -4
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +171 -17
- package/build/start-tickets-conductor.js +32 -32
- package/build/start-tickets-prereqs.js +4 -9
- package/build/start-tickets.js +475 -91
- package/build/version.generated.js +3 -3
- package/docs/CONDUCTOR.md +69 -18
- package/docs/install/mcp-tool-integrations.md +2 -1
- package/package.json +4 -4
- package/pipelines/implement-ticket.json +1 -1
- package/pipelines/plan-epic.json +2 -2
- 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/checkpoint-store.js +0 -542
- package/build/implement-epic/cli.js +0 -3158
- 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/{implement-epic → epic-shared}/bridge-client.js +0 -0
- /package/build/{implement-epic → epic-shared}/cut-protocol.js +0 -0
|
@@ -109,7 +109,7 @@ export function buildGateMetEventInput(binding, evaluation, runId = null, worker
|
|
|
109
109
|
if (!evaluation.met || !evaluation.gateEventData)
|
|
110
110
|
return null;
|
|
111
111
|
return {
|
|
112
|
-
source: "
|
|
112
|
+
source: "epic-implementer",
|
|
113
113
|
type: "gate.met",
|
|
114
114
|
subject: binding.subject,
|
|
115
115
|
run_id: runId,
|
|
@@ -142,8 +142,8 @@ async function observeWithResolved(binding, access, gateConfig, deps, expectedBa
|
|
|
142
142
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
143
143
|
// Resolve run_id/worker_id for attribution. Env takes precedence; fall back
|
|
144
144
|
// to the injected resolution seam when the env identity is absent.
|
|
145
|
-
let run_id = deps.env?.
|
|
146
|
-
const worker_id = deps.env?.
|
|
145
|
+
let run_id = deps.env?.BAPI_EPIC_IMPLEMENTER_RUN_ID?.trim() || null;
|
|
146
|
+
const worker_id = deps.env?.BAPI_EPIC_IMPLEMENTER_WORKER_ID?.trim() || null;
|
|
147
147
|
if (run_id === null && deps.resolveRunId) {
|
|
148
148
|
try {
|
|
149
149
|
run_id = await deps.resolveRunId(access, binding) ?? null;
|
|
@@ -432,8 +432,8 @@ export async function observePrCiFromPollResponse(commitRef, pollResponse, deps
|
|
|
432
432
|
}
|
|
433
433
|
// Env-stamp identity; fall back to the resolution seam when env is absent.
|
|
434
434
|
// Access resolution for the seam is attempted inline within the gate block.
|
|
435
|
-
let run_id = deps.env?.
|
|
436
|
-
const worker_id = deps.env?.
|
|
435
|
+
let run_id = deps.env?.BAPI_EPIC_IMPLEMENTER_RUN_ID?.trim() || null;
|
|
436
|
+
const worker_id = deps.env?.BAPI_EPIC_IMPLEMENTER_WORKER_ID?.trim() || null;
|
|
437
437
|
const result = {
|
|
438
438
|
binding,
|
|
439
439
|
pr_opened_emitted: false,
|
|
@@ -541,7 +541,7 @@ function extractTicketKeyFromRef(headRef) {
|
|
|
541
541
|
* any step fails — the producer never crashes on a resolution failure.
|
|
542
542
|
*
|
|
543
543
|
* Intended to be injected as `PrCiProducerDeps.resolveRunId` at call sites
|
|
544
|
-
* that lack `
|
|
544
|
+
* that lack `BAPI_EPIC_IMPLEMENTER_RUN_ID` in the environment (git-hook / CI-poll
|
|
545
545
|
* contexts). A production `fetchImpl` is optional; defaults to the global fetch.
|
|
546
546
|
*/
|
|
547
547
|
export async function resolveDispatchRunIdForBinding(access, binding, fetchImpl = fetch) {
|
|
@@ -58,8 +58,8 @@ export function buildReviewObservationEventInput(binding, snapshot, eventType, r
|
|
|
58
58
|
export async function observeReviewWithResolved(binding, access, gateConfig, deps = {}) {
|
|
59
59
|
const fetchStatus = deps.fetchReviewStatus ?? fetchPrReviewStatus;
|
|
60
60
|
const emitIfNew = deps.emitIfNew ?? emitConductorEventIfNew;
|
|
61
|
-
const run_id = deps.env?.
|
|
62
|
-
const worker_id = deps.env?.
|
|
61
|
+
const run_id = deps.env?.BAPI_EPIC_IMPLEMENTER_RUN_ID?.trim() || null;
|
|
62
|
+
const worker_id = deps.env?.BAPI_EPIC_IMPLEMENTER_WORKER_ID?.trim() || null;
|
|
63
63
|
const result = {
|
|
64
64
|
snapshot: null,
|
|
65
65
|
review_passed_emitted: false,
|
|
@@ -33,6 +33,8 @@ export function makeProducerDedupeKey(dimensions) {
|
|
|
33
33
|
* the `events.id` UNIQUE constraint instead of appending a duplicate.
|
|
34
34
|
*/
|
|
35
35
|
export function makeStableProducerEventId(dedupeKey) {
|
|
36
|
+
// Upgrade seam: preserve the legacy namespace so retries spanning releases
|
|
37
|
+
// collide with the event ID already written before the wire-name migration.
|
|
36
38
|
const h = createHash("sha256").update(`conductor-producer:${dedupeKey}`).digest("hex");
|
|
37
39
|
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
38
40
|
}
|
|
@@ -36,10 +36,12 @@
|
|
|
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
|
-
export const CONDUCTOR_OPERATOR_RUNBOOK_POINTER = "docs/claude/epic-
|
|
44
|
+
export const CONDUCTOR_OPERATOR_RUNBOOK_POINTER = "docs/claude/epic-implementer-v2-operator-runbook.md";
|
|
43
45
|
/**
|
|
44
46
|
* Capability-matrix guidance for the executor leg.
|
|
45
47
|
*
|
|
@@ -51,13 +53,13 @@ export const CONDUCTOR_OPERATOR_RUNBOOK_POINTER = "docs/claude/epic-conductor-v2
|
|
|
51
53
|
export const EXECUTOR_PROVISIONING_GUIDANCE = "`install conductor` writes the unit and starts it, each behind its own consent; " +
|
|
52
54
|
"readiness still comes only from a live server observation";
|
|
53
55
|
/**
|
|
54
|
-
* The `BRIDGE_MCP_PROFILE` token that makes the 8
|
|
56
|
+
* The `BRIDGE_MCP_PROFILE` token that makes the 8 Epic Implementer MCP tools visible.
|
|
55
57
|
*
|
|
56
58
|
* The tools are profile-gated, never core, and this is the only token that
|
|
57
59
|
* un-gates them — so the doctor names it explicitly rather than describing the
|
|
58
60
|
* gap abstractly (BAPI-775).
|
|
59
61
|
*/
|
|
60
|
-
export const
|
|
62
|
+
export const EPIC_IMPLEMENTER_PROFILE_TOKEN = "epic_implementer";
|
|
61
63
|
/** Remediation for a host MCP config that carries no Bridge entry at all. */
|
|
62
64
|
export const CONDUCTOR_PROFILE_TOKEN_NO_ENTRY_REMEDIATION = "no supported host MCP config carries a Bridge entry — run " +
|
|
63
65
|
`\`npx ${MCP_PACKAGE_NAME} install\` to register one, then re-run \`install conductor\`.`;
|
|
@@ -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) {
|
|
@@ -500,7 +590,7 @@ export function executorObservationSection(readiness) {
|
|
|
500
590
|
* Whether this host looks like a conductor context.
|
|
501
591
|
*
|
|
502
592
|
* Composed from the two facts already collected: the resolved MCP-profile
|
|
503
|
-
* inspection (`
|
|
593
|
+
* inspection (`BAPI_EPIC_IMPLEMENTER_ENABLED` or a registered epic-tick schedule) and
|
|
504
594
|
* the read-only host token inspection. Outside such a context a missing local
|
|
505
595
|
* capability is ADVISORY — reporting it as a gap would train operators of
|
|
506
596
|
* ordinary repositories to ignore this whole part of the report.
|
|
@@ -522,7 +612,7 @@ export function detectConductorContext(legacyConductor, profile) {
|
|
|
522
612
|
* that Codex or Copilot configuration was inspected or changed.
|
|
523
613
|
*/
|
|
524
614
|
export function conductorProfileTokenSection(profile, conductorContext) {
|
|
525
|
-
const base = { id: "
|
|
615
|
+
const base = { id: "epic-implementer-profile-token", label: "Epic Implementer MCP profile token" };
|
|
526
616
|
if (profile === null) {
|
|
527
617
|
return {
|
|
528
618
|
...base,
|
|
@@ -630,10 +720,77 @@ 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
|
-
id: "
|
|
636
|
-
label: "
|
|
792
|
+
id: "epic-implementer-ledger-loadability",
|
|
793
|
+
label: "Epic Implementer ledger loadability",
|
|
637
794
|
};
|
|
638
795
|
if (legacyConductor === null || legacyConductor === undefined) {
|
|
639
796
|
return {
|
|
@@ -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;
|
|
@@ -626,8 +707,8 @@ const INSTALL_SECTION_IDS_OWNED_ELSEWHERE = new Set([
|
|
|
626
707
|
"server-readiness",
|
|
627
708
|
"bridge-access",
|
|
628
709
|
// Local conductor facts — owned by `mapConductorDoctorReportToReadinessChecks`.
|
|
629
|
-
"
|
|
630
|
-
"
|
|
710
|
+
"epic-implementer-ledger",
|
|
711
|
+
"epic-implementer-ledger-loadability",
|
|
631
712
|
// Legacy embedded reports — owned by their own adapters.
|
|
632
713
|
"install-status",
|
|
633
714
|
]);
|