@bridge_gpt/mcp-server 0.2.51 → 0.2.53
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 +59 -13
- package/build/agent-capabilities/probe-context.js +15 -7
- package/build/agent-capabilities/probes.js +42 -6
- package/build/agent-launchers/claude-executor-adapter.js +98 -14
- package/build/commands.generated.js +7 -5
- package/build/conduct-epic/cut-protocol.js +17 -3
- package/build/conductor/bridge-api-client.js +232 -5
- package/build/conductor/cli.js +23 -0
- package/build/conductor/deny-enforcement-preflight.js +107 -10
- package/build/conductor/doctor.js +428 -5
- package/build/conductor/install-doctor.js +65 -656
- package/build/conductor/local-merge.js +170 -11
- package/build/conductor/readiness-cli.js +152 -0
- package/build/conductor/readiness-sections.js +666 -0
- package/build/conductor/readiness.js +710 -0
- package/build/conductor/tools.js +56 -3
- package/build/conductor-bin.js +21 -17
- package/build/connect-bitbucket-api.js +370 -0
- package/build/connect-bitbucket.js +437 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +40 -1
- package/build/drive-epic.js +423 -11
- package/build/env-file-link.js +164 -0
- package/build/epic-integration-pr.js +10 -0
- package/build/executor/cli.js +41 -6
- package/build/executor/deps.js +5 -1
- package/build/executor/env-file-guard.js +113 -0
- package/build/executor/env.js +78 -1
- package/build/executor/heartbeat.js +9 -0
- package/build/executor/http-client.js +90 -22
- package/build/executor/job-errors.js +43 -2
- package/build/executor/job-runner.js +130 -28
- package/build/executor/merge-job.js +67 -16
- package/build/executor/permissions.js +106 -0
- package/build/executor/preflight.js +38 -13
- package/build/executor/resume-pre-spawn.js +2 -1
- package/build/executor/runner.js +175 -4
- package/build/executor/service-unit.js +15 -0
- package/build/executor/terminal-mutation.js +22 -1
- package/build/executor/types.js +86 -0
- package/build/executor/worker-command.js +21 -5
- package/build/executor/worker-guard-hook.js +939 -0
- package/build/executor/worker-log.js +56 -0
- package/build/executor/worktree.js +11 -0
- package/build/git-reachability.js +147 -0
- package/build/index.js +4734 -4270
- package/build/install-bridge.js +95 -0
- package/build/install-doctor.js +154 -2
- package/build/pipelines.generated.js +6 -4
- package/build/plan-epic-conductor-eligibility.js +37 -7
- package/build/plane/alembic-head.js +40 -11
- package/build/plane/build-freshness.js +22 -11
- package/build/plane/cli.js +78 -15
- package/build/plane/defaults.js +165 -0
- package/build/plane/manifest.js +63 -8
- package/build/plane/member-logs.js +6 -0
- package/build/plane/member-roster.js +195 -11
- package/build/plane/preflight.js +402 -44
- package/build/plane/shutdown.js +25 -3
- package/build/plane/status.js +11 -0
- package/build/plane/supervisor.js +343 -14
- package/build/plane/test-fakes.js +43 -0
- package/build/plane/types.js +118 -11
- package/build/pr-base-contract.js +20 -0
- package/build/readiness-check.js +412 -0
- package/build/readme.generated.js +1 -1
- package/build/review-synthesis-config.js +60 -0
- package/build/scripts/executor-protocol-contract-driver.js +311 -0
- package/build/setup-epic.js +560 -139
- package/build/sfcc/log-query.js +2 -1
- package/build/start-tickets-conductor.js +11 -2
- package/build/start-tickets.js +69 -2
- package/build/version.generated.js +3 -3
- package/build/worker-containment-diagnostic.js +97 -0
- package/build/worker-guard-hook-bin.js +6 -0
- package/docs/CONDUCTOR.md +27 -0
- package/docs/install/mcp-tool-integrations.md +3 -2
- package/package.json +4 -3
- package/pipelines/{full-automation.json → idea-to-pr.json} +1 -1
|
@@ -16,6 +16,55 @@ import { inspectConductorGitHooks } from "./git-hooks.js";
|
|
|
16
16
|
import { resolveMcpShimInvocationForRuntime, resolvePackageRootFromModuleUrl, } from "../mcp-server-invocation.js";
|
|
17
17
|
import { MCP_PACKAGE_NAME } from "../mcp-identity.js";
|
|
18
18
|
import { resolveProfiles } from "../mcp-profile.js";
|
|
19
|
+
import { createReadinessCheck, createReadinessCheckSafely, } from "../readiness-check.js";
|
|
20
|
+
/**
|
|
21
|
+
* Remediation for a conductor context whose resolved MCP groups omit `conductor`.
|
|
22
|
+
*
|
|
23
|
+
* Fixed prose: it never interpolates the raw `BRIDGE_MCP_PROFILE` value. The
|
|
24
|
+
* legacy warning does interpolate it (unchanged), but a consolidated report is a
|
|
25
|
+
* wider surface and an operator-set environment variable is not a value this
|
|
26
|
+
* gate needs to echo to name the fix. ADD, never replace — replacing the value
|
|
27
|
+
* would drop the worker's other groups.
|
|
28
|
+
*/
|
|
29
|
+
export const MCP_PROFILE_REMEDIATION = "ADD `conductor` to BRIDGE_MCP_PROFILE (comma-separated) — do NOT replace the existing value, " +
|
|
30
|
+
"which would drop the worker's other tool groups. Without it the conductor/event/supervisor " +
|
|
31
|
+
"tools are not registered.";
|
|
32
|
+
/**
|
|
33
|
+
* Remediation for a host with no `gh` on PATH.
|
|
34
|
+
*
|
|
35
|
+
* LOAD-BEARING, not legacy (AC-7). Conductor merges run under the machine's own
|
|
36
|
+
* `gh` auth because the GitHub App token is `contents: read` and structurally
|
|
37
|
+
* cannot merge — see `mcp_server/src/executor/merge-job.ts`. Retiring this check
|
|
38
|
+
* is blocked on epic A2 (App elevation to `contents: write`); until A2 ships,
|
|
39
|
+
* `gh` is a real prerequisite and this stays a named check.
|
|
40
|
+
*/
|
|
41
|
+
export const GH_CLI_REMEDIATION = "install the GitHub CLI (`gh`) and put it on PATH; conductor-driven merges run under this " +
|
|
42
|
+
"host's own `gh` auth because the GitHub App token is `contents: read` and cannot merge.";
|
|
43
|
+
/** Remediation for an installed but unauthenticated `gh` (AC-7; see {@link GH_CLI_REMEDIATION}). */
|
|
44
|
+
export const GH_AUTH_REMEDIATION = "run `gh auth login` to grant the conductor merge permission; otherwise local merge emits " +
|
|
45
|
+
"merge.failed/skip rather than merging.";
|
|
46
|
+
/** Detail used when `gh` authentication was never tested because `gh` is absent. */
|
|
47
|
+
export const GH_AUTH_SKIPPED_DETAIL = "not tested — `gh` is not installed, so `gh auth status` was never run";
|
|
48
|
+
/**
|
|
49
|
+
* Remediation for a native ledger whose binding does not load.
|
|
50
|
+
*
|
|
51
|
+
* DIAGNOSTIC ONLY (BAPI-526): this never installs, rebuilds, or resolves a
|
|
52
|
+
* package path, and the remediation names the operator action rather than
|
|
53
|
+
* performing it. Retiring this check is blocked on epic B3 (parallel-ledger
|
|
54
|
+
* paring), so it stays a named check until B3 ships (AC-7).
|
|
55
|
+
*/
|
|
56
|
+
export const NATIVE_LEDGER_REMEDIATION = "`better-sqlite3` is an optionalDependency npm silently skips on a build failure — reinstall it " +
|
|
57
|
+
"for this Node runtime to restore local conductor observability. Diagnostic only: nothing is " +
|
|
58
|
+
"installed or rebuilt for you.";
|
|
59
|
+
/** Remediation for a worker MCP registration that fell back to the npm channel. */
|
|
60
|
+
export const MCP_REGISTRATION_FORM_REMEDIATION = "build this package so an on-disk entry resolves (`npm run build` in mcp_server), or accept that " +
|
|
61
|
+
"worker MCP registration depends on the published package being reachable.";
|
|
62
|
+
/** Remediation for an unreadable `engines.node`. */
|
|
63
|
+
export const NODE_ENGINE_REMEDIATION = "reinstall the package so its package.json is readable; until then the expected Node version is " +
|
|
64
|
+
"unknown and a Node/ABI skew cannot be diagnosed.";
|
|
65
|
+
/** Remediation for a deny-enforcement result the readiness gate could not establish. */
|
|
66
|
+
export const DENY_ENFORCEMENT_SKIP_REMEDIATION = "run `conductor doctor` (without --no-deny-probe) to execute the owning deny-enforcement " +
|
|
67
|
+
"preflight; `conductor readiness` never spawns that probe itself.";
|
|
19
68
|
/** Explicit display value for an unset `BRIDGE_MCP_PROFILE`. */
|
|
20
69
|
export const MCP_PROFILE_UNSET = "(unset)";
|
|
21
70
|
/**
|
|
@@ -105,6 +154,58 @@ export async function inspectNativeLedger(nativeLoad, deps = {}) {
|
|
|
105
154
|
warnings.push("Could not read package.json engines.node; expected Node version is unknown.");
|
|
106
155
|
}
|
|
107
156
|
const degraded = nativeLoad.degraded || invocation.form !== "absolute-build-path" || engines_node === null;
|
|
157
|
+
// THREE outcomes over the SAME inputs (BAPI-1055), because the aggregate
|
|
158
|
+
// `degraded` above folds three independent facts together and each has its own
|
|
159
|
+
// fix. `degraded` itself is unchanged — this adds structure beside it.
|
|
160
|
+
//
|
|
161
|
+
// PATH-FREE by construction: no branch interpolates `mcp_shim_node`, a server
|
|
162
|
+
// entry path, a package root, or a native error message. Only the module name,
|
|
163
|
+
// the ABI, the sanitized failure kind, and the closed registration form.
|
|
164
|
+
const outcomes = [
|
|
165
|
+
binding_loads
|
|
166
|
+
? {
|
|
167
|
+
id: "native-ledger",
|
|
168
|
+
label: "Native ledger binding",
|
|
169
|
+
status: "pass",
|
|
170
|
+
detail: `${nativeLoad.module} loads (NODE_MODULE_VERSION ${nativeLoad.node_modules_abi})`,
|
|
171
|
+
}
|
|
172
|
+
: {
|
|
173
|
+
id: "native-ledger",
|
|
174
|
+
label: "Native ledger binding",
|
|
175
|
+
status: "warn",
|
|
176
|
+
detail: `${nativeLoad.module} did not load ` +
|
|
177
|
+
`(NODE_MODULE_VERSION ${nativeLoad.node_modules_abi}, failure: ${failure_kind})`,
|
|
178
|
+
remediation: NATIVE_LEDGER_REMEDIATION,
|
|
179
|
+
},
|
|
180
|
+
invocation.form === "absolute-build-path"
|
|
181
|
+
? {
|
|
182
|
+
id: "mcp-registration-form",
|
|
183
|
+
label: "Worker MCP registration form",
|
|
184
|
+
status: "pass",
|
|
185
|
+
detail: "resolves an on-disk build entry",
|
|
186
|
+
}
|
|
187
|
+
: {
|
|
188
|
+
id: "mcp-registration-form",
|
|
189
|
+
label: "Worker MCP registration form",
|
|
190
|
+
status: "warn",
|
|
191
|
+
detail: `registration form is \`${invocation.form}\` — the npm-channel fallback`,
|
|
192
|
+
remediation: MCP_REGISTRATION_FORM_REMEDIATION,
|
|
193
|
+
},
|
|
194
|
+
engines_node === null
|
|
195
|
+
? {
|
|
196
|
+
id: "node-engine",
|
|
197
|
+
label: "Expected Node engine",
|
|
198
|
+
status: "warn",
|
|
199
|
+
detail: "package.json engines.node could not be read",
|
|
200
|
+
remediation: NODE_ENGINE_REMEDIATION,
|
|
201
|
+
}
|
|
202
|
+
: {
|
|
203
|
+
id: "node-engine",
|
|
204
|
+
label: "Expected Node engine",
|
|
205
|
+
status: "pass",
|
|
206
|
+
detail: `expected ${engines_node}; running ${process.version}`,
|
|
207
|
+
},
|
|
208
|
+
];
|
|
108
209
|
return {
|
|
109
210
|
cli_node_version: process.version,
|
|
110
211
|
engines_node,
|
|
@@ -116,6 +217,7 @@ export async function inspectNativeLedger(nativeLoad, deps = {}) {
|
|
|
116
217
|
failure_kind,
|
|
117
218
|
degraded,
|
|
118
219
|
warnings,
|
|
220
|
+
outcomes,
|
|
119
221
|
};
|
|
120
222
|
}
|
|
121
223
|
/**
|
|
@@ -175,6 +277,31 @@ export async function collectConductorNativeLedgerSafe(deps = {}) {
|
|
|
175
277
|
failure_kind: NATIVE_LEDGER_UNCOLLECTED_FAILURE_KIND,
|
|
176
278
|
degraded: true,
|
|
177
279
|
warnings: ["the native ledger inspection could not be collected on this host"],
|
|
280
|
+
// The collection failure is its own finding, not a load failure: an
|
|
281
|
+
// operator who cannot collect the inspection needs to retry it, not
|
|
282
|
+
// reinstall the binding. Reported at the same granularity as the real
|
|
283
|
+
// inspector so a consolidated report never loses these three checks.
|
|
284
|
+
outcomes: [
|
|
285
|
+
{
|
|
286
|
+
id: "native-ledger",
|
|
287
|
+
label: "Native ledger binding",
|
|
288
|
+
status: "warn",
|
|
289
|
+
detail: "the native ledger inspection could not be collected on this host",
|
|
290
|
+
remediation: NATIVE_LEDGER_REMEDIATION,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "mcp-registration-form",
|
|
294
|
+
label: "Worker MCP registration form",
|
|
295
|
+
status: "skip",
|
|
296
|
+
detail: "not determined — the native ledger inspection could not be collected",
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
id: "node-engine",
|
|
300
|
+
label: "Expected Node engine",
|
|
301
|
+
status: "skip",
|
|
302
|
+
detail: "not determined — the native ledger inspection could not be collected",
|
|
303
|
+
},
|
|
304
|
+
],
|
|
178
305
|
};
|
|
179
306
|
}
|
|
180
307
|
}
|
|
@@ -206,7 +333,28 @@ export function inspectMcpProfile(env) {
|
|
|
206
333
|
`do NOT replace the value, which would drop the worker's other groups. ` +
|
|
207
334
|
`Without it the conductor/event/supervisor tools are not registered.`);
|
|
208
335
|
}
|
|
209
|
-
|
|
336
|
+
// Fixed state labels and the CLOSED active-group vocabulary only (BAPI-1055):
|
|
337
|
+
// `resolveProfiles` yields nothing but recognized group names, so this detail
|
|
338
|
+
// cannot echo an arbitrary operator-set string the way `raw_profile` can.
|
|
339
|
+
const outcomes = [
|
|
340
|
+
degraded
|
|
341
|
+
? {
|
|
342
|
+
id: "mcp-profile",
|
|
343
|
+
label: "MCP conductor profile",
|
|
344
|
+
status: "warn",
|
|
345
|
+
detail: `a conductor context is active but the resolved groups omit \`conductor\` (active: ${active_groups.join(", ")})`,
|
|
346
|
+
remediation: MCP_PROFILE_REMEDIATION,
|
|
347
|
+
}
|
|
348
|
+
: {
|
|
349
|
+
id: "mcp-profile",
|
|
350
|
+
label: "MCP conductor profile",
|
|
351
|
+
status: "pass",
|
|
352
|
+
detail: conductor_context_detected
|
|
353
|
+
? `conductor context active; resolved groups: ${active_groups.join(", ")}`
|
|
354
|
+
: `no conductor context detected; resolved groups: ${active_groups.join(", ")}`,
|
|
355
|
+
},
|
|
356
|
+
];
|
|
357
|
+
return { raw_profile, active_groups, conductor_context_detected, degraded, warnings, outcomes };
|
|
210
358
|
}
|
|
211
359
|
/**
|
|
212
360
|
* Read-only probe of the local-merge (F4) capability. Runs `gh --version` and
|
|
@@ -265,7 +413,60 @@ export function inspectLocalMerge(runCommand) {
|
|
|
265
413
|
// Capability gap only — local merge is opt-in, so an unauthed host is not a hard
|
|
266
414
|
// failure unless the operator enabled it. Reported as degraded for visibility.
|
|
267
415
|
const degraded = !gh_available || !gh_authed;
|
|
268
|
-
|
|
416
|
+
// TWO outcomes, not one (BAPI-1055): installing `gh` and running `gh auth
|
|
417
|
+
// login` are independently actionable, and `degraded` below is deliberately
|
|
418
|
+
// left as the SAME `!gh_available || !gh_authed` conjunction it has always
|
|
419
|
+
// been. When `gh` is absent the auth outcome is `skip`, never `fail` — the
|
|
420
|
+
// authentication was never tested, and claiming it failed would send the
|
|
421
|
+
// operator to `gh auth login` on a host with no `gh`.
|
|
422
|
+
//
|
|
423
|
+
// AC-7 — these two checks are LOAD-BEARING and must not be retired here.
|
|
424
|
+
// Conductor merges run under this machine's own `gh` auth because the GitHub
|
|
425
|
+
// App token is `contents: read` and structurally cannot merge; see
|
|
426
|
+
// `mcp_server/src/executor/merge-job.ts` and the `contents: read` constraint
|
|
427
|
+
// documented in `mcp_server/src/merge-pull-request.ts`. Retirement is owned by
|
|
428
|
+
// epic A2, after the App is elevated to `contents: write`. Nothing in this
|
|
429
|
+
// change removes, hides, or downgrades either check, and no merge behaviour is
|
|
430
|
+
// altered by reporting them.
|
|
431
|
+
const outcomes = [
|
|
432
|
+
gh_available
|
|
433
|
+
? {
|
|
434
|
+
id: "gh-cli",
|
|
435
|
+
label: "GitHub CLI availability",
|
|
436
|
+
status: "pass",
|
|
437
|
+
detail: "`gh --version` succeeded",
|
|
438
|
+
}
|
|
439
|
+
: {
|
|
440
|
+
id: "gh-cli",
|
|
441
|
+
label: "GitHub CLI availability",
|
|
442
|
+
status: "warn",
|
|
443
|
+
detail: "`gh` is not installed or not on PATH",
|
|
444
|
+
remediation: GH_CLI_REMEDIATION,
|
|
445
|
+
},
|
|
446
|
+
!gh_available
|
|
447
|
+
? {
|
|
448
|
+
id: "gh-auth",
|
|
449
|
+
label: "GitHub CLI authentication",
|
|
450
|
+
status: "skip",
|
|
451
|
+
detail: GH_AUTH_SKIPPED_DETAIL,
|
|
452
|
+
remediation: GH_CLI_REMEDIATION,
|
|
453
|
+
}
|
|
454
|
+
: gh_authed
|
|
455
|
+
? {
|
|
456
|
+
id: "gh-auth",
|
|
457
|
+
label: "GitHub CLI authentication",
|
|
458
|
+
status: "pass",
|
|
459
|
+
detail: "`gh auth status` reports an authenticated account",
|
|
460
|
+
}
|
|
461
|
+
: {
|
|
462
|
+
id: "gh-auth",
|
|
463
|
+
label: "GitHub CLI authentication",
|
|
464
|
+
status: "warn",
|
|
465
|
+
detail: "`gh` is installed but `gh auth status` failed",
|
|
466
|
+
remediation: GH_AUTH_REMEDIATION,
|
|
467
|
+
},
|
|
468
|
+
];
|
|
469
|
+
return { gh_available, gh_authed, degraded, warnings, outcomes };
|
|
269
470
|
}
|
|
270
471
|
/**
|
|
271
472
|
* Build the combined read-only doctor report. Composes the existing ledger
|
|
@@ -312,14 +513,18 @@ async function inspectDenyEnforcementSafe(inspect) {
|
|
|
312
513
|
}
|
|
313
514
|
return await run();
|
|
314
515
|
}
|
|
315
|
-
catch
|
|
316
|
-
|
|
516
|
+
catch {
|
|
517
|
+
// BAPI-1055: the thrown message is NOT interpolated. A spawn failure here
|
|
518
|
+
// routinely carries an absolute executable path or a temp-directory path,
|
|
519
|
+
// and this warning now flows into the consolidated readiness report as well
|
|
520
|
+
// as the doctor's own output. The actionable fact — enforcement could not be
|
|
521
|
+
// established, so claiming must refuse — is fixed prose.
|
|
317
522
|
return {
|
|
318
523
|
enforced: false,
|
|
319
524
|
layer: "none",
|
|
320
525
|
degraded: true,
|
|
321
526
|
warnings: [
|
|
322
|
-
|
|
527
|
+
"Deny-layer enforcement inspection failed: the probe could not be run.",
|
|
323
528
|
"The executor claim loop MUST refuse to claim jobs until the deny layer is enforced.",
|
|
324
529
|
],
|
|
325
530
|
};
|
|
@@ -456,3 +661,221 @@ export function formatConductorDoctorReport(report) {
|
|
|
456
661
|
lines.push(formatClaudeLoginAdvisory(claude_login));
|
|
457
662
|
return lines.join("\n");
|
|
458
663
|
}
|
|
664
|
+
// ---------------------------------------------------------------------------
|
|
665
|
+
// Canonical readiness projection (BAPI-1055)
|
|
666
|
+
// ---------------------------------------------------------------------------
|
|
667
|
+
/**
|
|
668
|
+
* The complete conductor-doctor prerequisite set, in render order.
|
|
669
|
+
*
|
|
670
|
+
* Like the install-side descriptor set, this is STABLE rather than derived from
|
|
671
|
+
* whatever the report happened to contain: a report that failed to build, or an
|
|
672
|
+
* injected sentinel carrying only the fields a test cares about, must still
|
|
673
|
+
* yield every prerequisite — as an explicit unknown, never as an absence.
|
|
674
|
+
*/
|
|
675
|
+
export const CONDUCTOR_READINESS_DESCRIPTORS = [
|
|
676
|
+
{ id: "ledger", label: "Conductor event ledger" },
|
|
677
|
+
{ id: "git-hooks", label: "Conductor git hooks" },
|
|
678
|
+
{ id: "mcp-profile", label: "MCP conductor profile" },
|
|
679
|
+
{ id: "gh-cli", label: "GitHub CLI availability" },
|
|
680
|
+
{ id: "gh-auth", label: "GitHub CLI authentication" },
|
|
681
|
+
{ id: "native-ledger", label: "Native ledger binding" },
|
|
682
|
+
{ id: "mcp-registration-form", label: "Worker MCP registration form" },
|
|
683
|
+
{ id: "node-engine", label: "Expected Node engine" },
|
|
684
|
+
{ id: "deny-enforcement", label: "Deny-layer enforcement" },
|
|
685
|
+
{ id: "claude-login", label: "Claude login" },
|
|
686
|
+
];
|
|
687
|
+
/** Remediation for a degraded local event ledger. */
|
|
688
|
+
export const CONDUCTOR_LEDGER_REMEDIATION = "run `conductor doctor` for the ledger detail, then re-create the local ledger directory with " +
|
|
689
|
+
"owner-only permissions; the ledger is local observability and never blocks a run.";
|
|
690
|
+
/** Remediation for degraded or missing managed git hooks. */
|
|
691
|
+
export const CONDUCTOR_GIT_HOOKS_REMEDIATION = "run `conductor install-git-hooks` to install the managed post-commit and reference-transaction " +
|
|
692
|
+
"hooks; they are local, opportunistic, and bypassable.";
|
|
693
|
+
/** Remediation for a Claude login this host cannot confirm. */
|
|
694
|
+
export const CONDUCTOR_CLAUDE_LOGIN_REMEDIATION = "run `claude login` on the host that will run conductor workers; Bridge stores no Anthropic " +
|
|
695
|
+
"credential and can only advise here.";
|
|
696
|
+
/** Fixed detail for a prerequisite whose owning inspection was not collected. */
|
|
697
|
+
const CONDUCTOR_UNCOLLECTED_DETAIL = "not collected — the conductor doctor did not report this inspection";
|
|
698
|
+
/** Fixed remediation for a prerequisite whose owning inspection was not collected. */
|
|
699
|
+
const CONDUCTOR_UNCOLLECTED_REMEDIATION = "run `conductor doctor` directly to collect this inspection; its state is unknown, not healthy.";
|
|
700
|
+
/** Ordered outcomes an inspection contributed, or `[]` when it reported none. */
|
|
701
|
+
function outcomesOf(inspection) {
|
|
702
|
+
// Read defensively: an injected sentinel (and the installer's degraded-doctor
|
|
703
|
+
// fallback) may populate only the fields a test cares about, and an adapter
|
|
704
|
+
// that threw on a partially-built report would lose the whole locus.
|
|
705
|
+
return Array.isArray(inspection?.outcomes) ? inspection?.outcomes : [];
|
|
706
|
+
}
|
|
707
|
+
/** Derive the ledger outcome from the ledger doctor result's own `degraded` flag. */
|
|
708
|
+
function ledgerOutcome(report) {
|
|
709
|
+
const ledger = report?.ledger;
|
|
710
|
+
if (ledger === undefined)
|
|
711
|
+
return null;
|
|
712
|
+
// Fixed facts only: `DoctorResult.path` and `directory_path` are absolute and
|
|
713
|
+
// are deliberately never projected.
|
|
714
|
+
return ledger.degraded
|
|
715
|
+
? {
|
|
716
|
+
id: "ledger",
|
|
717
|
+
label: "Conductor event ledger",
|
|
718
|
+
status: "warn",
|
|
719
|
+
detail: `local ledger degraded (schema present: ${ledger.schema_present}, permissions ok: ${ledger.directory_permissions_ok && ledger.database_permissions_ok})`,
|
|
720
|
+
remediation: CONDUCTOR_LEDGER_REMEDIATION,
|
|
721
|
+
}
|
|
722
|
+
: {
|
|
723
|
+
id: "ledger",
|
|
724
|
+
label: "Conductor event ledger",
|
|
725
|
+
status: "pass",
|
|
726
|
+
detail: `local ledger healthy (schema version ${ledger.user_version ?? "unknown"})`,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/** Derive the git-hooks outcome from the hook inspection's own `degraded` flag. */
|
|
730
|
+
function gitHooksOutcome(report) {
|
|
731
|
+
const hooks = report?.git_hooks;
|
|
732
|
+
if (hooks === undefined)
|
|
733
|
+
return null;
|
|
734
|
+
if (!hooks.is_worktree) {
|
|
735
|
+
return {
|
|
736
|
+
id: "git-hooks",
|
|
737
|
+
label: "Conductor git hooks",
|
|
738
|
+
status: "skip",
|
|
739
|
+
detail: "not a git worktree, so no managed conductor hooks apply",
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
// Hook NAMES and booleans only — `hooks_dir` is absolute and never projected.
|
|
743
|
+
const rendered = hooks.hooks
|
|
744
|
+
.map((hook) => `${hook.name}=${hook.exists && hook.managed_block_present}`)
|
|
745
|
+
.join(", ");
|
|
746
|
+
return hooks.degraded
|
|
747
|
+
? {
|
|
748
|
+
id: "git-hooks",
|
|
749
|
+
label: "Conductor git hooks",
|
|
750
|
+
status: "warn",
|
|
751
|
+
detail: `managed hooks installed: ${rendered}`,
|
|
752
|
+
remediation: CONDUCTOR_GIT_HOOKS_REMEDIATION,
|
|
753
|
+
}
|
|
754
|
+
: {
|
|
755
|
+
id: "git-hooks",
|
|
756
|
+
label: "Conductor git hooks",
|
|
757
|
+
status: "pass",
|
|
758
|
+
detail: `managed hooks installed: ${rendered}`,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Derive the deny-enforcement outcome.
|
|
763
|
+
*
|
|
764
|
+
* `conductor readiness` NEVER runs the owning probe: `runDenyEnforcementPreflight`
|
|
765
|
+
* spawns real headless agent runs costing minutes, and an advisory report is not
|
|
766
|
+
* a reason to pay that. When the report carries an explicitly skipped result the
|
|
767
|
+
* outcome is `skip` with a remediation naming the command that DOES run it —
|
|
768
|
+
* never `pass`, which would claim enforcement that was never verified.
|
|
769
|
+
*/
|
|
770
|
+
function denyEnforcementOutcome(report) {
|
|
771
|
+
const deny = report?.deny_enforcement;
|
|
772
|
+
if (deny === undefined)
|
|
773
|
+
return null;
|
|
774
|
+
if (deny.layer === "none" && !deny.enforced && deny.status === "skip") {
|
|
775
|
+
return {
|
|
776
|
+
id: "deny-enforcement",
|
|
777
|
+
label: "Deny-layer enforcement",
|
|
778
|
+
status: "skip",
|
|
779
|
+
detail: "not probed — enforcement is UNVERIFIED, which is not the same as unenforced",
|
|
780
|
+
remediation: DENY_ENFORCEMENT_SKIP_REMEDIATION,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
// Fixed categories only: the underlying `detail`/`warnings` can carry probe
|
|
784
|
+
// text, so neither is projected here.
|
|
785
|
+
return deny.enforced && !deny.degraded
|
|
786
|
+
? {
|
|
787
|
+
id: "deny-enforcement",
|
|
788
|
+
label: "Deny-layer enforcement",
|
|
789
|
+
status: "pass",
|
|
790
|
+
detail: `enforced by the ${deny.layer} layer`,
|
|
791
|
+
}
|
|
792
|
+
: {
|
|
793
|
+
id: "deny-enforcement",
|
|
794
|
+
label: "Deny-layer enforcement",
|
|
795
|
+
status: "fail",
|
|
796
|
+
detail: deny.enforced
|
|
797
|
+
? `enforced by the ${deny.layer} layer, but degraded`
|
|
798
|
+
: "the denied call was not rejected by any layer",
|
|
799
|
+
remediation: "restore the settings-deny layer before running a conductor worker; the executor claim " +
|
|
800
|
+
"loop refuses to claim jobs while enforcement is missing.",
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/** Derive the Claude-login outcome from the advisory detection result. */
|
|
804
|
+
function claudeLoginOutcome(report) {
|
|
805
|
+
const login = report?.claude_login;
|
|
806
|
+
if (login === undefined)
|
|
807
|
+
return null;
|
|
808
|
+
return login.detected
|
|
809
|
+
? {
|
|
810
|
+
id: "claude-login",
|
|
811
|
+
label: "Claude login",
|
|
812
|
+
status: "pass",
|
|
813
|
+
detail: "a local Claude login marker was detected",
|
|
814
|
+
}
|
|
815
|
+
: {
|
|
816
|
+
id: "claude-login",
|
|
817
|
+
label: "Claude login",
|
|
818
|
+
status: "warn",
|
|
819
|
+
detail: "no local Claude login marker was detected",
|
|
820
|
+
remediation: CONDUCTOR_CLAUDE_LOGIN_REMEDIATION,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Project a conductor-doctor report into canonical readiness checks.
|
|
825
|
+
*
|
|
826
|
+
* PURE: no probe, no I/O, and no re-derivation of any verdict. Every outcome is
|
|
827
|
+
* read from the structured fields the owning inspections now populate, never
|
|
828
|
+
* parsed out of their free-text `warnings` — a warning is prose an author may
|
|
829
|
+
* reword, and a gate that read it would change meaning on a copy edit.
|
|
830
|
+
*
|
|
831
|
+
* A null report yields the full descriptor set as failures rather than nothing:
|
|
832
|
+
* "the conductor doctor could not be built" must not read as "there is nothing
|
|
833
|
+
* to report here".
|
|
834
|
+
*/
|
|
835
|
+
export function mapConductorDoctorReportToReadinessChecks(report) {
|
|
836
|
+
const collected = new Map();
|
|
837
|
+
const record = (outcome) => {
|
|
838
|
+
// First writer wins, so an inspection cannot overwrite another's finding.
|
|
839
|
+
if (outcome && !collected.has(outcome.id))
|
|
840
|
+
collected.set(outcome.id, outcome);
|
|
841
|
+
};
|
|
842
|
+
record(ledgerOutcome(report));
|
|
843
|
+
record(gitHooksOutcome(report));
|
|
844
|
+
for (const outcome of outcomesOf(report?.mcp_profile))
|
|
845
|
+
record(outcome);
|
|
846
|
+
for (const outcome of outcomesOf(report?.local_merge))
|
|
847
|
+
record(outcome);
|
|
848
|
+
for (const outcome of outcomesOf(report?.native_ledger))
|
|
849
|
+
record(outcome);
|
|
850
|
+
record(denyEnforcementOutcome(report));
|
|
851
|
+
record(claudeLoginOutcome(report));
|
|
852
|
+
return CONDUCTOR_READINESS_DESCRIPTORS.map(({ id, label }) => {
|
|
853
|
+
const outcome = collected.get(id);
|
|
854
|
+
if (!outcome) {
|
|
855
|
+
// Unknown, and unknown is never healthy: a prerequisite whose owning
|
|
856
|
+
// inspection did not report is a failure with a restoring remediation.
|
|
857
|
+
return createReadinessCheck({
|
|
858
|
+
id: `conductor.${id}`,
|
|
859
|
+
source: "conductor",
|
|
860
|
+
label,
|
|
861
|
+
status: "fail",
|
|
862
|
+
detail: CONDUCTOR_UNCOLLECTED_DETAIL,
|
|
863
|
+
remediation: CONDUCTOR_UNCOLLECTED_REMEDIATION,
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
return createReadinessCheckSafely({
|
|
867
|
+
id: `conductor.${id}`,
|
|
868
|
+
source: "conductor",
|
|
869
|
+
label: outcome.label || label,
|
|
870
|
+
status: outcome.status,
|
|
871
|
+
...(outcome.detail ? { detail: outcome.detail } : {}),
|
|
872
|
+
// Same fallback as the plane adapter: a real failure must never be
|
|
873
|
+
// downgraded into a generic "unreadable check" for want of a fix line.
|
|
874
|
+
...(outcome.status === "fail"
|
|
875
|
+
? { remediation: outcome.remediation ?? CONDUCTOR_UNCOLLECTED_REMEDIATION }
|
|
876
|
+
: outcome.status !== "pass" && outcome.remediation
|
|
877
|
+
? { remediation: outcome.remediation }
|
|
878
|
+
: {}),
|
|
879
|
+
});
|
|
880
|
+
});
|
|
881
|
+
}
|