@bridge_gpt/mcp-server 0.2.65 → 0.2.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +25 -13
  2. package/build/agent-launchers/claude-executor-adapter.js +12 -18
  3. package/build/agent-launchers/executor-adapter.js +27 -0
  4. package/build/bridge-config.js +104 -0
  5. package/build/codex-bridge-profile.js +10 -1
  6. package/build/commands.generated.js +4 -4
  7. package/build/conductor/bridge-api-client.js +230 -10
  8. package/build/conductor/epic-child-continuation.js +61 -34
  9. package/build/conductor/epic-implementer-cli.js +12 -0
  10. package/build/conductor/guided-repair.js +323 -0
  11. package/build/conductor/readiness-cli.js +30 -9
  12. package/build/conductor/readiness.js +23 -1
  13. package/build/conductor/recovery-cli.js +507 -16
  14. package/build/conductor/recovery-operations.js +325 -2
  15. package/build/conductor/repair-contract.js +61 -3
  16. package/build/credential-store.js +5 -4
  17. package/build/diagnostic-text.js +36 -0
  18. package/build/docs.generated.js +1 -1
  19. package/build/doctor.js +70 -10
  20. package/build/drive-epic.js +395 -78
  21. package/build/epic-implementer-bin.js +24 -22
  22. package/build/epic-launch-diagnostic.js +31 -0
  23. package/build/epic-launch-profile.js +44 -14
  24. package/build/executor/completion-supervision.js +5 -0
  25. package/build/executor/correction-dispatch.js +60 -0
  26. package/build/executor/env.js +6 -0
  27. package/build/executor/http-client.js +15 -0
  28. package/build/executor/integration-job.js +6 -1
  29. package/build/executor/job-errors.js +13 -4
  30. package/build/executor/job-log-registry.js +340 -73
  31. package/build/executor/job-runner.js +260 -33
  32. package/build/executor/job-types.js +8 -13
  33. package/build/executor/preflight.js +91 -2
  34. package/build/executor/prompt-spec.js +113 -9
  35. package/build/executor/runner.js +23 -0
  36. package/build/executor/stale-artifacts.js +5 -4
  37. package/build/executor/types.js +1 -0
  38. package/build/executor/watch-cli.js +35 -8
  39. package/build/executor/worker-command.js +43 -43
  40. package/build/executor/worker-finalization.js +80 -15
  41. package/build/executor/worker-guard-hook.js +46 -10
  42. package/build/executor/worker-log.js +137 -70
  43. package/build/executor/worktree-gc.js +30 -6
  44. package/build/index.js +177 -329
  45. package/build/init.js +107 -4
  46. package/build/install-bridge.js +284 -20
  47. package/build/install-doctor.js +172 -30
  48. package/build/install-reexec.js +69 -7
  49. package/build/macos-terminal-permission-guidance.js +16 -0
  50. package/build/model-thresholds-tool.js +185 -0
  51. package/build/pipelines.generated.js +1 -1
  52. package/build/plane/status.js +4 -0
  53. package/build/plane/supervisor.js +4 -0
  54. package/build/plane/worker-exit-code.js +2 -0
  55. package/build/project-root.js +99 -0
  56. package/build/readme.generated.js +1 -1
  57. package/build/review-tickets.js +156 -13
  58. package/build/setup-epic.js +180 -108
  59. package/build/sfcc/setup-status.js +2 -0
  60. package/build/sfcc/tool-wrapper.js +2 -0
  61. package/build/sfcc/tools-configured.js +96 -0
  62. package/build/start-tickets.js +163 -58
  63. package/build/upgrade-cli.js +109 -24
  64. package/build/version.generated.js +3 -3
  65. package/build/windows-npm-entry.js +103 -0
  66. package/build/worker-guard-hook-bin.js +4 -3
  67. package/build/worktree-core.js +6 -5
  68. package/docs/install/mcp-tool-integrations.md +1 -0
  69. package/docs/install/sfcc-integration.md +12 -7
  70. package/package.json +1 -1
  71. package/pipelines/implement-ticket.json +1 -1
@@ -0,0 +1,323 @@
1
+ /**
2
+ * BAPI-1333 — guided operator repairs: the displayed binding and its local
3
+ * recovery reference.
4
+ *
5
+ * ExplainRun renders a guided command with one `--binding v1.<base64url>` token.
6
+ * The token names the verb, the run, and the bounded subject the command was
7
+ * rendered against (a run repair revision plus its control, block, or policy
8
+ * subject; a scope episode; or a ticket version and head). This module:
9
+ *
10
+ * - decodes and strictly validates that token (identifiers only, closed keys);
11
+ * - turns it into the typed `guard` the server enforces in its guarded write;
12
+ * - persists a small local reference BEFORE the first POST — the request id, the
13
+ * binding, and digests of the binding and the payload — so a transport retry
14
+ * or a restarted CLI process re-sends the SAME request instead of re-reading
15
+ * the run and approving whatever subject exists now.
16
+ *
17
+ * The stored reference never holds a payload value: a policy patch or a driver
18
+ * context is represented only by its SHA-256 digest. Nothing here performs HTTP.
19
+ */
20
+ import { createHash, randomUUID } from "node:crypto";
21
+ import { canonicalSha256, clearRecoveryReferenceFile, readRecoveryReferenceFile, writeRecoveryReferenceFile, } from "./epic-child-continuation.js";
22
+ export const GUIDED_BINDING_TOKEN_PREFIX = "v1.";
23
+ export const GUIDED_REPAIR_REFERENCE_NAMESPACE = "epic-guided-repairs";
24
+ /** The fixed message when the reference cannot be stored; nothing is sent. */
25
+ export const GUIDED_REFERENCE_STORE_FAILED_MESSAGE = "could not store the local recovery reference, so the request was not sent";
26
+ /** A rerun of the same displayed command with a DIFFERENT payload. */
27
+ export const GUIDED_PAYLOAD_CONFLICT_MESSAGE = "a different request was already issued for this displayed command; nothing " +
28
+ "was sent. Re-read the run explanation before choosing another action";
29
+ export const GUIDED_REPAIR_VERBS = [
30
+ "resume-run",
31
+ "patch-policy",
32
+ "scope-reparse",
33
+ "scope-catch-up",
34
+ "dispatch-remediation",
35
+ ];
36
+ /** A malformed or mismatched binding. The message never echoes the token. */
37
+ export class GuidedBindingError extends Error {
38
+ constructor(message) {
39
+ super(message);
40
+ this.name = "GuidedBindingError";
41
+ }
42
+ }
43
+ const TOKEN_RE = /^[a-z][a-z0-9_]{0,63}$/;
44
+ const SHA_RE = /^[0-9a-f]{40}$/;
45
+ const REQUEST_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
46
+ const FINGERPRINT_RE = /^[0-9a-f]{64}$/;
47
+ /** Keys each verb's token may carry, beyond `v`, `verb`, and `epic_run_id`. */
48
+ const ALLOWED_KEYS = {
49
+ "resume-run": [
50
+ "repair_revision",
51
+ "subject",
52
+ "control_state",
53
+ "control_generation",
54
+ "block_reason",
55
+ ],
56
+ "patch-policy": ["repair_revision", "subject"],
57
+ "scope-reparse": ["scope_id", "lifecycle_state", "required_commit_sha"],
58
+ "scope-catch-up": ["scope_id", "lifecycle_state", "required_commit_sha"],
59
+ "dispatch-remediation": ["ticket_key", "expected_row_version", "expected_head_sha"],
60
+ };
61
+ function invalid() {
62
+ return new GuidedBindingError("--binding is not a valid guided-repair binding; nothing was sent. Copy the " +
63
+ "command exactly as the run explanation rendered it.");
64
+ }
65
+ function nonNegativeInt(value) {
66
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
67
+ throw invalid();
68
+ return value;
69
+ }
70
+ function boundedString(value, max) {
71
+ if (typeof value !== "string" || value.length === 0 || value.length > max)
72
+ throw invalid();
73
+ return value;
74
+ }
75
+ function optionalSha(value) {
76
+ if (value === undefined || value === null)
77
+ return null;
78
+ if (typeof value !== "string" || !SHA_RE.test(value))
79
+ throw invalid();
80
+ return value;
81
+ }
82
+ function decodeBase64Url(text) {
83
+ if (!/^[A-Za-z0-9_-]+$/.test(text) || text.length > 2048)
84
+ throw invalid();
85
+ return Buffer.from(text, "base64url").toString("utf8");
86
+ }
87
+ /** Decode and strictly validate a `--binding` token. Throws {@link GuidedBindingError}. */
88
+ export function decodeGuidedBinding(token) {
89
+ if (typeof token !== "string" || !token.startsWith(GUIDED_BINDING_TOKEN_PREFIX)) {
90
+ throw invalid();
91
+ }
92
+ let parsed;
93
+ try {
94
+ parsed = JSON.parse(decodeBase64Url(token.slice(GUIDED_BINDING_TOKEN_PREFIX.length)));
95
+ }
96
+ catch (err) {
97
+ if (err instanceof GuidedBindingError)
98
+ throw err;
99
+ throw invalid();
100
+ }
101
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
102
+ throw invalid();
103
+ const raw = parsed;
104
+ if (raw.v !== 1)
105
+ throw invalid();
106
+ const verb = raw.verb;
107
+ if (typeof verb !== "string" || !GUIDED_REPAIR_VERBS.includes(verb)) {
108
+ throw invalid();
109
+ }
110
+ const guidedVerb = verb;
111
+ const allowed = new Set(["v", "verb", "epic_run_id", ...ALLOWED_KEYS[guidedVerb]]);
112
+ for (const key of Object.keys(raw)) {
113
+ if (!allowed.has(key))
114
+ throw invalid();
115
+ }
116
+ const binding = {
117
+ verb: guidedVerb,
118
+ epicRunId: boundedString(raw.epic_run_id, 200),
119
+ };
120
+ if (guidedVerb === "resume-run" || guidedVerb === "patch-policy") {
121
+ binding.repairRevision = nonNegativeInt(raw.repair_revision);
122
+ const subject = raw.subject;
123
+ if (guidedVerb === "patch-policy") {
124
+ if (subject !== "policy")
125
+ throw invalid();
126
+ binding.subject = "policy";
127
+ }
128
+ else if (subject === "control") {
129
+ if (raw.control_state !== "paused" && raw.control_state !== "quarantined")
130
+ throw invalid();
131
+ const generation = nonNegativeInt(raw.control_generation);
132
+ if (generation < 1 || raw.block_reason !== undefined)
133
+ throw invalid();
134
+ binding.subject = "control";
135
+ binding.controlState = raw.control_state;
136
+ binding.controlGeneration = generation;
137
+ }
138
+ else if (subject === "integration_block") {
139
+ const reason = raw.block_reason;
140
+ if (typeof reason !== "string" || !TOKEN_RE.test(reason))
141
+ throw invalid();
142
+ if (raw.control_state !== undefined || raw.control_generation !== undefined)
143
+ throw invalid();
144
+ binding.subject = "integration_block";
145
+ binding.blockReason = reason;
146
+ }
147
+ else {
148
+ throw invalid();
149
+ }
150
+ }
151
+ else if (guidedVerb === "dispatch-remediation") {
152
+ binding.ticketKey = boundedString(raw.ticket_key, 64);
153
+ binding.expectedRowVersion = nonNegativeInt(raw.expected_row_version);
154
+ binding.expectedHeadSha = optionalSha(raw.expected_head_sha);
155
+ }
156
+ else {
157
+ binding.scopeId = boundedString(raw.scope_id, 200);
158
+ const lifecycle = raw.lifecycle_state;
159
+ if (typeof lifecycle !== "string" || !TOKEN_RE.test(lifecycle))
160
+ throw invalid();
161
+ binding.lifecycleState = lifecycle;
162
+ binding.requiredCommitSha = optionalSha(raw.required_commit_sha);
163
+ }
164
+ return binding;
165
+ }
166
+ /** The canonical, snake_case identity of a binding (what its digest covers). */
167
+ export function bindingIdentity(binding) {
168
+ const out = { verb: binding.verb, epic_run_id: binding.epicRunId };
169
+ const pairs = [
170
+ ["repair_revision", binding.repairRevision],
171
+ ["subject", binding.subject],
172
+ ["control_state", binding.controlState],
173
+ ["control_generation", binding.controlGeneration],
174
+ ["block_reason", binding.blockReason],
175
+ ["scope_id", binding.scopeId],
176
+ ["lifecycle_state", binding.lifecycleState],
177
+ ["required_commit_sha", binding.requiredCommitSha],
178
+ ["ticket_key", binding.ticketKey],
179
+ ["expected_row_version", binding.expectedRowVersion],
180
+ ["expected_head_sha", binding.expectedHeadSha],
181
+ ];
182
+ for (const [key, value] of pairs) {
183
+ if (value !== undefined)
184
+ out[key] = value;
185
+ }
186
+ return out;
187
+ }
188
+ /** The typed body `guard` for this binding and request id. */
189
+ export function guardForBinding(binding, requestId) {
190
+ if (binding.verb === "resume-run" || binding.verb === "patch-policy") {
191
+ const guard = {
192
+ request_id: requestId,
193
+ epic_run_id: binding.epicRunId,
194
+ repair_revision: binding.repairRevision ?? 0,
195
+ subject: binding.subject ?? "policy",
196
+ };
197
+ if (binding.controlState !== undefined)
198
+ guard.control_state = binding.controlState;
199
+ if (binding.controlGeneration !== undefined)
200
+ guard.control_generation = binding.controlGeneration;
201
+ if (binding.blockReason !== undefined)
202
+ guard.block_reason = binding.blockReason;
203
+ return guard;
204
+ }
205
+ if (binding.verb === "dispatch-remediation") {
206
+ return {
207
+ request_id: requestId,
208
+ epic_run_id: binding.epicRunId,
209
+ expected_row_version: binding.expectedRowVersion ?? 0,
210
+ expected_head_sha: binding.expectedHeadSha ?? null,
211
+ };
212
+ }
213
+ return {
214
+ request_id: requestId,
215
+ epic_run_id: binding.epicRunId,
216
+ lifecycle_state: binding.lifecycleState ?? "",
217
+ required_commit_sha: binding.requiredCommitSha ?? null,
218
+ };
219
+ }
220
+ function stableStringify(value) {
221
+ if (Array.isArray(value))
222
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
223
+ if (value !== null && typeof value === "object") {
224
+ const record = value;
225
+ return `{${Object.keys(record)
226
+ .sort()
227
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
228
+ .join(",")}}`;
229
+ }
230
+ return JSON.stringify(value) ?? "null";
231
+ }
232
+ /**
233
+ * SHA-256 of a request payload in canonical form (sorted keys; any JSON number).
234
+ * Only this digest is persisted, so a patch or context never reaches the disk.
235
+ */
236
+ export function guidedPayloadDigest(payload) {
237
+ return createHash("sha256").update(stableStringify(payload), "utf8").digest("hex");
238
+ }
239
+ function referenceTarget(binding) {
240
+ return binding.ticketKey ?? binding.scopeId ?? "run";
241
+ }
242
+ function referenceKey(binding) {
243
+ return { epicRunId: binding.epicRunId, ticketKey: `${binding.verb}:${referenceTarget(binding)}` };
244
+ }
245
+ function referenceFingerprint(ref) {
246
+ return canonicalSha256({
247
+ v: 1,
248
+ verb: ref.verb,
249
+ epic_run_id: ref.epicRunId,
250
+ target: ref.target,
251
+ request_id: ref.requestId,
252
+ binding_digest: ref.bindingDigest,
253
+ payload_digest: ref.payloadDigest,
254
+ });
255
+ }
256
+ function parseReference(raw) {
257
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
258
+ return null;
259
+ const ref = raw;
260
+ if (ref.version !== 1)
261
+ return null;
262
+ if (typeof ref.verb !== "string" || !GUIDED_REPAIR_VERBS.includes(ref.verb)) {
263
+ return null;
264
+ }
265
+ for (const key of ["epicRunId", "target", "createdAt"]) {
266
+ if (typeof ref[key] !== "string" || ref[key].length === 0)
267
+ return null;
268
+ }
269
+ if (typeof ref.requestId !== "string" || !REQUEST_ID_RE.test(ref.requestId))
270
+ return null;
271
+ for (const key of ["bindingDigest", "payloadDigest", "fingerprint"]) {
272
+ if (typeof ref[key] !== "string" || !FINGERPRINT_RE.test(ref[key]))
273
+ return null;
274
+ }
275
+ const parsed = raw;
276
+ // A reference whose fingerprint no longer matches its own identity was
277
+ // tampered with or truncated; it never authorizes a re-send.
278
+ return referenceFingerprint(parsed) === parsed.fingerprint ? parsed : null;
279
+ }
280
+ /**
281
+ * Reuse the persisted request for this exact binding and payload, or mint and
282
+ * durably persist a new one BEFORE anything is sent.
283
+ *
284
+ * - Same binding, same payload → the SAME request id (transport retry, restart).
285
+ * - Same binding, different payload → refused locally; nothing is sent.
286
+ * - Different binding (a newer rendered command) → a new request replaces it.
287
+ */
288
+ export async function prepareGuidedRequest(binding, payloadDigest, deps = {}) {
289
+ const key = referenceKey(binding);
290
+ const bindingDigest = canonicalSha256(bindingIdentity(binding));
291
+ const existing = parseReference(await readRecoveryReferenceFile(GUIDED_REPAIR_REFERENCE_NAMESPACE, key, deps.store));
292
+ if (existing !== null && existing.bindingDigest === bindingDigest) {
293
+ if (existing.payloadDigest !== payloadDigest)
294
+ return { kind: "payload_conflict" };
295
+ return { kind: "ready", requestId: existing.requestId, referenceReused: true };
296
+ }
297
+ const requestId = (deps.newRequestId ?? randomUUID)();
298
+ const base = {
299
+ version: 1,
300
+ verb: binding.verb,
301
+ epicRunId: binding.epicRunId,
302
+ target: referenceTarget(binding),
303
+ requestId,
304
+ bindingDigest,
305
+ payloadDigest,
306
+ };
307
+ const reference = {
308
+ ...base,
309
+ createdAt: (deps.now ?? (() => new Date()))().toISOString(),
310
+ fingerprint: referenceFingerprint(base),
311
+ };
312
+ try {
313
+ await writeRecoveryReferenceFile(GUIDED_REPAIR_REFERENCE_NAMESPACE, key, reference, deps.store);
314
+ }
315
+ catch {
316
+ return { kind: "store_failed" };
317
+ }
318
+ return { kind: "ready", requestId, referenceReused: false };
319
+ }
320
+ /** Remove the reference once the server returned a definitive answer. Never throws. */
321
+ export async function clearGuidedReference(binding, deps = {}) {
322
+ await clearRecoveryReferenceFile(GUIDED_REPAIR_REFERENCE_NAMESPACE, referenceKey(binding), deps.store);
323
+ }
@@ -35,7 +35,8 @@
35
35
  import os from "node:os";
36
36
  import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
37
37
  import { collectConductorReadinessGate, formatConductorReadinessGateReport, } from "./readiness.js";
38
- import { resolveConductorBridgeApiAccess } from "./bridge-api-client.js";
38
+ import { readinessEndpointEnvironments, resolveConductorBridgeApiAccess, resolveEpicLaunchBridgeApiAccess } from "./bridge-api-client.js";
39
+ import { validateHttpBaseUrl } from "../base-url.js";
39
40
  import { claudeReviewWorkflowPath } from "../claude-review-workflow.js";
40
41
  import { runDefaultPlanePreflight } from "../plane/cli.js";
41
42
  /** Fixed notice emitted to stderr when a source could not be collected. */
@@ -43,7 +44,7 @@ export const READINESS_COLLECTION_NOTICE = "one or more readiness sources could
43
44
  /** Usage text for `epic-implementer readiness`. */
44
45
  export function getReadinessUsage() {
45
46
  return [
46
- "Usage: epic-implementer readiness [--json] [--no-deny-probe]",
47
+ "Usage: epic-implementer readiness [--json] [--no-deny-probe] [--base-url <url>]",
47
48
  "",
48
49
  "Read-only ADVISORY report of every conductor prerequisite, consolidated from the",
49
50
  "Bridge install checklist, the epic-implementer doctor, the plane preflight, and the",
@@ -58,19 +59,31 @@ export function getReadinessUsage() {
58
59
  " --json Print the versioned structured report as JSON",
59
60
  " --no-deny-probe Accepted for parity with `epic-implementer doctor`; inert here, because",
60
61
  " this command never spawns the deny-enforcement probe",
62
+ " --base-url <url> Select a read-only Bridge API endpoint for this report;",
63
+ " this does not authorize or supply a credential",
61
64
  " --help Print this usage message",
62
65
  ].join("\n");
63
66
  }
64
67
  /** Parse `epic-implementer readiness` argv. Unknown flags and positionals are errors. */
65
68
  export function parseReadinessArgs(argv) {
66
69
  const parsed = { json: false, help: false, noDenyProbe: false };
67
- for (const token of argv) {
70
+ for (let index = 0; index < argv.length; index++) {
71
+ const token = argv[index];
68
72
  if (token === "--json")
69
73
  parsed.json = true;
70
74
  else if (token === "--no-deny-probe")
71
75
  parsed.noDenyProbe = true;
72
76
  else if (token === "--help" || token === "-h")
73
77
  parsed.help = true;
78
+ else if (token === "--base-url") {
79
+ const raw = argv[++index];
80
+ if (!raw || raw.startsWith("--"))
81
+ return { error: "--base-url requires an HTTP(S) URL." };
82
+ const valid = validateHttpBaseUrl(raw);
83
+ if (!valid.ok)
84
+ return { error: "--base-url requires a valid HTTP(S) URL." };
85
+ parsed.baseUrl = valid.baseUrl;
86
+ }
74
87
  else
75
88
  return { error: `Unknown argument "${token}". Run "epic-implementer readiness --help" for usage.` };
76
89
  }
@@ -83,8 +96,16 @@ export function parseReadinessArgs(argv) {
83
96
  * epic-implementer doctor's local inspections, and the same plane preflight
84
97
  * `plane up` and `drive-epic` run. Nothing is re-implemented here.
85
98
  */
86
- async function collectProductionReport(repoRoot, env) {
87
- const accessResult = await resolveConductorBridgeApiAccess({ env, cwd: repoRoot });
99
+ async function collectProductionReport(repoRoot, env, baseUrl) {
100
+ const { reportEnv, resolverEnv } = readinessEndpointEnvironments(env, baseUrl);
101
+ const accessResult = baseUrl === undefined
102
+ ? await resolveConductorBridgeApiAccess({ env: reportEnv, cwd: repoRoot })
103
+ : await resolveEpicLaunchBridgeApiAccess({ baseUrl }, { env: resolverEnv, cwd: repoRoot });
104
+ if (baseUrl !== undefined && !accessResult.ok) {
105
+ // A refused override is never permission for a downstream doctor collector
106
+ // to resolve credentials afresh from the general CLI environment.
107
+ return collectConductorReadinessGate({ repoName: null });
108
+ }
88
109
  const access = accessResult.ok ? accessResult.access : null;
89
110
  return collectConductorReadinessGate({
90
111
  // Never guessed: an unresolved identity is reported as such rather than
@@ -101,7 +122,7 @@ async function collectProductionReport(repoRoot, env) {
101
122
  readWorkflowFile: () => fsReadFile(claudeReviewWorkflowPath(repoRoot), "utf-8"),
102
123
  projectRoot: repoRoot,
103
124
  installDoctorDeps: {
104
- env,
125
+ env: reportEnv,
105
126
  cwd: repoRoot,
106
127
  platform: process.platform,
107
128
  homedir: os.homedir,
@@ -112,8 +133,8 @@ async function collectProductionReport(repoRoot, env) {
112
133
  },
113
134
  // The deny-enforcement probe is never spawned from this command; the check
114
135
  // is reported as an explicit skip naming `epic-implementer doctor` instead.
115
- conductorDoctorDeps: { env, skipDenyProbe: true },
116
- runPlanePreflight: () => runDefaultPlanePreflight(repoRoot, env),
136
+ conductorDoctorDeps: { env: reportEnv, skipDenyProbe: true },
137
+ runPlanePreflight: () => runDefaultPlanePreflight(repoRoot, reportEnv),
117
138
  });
118
139
  }
119
140
  /**
@@ -138,7 +159,7 @@ export async function runConductorReadinessCommand(argv, deps = {}) {
138
159
  }
139
160
  const cwd = deps.cwd ?? process.cwd();
140
161
  const env = deps.env ?? process.env;
141
- const collect = deps.collect ?? ((repoRoot) => collectProductionReport(repoRoot, env));
162
+ const collect = deps.collect ?? ((repoRoot) => collectProductionReport(repoRoot, env, parsed.baseUrl));
142
163
  const report = await collect(cwd);
143
164
  // Fixed, sanitized, and stderr-only: never a credential, exception, header,
144
165
  // body, subprocess output, or absolute path. It fires on a COLLECTION gap,
@@ -338,7 +338,28 @@ export function mapServerReadinessToChecks(readiness, failure = "unreachable") {
338
338
  });
339
339
  // BAPI-1260 — appended ONLY when the server reported the block, so every
340
340
  // pre-BAPI-1260 report keeps exactly the descriptor set above.
341
- return [...described, ...admissionEvidenceChecks(readiness)];
341
+ return [...described, ...admissionEvidenceChecks(readiness), ...genericDiagnosticChecks(readiness)];
342
+ }
343
+ function genericDiagnosticChecks(readiness) {
344
+ return (readiness.diagnostics ?? []).map((diagnostic, index) => {
345
+ const inapplicable = diagnostic.applicability === "inapplicable";
346
+ const declared = diagnostic.severity !== null && diagnostic.applicability !== null;
347
+ const satisfied = declared && diagnostic.verdict === "passed" && diagnostic.applicability === "applicable";
348
+ const advisory = diagnostic.severity === "advisory";
349
+ const status = !declared ? "fail" : inapplicable ? "skip" : satisfied ? "pass" : advisory ? "warn" : "fail";
350
+ return createReadinessCheckSafely({
351
+ id: `server.diagnostic-${index + 1}-${diagnostic.id.replace(/[_.]/g, "-")}`,
352
+ source: "server",
353
+ label: `Server diagnostic ${diagnostic.id}`,
354
+ status,
355
+ detail: `severity ${diagnostic.severity ?? "unknown"}; applicability ${diagnostic.applicability ?? "unknown"}; verdict ${diagnostic.verdict}`,
356
+ ...(status === "pass" ? {} : {
357
+ remediation: status === "skip"
358
+ ? "This diagnostic does not apply to the selected topology."
359
+ : "Restore the diagnostic producer or resolve the reported condition before launch.",
360
+ }),
361
+ });
362
+ });
342
363
  }
343
364
  /** Human labels for the known BAPI-1260 evidence predicates. */
344
365
  const ADMISSION_EVIDENCE_LABELS = {
@@ -902,6 +923,7 @@ export async function collectConductorReadinessGate(deps) {
902
923
  repoName: deps.repoName ?? null,
903
924
  unavailableSources,
904
925
  checks: validated,
926
+ ...(installReport?.readiness?.diagnostics ? { diagnostics: installReport.readiness.diagnostics } : {}),
905
927
  summary: summarizeReadinessChecks(validated),
906
928
  };
907
929
  }