@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
package/build/install-bridge.js
CHANGED
|
@@ -176,6 +176,8 @@ import { upsertBapiCredential, getPrimaryCredentialStorePath, prepareBootstrapPe
|
|
|
176
176
|
// API primitives verbatim — no duplicated polling, browser, or picker logic here.
|
|
177
177
|
import { fetchGithubConfigurationState } from "./connect-github-api.js";
|
|
178
178
|
import { createDefaultConnectGithubDeps, runGithubConnectionFlow } from "./connect-github.js";
|
|
179
|
+
import { fetchBitbucketConfigurationState } from "./connect-bitbucket-api.js";
|
|
180
|
+
import { createDefaultConnectBitbucketDeps, runBitbucketConnectionFlow } from "./connect-bitbucket.js";
|
|
179
181
|
import { resolveAgentSpec, isAgentName, formatValidAgentNames, } from "./agent-registry.js";
|
|
180
182
|
import { buildGenericAgentShellCommand, getDefaultSpawnTerminalTabForPlatform, detectTerminal, createDefaultStartTicketsDeps, materializeWorkerLaunchCommand, MAX_TERMINAL_COMMAND_BYTES, } from "./start-tickets.js";
|
|
181
183
|
/** Redaction sentinel — the API-key value is NEVER printed; this stands in. */
|
|
@@ -884,6 +886,91 @@ export async function offerGithubConnection(repoName, baseUrl, deps, log) {
|
|
|
884
886
|
`GitHub later with 'npx -y ${MCP_PACKAGE_NAME}@latest connect-github --repo ${repoName}'.`);
|
|
885
887
|
}
|
|
886
888
|
}
|
|
889
|
+
/**
|
|
890
|
+
* The deferral command printed with the Bitbucket offer (BAPI-966). Declining is cheap
|
|
891
|
+
* precisely because this exists — the connection is a standalone command.
|
|
892
|
+
*/
|
|
893
|
+
export const INSTALL_BRIDGE_CONNECT_BITBUCKET_COMMAND = `npx -y ${MCP_PACKAGE_NAME} connect-bitbucket`;
|
|
894
|
+
/**
|
|
895
|
+
* The offer context lines printed immediately before the Bitbucket prompt. Mirrors
|
|
896
|
+
* {@link INSTALL_BRIDGE_GITHUB_OFFER_CONTEXT}'s shape.
|
|
897
|
+
*/
|
|
898
|
+
export const INSTALL_BRIDGE_BITBUCKET_OFFER_CONTEXT = [
|
|
899
|
+
"",
|
|
900
|
+
"Step 4b — optional: connect Bitbucket.",
|
|
901
|
+
" This installs the Bridge Bitbucket Forge app so you can implement and review tickets with Bridge.",
|
|
902
|
+
" It opens Bitbucket in your browser; no Bitbucket credential is shared with Bridge.",
|
|
903
|
+
` You can do this later instead: ${INSTALL_BRIDGE_CONNECT_BITBUCKET_COMMAND}`,
|
|
904
|
+
];
|
|
905
|
+
/** Default-No Bitbucket offer prompt. Mirrors {@link INSTALL_BRIDGE_GITHUB_CONNECT_PROMPT}. */
|
|
906
|
+
export const INSTALL_BRIDGE_BITBUCKET_CONNECT_PROMPT = "Connect Bitbucket? [y/N]: ";
|
|
907
|
+
/**
|
|
908
|
+
* Offer to connect Bitbucket, if it is not already connected (BAPI-966).
|
|
909
|
+
*
|
|
910
|
+
* Entirely best-effort and non-destructive, mirroring {@link offerGithubConnection}:
|
|
911
|
+
* by the time this runs the install itself is already complete and durable, so nothing
|
|
912
|
+
* here may fail the run. Every branch that is not "the user said yes and it worked"
|
|
913
|
+
* simply proceeds to the agent session.
|
|
914
|
+
*
|
|
915
|
+
* Reuses the shared connect-bitbucket flow rather than duplicating the API, polling,
|
|
916
|
+
* browser, or picker logic — there is exactly one implementation of that handshake.
|
|
917
|
+
*/
|
|
918
|
+
export async function offerBitbucketConnection(repoName, baseUrl, deps, log) {
|
|
919
|
+
// No prompt surface → no offer. Never assume consent on a non-interactive run.
|
|
920
|
+
if (!deps.isTTY || !deps.promptLine)
|
|
921
|
+
return;
|
|
922
|
+
try {
|
|
923
|
+
const credDeps = {
|
|
924
|
+
env: deps.env,
|
|
925
|
+
homedir: deps.homedir,
|
|
926
|
+
platform: deps.platform,
|
|
927
|
+
readFile: deps.readFile,
|
|
928
|
+
stat: deps.stat,
|
|
929
|
+
stderr: () => { },
|
|
930
|
+
};
|
|
931
|
+
// Resolve through the shared resolver rather than reusing an in-memory key from
|
|
932
|
+
// this run: the offered flow is the same shell-spawned surface the standalone
|
|
933
|
+
// command uses, and it must resolve credentials the same way (project MCP config
|
|
934
|
+
// env is NOT visible to a spawned shell).
|
|
935
|
+
const cred = await resolveBapiCredentials(repoName, credDeps);
|
|
936
|
+
if (!cred.ok)
|
|
937
|
+
return;
|
|
938
|
+
const api = {
|
|
939
|
+
fetch: deps.fetch,
|
|
940
|
+
baseUrl,
|
|
941
|
+
apiKey: cred.credentials.apiKey,
|
|
942
|
+
};
|
|
943
|
+
const state = await fetchBitbucketConfigurationState(api, repoName);
|
|
944
|
+
if (state === "configured")
|
|
945
|
+
return;
|
|
946
|
+
if (state === "unavailable") {
|
|
947
|
+
// Do NOT fabricate "unconfigured" from a probe that simply failed — offering to
|
|
948
|
+
// connect an already-connected repo is worse than staying quiet. Mirrors
|
|
949
|
+
// offerGithubConnection's same-shaped branch; this is also the normal outcome for
|
|
950
|
+
// a non-Bitbucket project, since the descriptor is provider-scoped.
|
|
951
|
+
log(" note: could not read Bitbucket configuration status; skipping the Bitbucket offer.");
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
for (const line of INSTALL_BRIDGE_BITBUCKET_OFFER_CONTEXT)
|
|
955
|
+
log(line);
|
|
956
|
+
const answer = (await deps.promptLine(INSTALL_BRIDGE_BITBUCKET_CONNECT_PROMPT)).trim().toLowerCase();
|
|
957
|
+
// Only an explicit yes proceeds — blank input, EOF, "n", and anything malformed
|
|
958
|
+
// all skip without opening a browser.
|
|
959
|
+
if (answer !== "y" && answer !== "yes")
|
|
960
|
+
return; // declining is a normal outcome
|
|
961
|
+
const connectDeps = createDefaultConnectBitbucketDeps();
|
|
962
|
+
const code = await runBitbucketConnectionFlow(connectDeps, api, repoName);
|
|
963
|
+
if (code !== 0) {
|
|
964
|
+
log(" note: Bitbucket was not connected. Your install is complete — connect Bitbucket later with " +
|
|
965
|
+
`'npx -y ${MCP_PACKAGE_NAME}@latest connect-bitbucket --repo ${repoName}'.`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
// The install is already durable; a failure here is never fatal to it.
|
|
970
|
+
log(" note: the Bitbucket connection offer could not run. Your install is complete — connect " +
|
|
971
|
+
`Bitbucket later with 'npx -y ${MCP_PACKAGE_NAME}@latest connect-bitbucket --repo ${repoName}'.`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
887
974
|
/**
|
|
888
975
|
* Echoed single-line prompt on stderr (used for repo confirmation / value).
|
|
889
976
|
* Exported so `index.ts`'s `--init` can share the exact same prompt mechanics
|
|
@@ -5437,6 +5524,14 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
5437
5524
|
// observe GitHub as configured if the user connects it here, and running it after the
|
|
5438
5525
|
// spawn would put two prompts on the same terminal at once.
|
|
5439
5526
|
await offerGithubConnection(repoName, baseUrl, deps, log);
|
|
5527
|
+
// ---- optional Bitbucket connect offer (BAPI-966) ----
|
|
5528
|
+
// Same placement reasoning as the GitHub offer above. Both offers are called
|
|
5529
|
+
// unconditionally here — each self-gates on its OWN provider-scoped configuration
|
|
5530
|
+
// state (`fetchGithubConfigurationState`/`fetchBitbucketConfigurationState` return
|
|
5531
|
+
// "unavailable" when their integration id is absent from the manifest, which is the
|
|
5532
|
+
// normal outcome for the non-matching provider), so only the project's actual VCS
|
|
5533
|
+
// provider ever reaches its prompt.
|
|
5534
|
+
await offerBitbucketConnection(repoName, baseUrl, deps, log);
|
|
5440
5535
|
// ---- Step 3b (await) — settle the background pre-warm before the session opens ----
|
|
5441
5536
|
// The warm was kicked off (only on a spawn-capable path) right after the
|
|
5442
5537
|
// launch-command guards; await it HERE so the bucket is settled before the spawned
|
package/build/install-doctor.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* required prerequisites only.
|
|
21
21
|
*/
|
|
22
22
|
import path from "path";
|
|
23
|
+
import { createReadinessCheck, createReadinessCheckSafely, } from "./readiness-check.js";
|
|
23
24
|
import { resolveBapiCredentials } from "./credential-store.js";
|
|
24
25
|
import { detectClaudeLogin, formatClaudeLoginAdvisory } from "./claude-login.js";
|
|
25
26
|
import { getProjectJsonTargets, hostAdapterForTarget } from "./mcp-host-targets.js";
|
|
@@ -105,7 +106,16 @@ export async function resolveInstallDoctorTarget(deps) {
|
|
|
105
106
|
}
|
|
106
107
|
return { repoName, repoSource, baseUrl: baseUrl ?? DEFAULT_BASE_URL };
|
|
107
108
|
}
|
|
108
|
-
/**
|
|
109
|
+
/** Classify a thrown probe failure into the closed category vocabulary. */
|
|
110
|
+
function classifyProbeFailure(error) {
|
|
111
|
+
// `AbortSignal.timeout` rejects with a DOMException named `TimeoutError`;
|
|
112
|
+
// fall back to the name check so a polyfilled or wrapped abort still reads as
|
|
113
|
+
// a timeout rather than as a generic failure.
|
|
114
|
+
const name = error?.name;
|
|
115
|
+
return name === "TimeoutError" || name === "AbortError"
|
|
116
|
+
? "request timed out"
|
|
117
|
+
: "request unavailable";
|
|
118
|
+
}
|
|
109
119
|
async function probeGet(deps, url, apiKey) {
|
|
110
120
|
try {
|
|
111
121
|
const resp = await deps.fetch(url, {
|
|
@@ -122,7 +132,7 @@ async function probeGet(deps, url, apiKey) {
|
|
|
122
132
|
return { ok: true, status: resp.status, body };
|
|
123
133
|
}
|
|
124
134
|
catch (e) {
|
|
125
|
-
return { ok: false, error:
|
|
135
|
+
return { ok: false, error: classifyProbeFailure(e) };
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
/** Count set/total bootstrap fields from an install-manifest response body. */
|
|
@@ -300,6 +310,8 @@ export async function collectInstallStatusChecks(deps) {
|
|
|
300
310
|
label: "Server connectivity",
|
|
301
311
|
status: "WARN",
|
|
302
312
|
detail: `unexpected HTTP ${ping.status} from /jira/ping`,
|
|
313
|
+
remediation: "the server answered but not with a status this check understands — re-run doctor; " +
|
|
314
|
+
"if it persists the Bridge API is unhealthy and no install fact below can be trusted.",
|
|
303
315
|
});
|
|
304
316
|
}
|
|
305
317
|
// --- bootstrap fields ---
|
|
@@ -310,6 +322,7 @@ export async function collectInstallStatusChecks(deps) {
|
|
|
310
322
|
label: "Bootstrap config fields",
|
|
311
323
|
status: "WARN",
|
|
312
324
|
detail: `manifest unreachable (${manifest.error})`,
|
|
325
|
+
remediation: `check ${target.baseUrl} and your network, then re-run doctor.`,
|
|
313
326
|
});
|
|
314
327
|
}
|
|
315
328
|
else if (manifest.status === 404) {
|
|
@@ -329,6 +342,8 @@ export async function collectInstallStatusChecks(deps) {
|
|
|
329
342
|
label: "Bootstrap config fields",
|
|
330
343
|
status: "WARN",
|
|
331
344
|
detail: "manifest response had an unexpected shape",
|
|
345
|
+
remediation: "the manifest could not be read, so no bootstrap field state is known — re-run doctor, " +
|
|
346
|
+
"then run /install-bridge to re-derive the project configuration.",
|
|
332
347
|
});
|
|
333
348
|
}
|
|
334
349
|
else if (summary.unset.length === 0) {
|
|
@@ -355,6 +370,8 @@ export async function collectInstallStatusChecks(deps) {
|
|
|
355
370
|
label: "Bootstrap config fields",
|
|
356
371
|
status: "WARN",
|
|
357
372
|
detail: `unexpected HTTP ${manifest.status} from the install manifest`,
|
|
373
|
+
remediation: "the manifest endpoint answered with an unrecognized status — re-run doctor; " +
|
|
374
|
+
"if it persists, re-run /install-bridge for this repository.",
|
|
358
375
|
});
|
|
359
376
|
}
|
|
360
377
|
// --- integrations (from the same manifest response; presence booleans only) ---
|
|
@@ -509,3 +526,138 @@ export function formatInstallStatusFallbackReport() {
|
|
|
509
526
|
},
|
|
510
527
|
]);
|
|
511
528
|
}
|
|
529
|
+
// ---------------------------------------------------------------------------
|
|
530
|
+
// Canonical readiness projection (BAPI-1055)
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
/**
|
|
533
|
+
* The complete install-side prerequisite set, in render order.
|
|
534
|
+
*
|
|
535
|
+
* A STABLE descriptor set, not a projection of whatever the collector happened
|
|
536
|
+
* to return. `collectInstallStatusChecks` returns early on an unresolved
|
|
537
|
+
* identity and again on an unresolved credential, so the checks it omits simply
|
|
538
|
+
* never appear — and a locus that vanished reads as "nothing to report" rather
|
|
539
|
+
* than "this could not be established". The adapter walks this list instead, so
|
|
540
|
+
* every prerequisite is always represented.
|
|
541
|
+
*/
|
|
542
|
+
export const INSTALL_READINESS_DESCRIPTORS = [
|
|
543
|
+
{ id: "claude-login", label: "Claude login" },
|
|
544
|
+
{ id: "identity", label: "Repository identity" },
|
|
545
|
+
{ id: "credential", label: "Bridge API credential" },
|
|
546
|
+
{ id: "connectivity", label: "Server connectivity" },
|
|
547
|
+
{ id: "bootstrap", label: "Bootstrap config fields" },
|
|
548
|
+
{ id: "integrations", label: "Integration credentials" },
|
|
549
|
+
{ id: "github", label: "GitHub connection" },
|
|
550
|
+
{ id: "indexing", label: "Repository indexing" },
|
|
551
|
+
];
|
|
552
|
+
/** Fixed remediation for an install prerequisite whose identity never resolved. */
|
|
553
|
+
export const INSTALL_IDENTITY_REMEDIATION = "run /install-bridge (or set BAPI_REPO_NAME) to configure this project; without a repository " +
|
|
554
|
+
"identity no server-side conductor fact can be resolved.";
|
|
555
|
+
/** Fixed remediation for the advisory Claude-login gap. */
|
|
556
|
+
export const INSTALL_CLAUDE_LOGIN_REMEDIATION = "run `claude login` on the host that will run conductor workers; Bridge stores no Anthropic " +
|
|
557
|
+
"credential and cannot confirm this for you.";
|
|
558
|
+
/** Fixed detail for a prerequisite the checklist stopped short of collecting. */
|
|
559
|
+
const INSTALL_NOT_COLLECTED_DETAIL = "not collected — an earlier install prerequisite stopped the checklist";
|
|
560
|
+
/** Fixed detail for a prerequisite whose whole collector could not be run. */
|
|
561
|
+
const INSTALL_UNAVAILABLE_DETAIL = "unknown — the install-status checklist could not be collected at all";
|
|
562
|
+
/** Fixed remediation for a prerequisite whose whole collector could not be run. */
|
|
563
|
+
const INSTALL_UNAVAILABLE_REMEDIATION = "run `doctor` to collect the advisory install-status checklist directly; this prerequisite's " +
|
|
564
|
+
"state is unknown, not healthy.";
|
|
565
|
+
/**
|
|
566
|
+
* How each install check's non-canonical statuses project (BAPI-1055).
|
|
567
|
+
*
|
|
568
|
+
* `INFO` never becomes a canonical status, and `SKIP` is not automatically
|
|
569
|
+
* `skip`: an unresolved repository identity is reported as `SKIP` by the
|
|
570
|
+
* collector because the checklist stops there, but it is a genuine unmet
|
|
571
|
+
* prerequisite, so it maps to `fail`. The mapping is therefore stated per check
|
|
572
|
+
* id rather than derived from the source status alone.
|
|
573
|
+
*/
|
|
574
|
+
function projectInstallStatus(check) {
|
|
575
|
+
if (check.status === "PASS")
|
|
576
|
+
return { status: "pass" };
|
|
577
|
+
if (check.status === "WARN") {
|
|
578
|
+
return { status: "warn", ...(check.remediation ? { remediation: check.remediation } : {}) };
|
|
579
|
+
}
|
|
580
|
+
if (check.status === "INFO") {
|
|
581
|
+
// `claude-login` INFO means "no local login marker found". That is an
|
|
582
|
+
// actionable conductor gap — a worker cannot run without it — so it warns.
|
|
583
|
+
if (check.id === "claude-login") {
|
|
584
|
+
return { status: "warn", remediation: INSTALL_CLAUDE_LOGIN_REMEDIATION };
|
|
585
|
+
}
|
|
586
|
+
// `indexing` INFO is genuinely informational in both of its branches (a
|
|
587
|
+
// parse running, or none running). Neither is a prerequisite gap, so the
|
|
588
|
+
// information rides on `detail` and the check passes.
|
|
589
|
+
return { status: "pass" };
|
|
590
|
+
}
|
|
591
|
+
// SKIP.
|
|
592
|
+
if (check.id === "identity") {
|
|
593
|
+
// Unknown identity is never a successful or guessed repository context.
|
|
594
|
+
return { status: "fail", remediation: check.remediation ?? INSTALL_IDENTITY_REMEDIATION };
|
|
595
|
+
}
|
|
596
|
+
return { status: "skip", ...(check.remediation ? { remediation: check.remediation } : {}) };
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Project the install-status checklist into canonical readiness checks.
|
|
600
|
+
*
|
|
601
|
+
* PURE: no probe, no I/O, no re-derivation. Ids are namespaced `install.<id>`
|
|
602
|
+
* so provenance survives into a consolidated report without being encoded in
|
|
603
|
+
* display prose, and only the collector's own fixed labels, bounded details, and
|
|
604
|
+
* authored remediations are carried across.
|
|
605
|
+
*/
|
|
606
|
+
export function mapInstallStatusChecksToReadinessChecks(checks,
|
|
607
|
+
/**
|
|
608
|
+
* True when the COLLECTOR itself could not run, as opposed to running and
|
|
609
|
+
* stopping early.
|
|
610
|
+
*
|
|
611
|
+
* `null` alone cannot tell those apart, and they are not the same fact. A
|
|
612
|
+
* checklist that ran and returned early leaves its later prerequisites
|
|
613
|
+
* genuinely dependent — a `skip`. A checklist that never ran leaves them
|
|
614
|
+
* simply unknown, and unknown must never read as healthy, so they fail with a
|
|
615
|
+
* restoring remediation instead.
|
|
616
|
+
*/
|
|
617
|
+
collectorUnavailable = false) {
|
|
618
|
+
const byId = new Map();
|
|
619
|
+
for (const check of checks ?? []) {
|
|
620
|
+
// First writer wins: the collector emits each id once, and a duplicate from
|
|
621
|
+
// an injected fake must not silently replace the real finding.
|
|
622
|
+
if (!byId.has(check.id))
|
|
623
|
+
byId.set(check.id, check);
|
|
624
|
+
}
|
|
625
|
+
return INSTALL_READINESS_DESCRIPTORS.map(({ id, label }) => {
|
|
626
|
+
const source = byId.get(id);
|
|
627
|
+
if (!source) {
|
|
628
|
+
return collectorUnavailable
|
|
629
|
+
? createReadinessCheck({
|
|
630
|
+
id: `install.${id}`,
|
|
631
|
+
source: "install",
|
|
632
|
+
label,
|
|
633
|
+
status: "fail",
|
|
634
|
+
detail: INSTALL_UNAVAILABLE_DETAIL,
|
|
635
|
+
remediation: INSTALL_UNAVAILABLE_REMEDIATION,
|
|
636
|
+
})
|
|
637
|
+
: // Absent because the checklist never got this far. A dependency case,
|
|
638
|
+
// so `skip` — the prerequisite that stopped the chain carries the fix.
|
|
639
|
+
createReadinessCheck({
|
|
640
|
+
id: `install.${id}`,
|
|
641
|
+
source: "install",
|
|
642
|
+
label,
|
|
643
|
+
status: "skip",
|
|
644
|
+
detail: INSTALL_NOT_COLLECTED_DETAIL,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
const projected = projectInstallStatus(source);
|
|
648
|
+
return createReadinessCheckSafely({
|
|
649
|
+
id: `install.${id}`,
|
|
650
|
+
source: "install",
|
|
651
|
+
label: source.label || label,
|
|
652
|
+
status: projected.status,
|
|
653
|
+
...(source.detail ? { detail: source.detail } : {}),
|
|
654
|
+
...(projected.status === "pass"
|
|
655
|
+
? {}
|
|
656
|
+
: projected.remediation
|
|
657
|
+
? { remediation: projected.remediation }
|
|
658
|
+
: projected.status === "fail"
|
|
659
|
+
? { remediation: INSTALL_IDENTITY_REMEDIATION }
|
|
660
|
+
: {}),
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
}
|