@bridge_gpt/mcp-server 0.2.62 → 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.
- package/README.md +66 -15
- package/build/agent-launchers/claude-executor-adapter.js +145 -20
- package/build/agent-launchers/executor-adapter.js +81 -2
- package/build/agents.generated.js +10 -1
- package/build/bounded-wait.js +32 -1
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +7 -6
- package/build/conductor/bridge-api-client.js +565 -32
- package/build/conductor/bring-up-facts.js +3 -1
- package/build/conductor/doctor.js +23 -14
- package/build/conductor/epic-child-continuation.js +351 -0
- package/build/conductor/epic-implementer-cli.js +154 -17
- package/build/conductor/git-hooks.js +529 -118
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/hook-runtime-qualification.js +340 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness-sections.js +5 -4
- package/build/conductor/readiness.js +45 -12
- package/build/conductor/recovery-cli.js +859 -27
- package/build/conductor/recovery-operations.js +574 -5
- package/build/conductor/repair-contract.js +69 -3
- package/build/conductor-bundle-artifacts.js +1 -0
- package/build/credential-store.js +52 -13
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +3 -2
- package/build/doctor.js +147 -21
- package/build/drive-epic-key.js +12 -0
- package/build/drive-epic.js +1537 -125
- package/build/epic-coherence-input-cli.js +137 -0
- package/build/epic-coherence-input.js +920 -0
- package/build/epic-coherence-input.test-fixtures.js +175 -0
- package/build/epic-implementer-bin.js +41 -33
- package/build/epic-implementer-bundle-cli.js +14 -1
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +58 -19
- package/build/epic-run-status.js +5 -0
- package/build/epic-shared/bridge-client.js +46 -4
- package/build/executor/cli.js +39 -0
- package/build/executor/completion-contract.js +423 -0
- package/build/executor/completion-supervision.js +291 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/credentials.js +3 -1
- package/build/executor/deps.js +65 -1
- package/build/executor/env.js +12 -0
- package/build/executor/heartbeat.js +13 -3
- package/build/executor/http-client.js +91 -4
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +61 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +1550 -55
- package/build/executor/job-types.js +9 -6
- package/build/executor/preflight.js +189 -1
- package/build/executor/process-containment.js +756 -0
- package/build/executor/process.js +235 -5
- package/build/executor/prompt-spec.js +366 -1
- package/build/executor/provider-checks.js +69 -0
- package/build/executor/rebase-finalization.js +351 -0
- package/build/executor/reconcile-overlap-job.js +77 -84
- package/build/executor/required-verification.js +313 -0
- package/build/executor/required-work-ledger.js +1121 -0
- package/build/executor/results.js +142 -0
- package/build/executor/resume-pre-spawn.js +87 -4
- package/build/executor/runner.js +168 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/test-declaration.js +20 -0
- package/build/executor/types.js +13 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +70 -34
- package/build/executor/worker-finalization.js +168 -26
- package/build/executor/worker-guard-hook.js +75 -14
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/executor/worktree.js +13 -0
- package/build/executor-launch.js +658 -0
- package/build/index.js +594 -1152
- package/build/init.js +107 -4
- package/build/install-bridge-conductor.js +231 -41
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/merge-pull-request.js +1 -1
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +9 -8
- package/build/plan-discovery.js +137 -0
- package/build/plan-epic-implementer-eligibility.js +2 -2
- package/build/plane/cli.js +37 -3
- package/build/plane/member-logs.js +3 -0
- package/build/plane/member-roster.js +7 -1
- package/build/plane/shutdown.js +59 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +146 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/scripts/executor-protocol-contract-driver.js +13 -3
- package/build/setup-epic.js +208 -125
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +5 -4
- package/build/worktree-core.js +6 -5
- package/docs/CONDUCTOR.md +116 -2
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +2 -1
- package/pipelines/implement-ticket.json +1 -1
package/build/setup-epic.js
CHANGED
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
|
|
79
79
|
import os from "node:os";
|
|
80
80
|
import { createProductionEpicIntegrationGhRunner, ensureEpicIntegrationPullRequest, formatEpicIntegrationPullRequestOutcome, } from "./epic-integration-pr.js";
|
|
81
|
-
import { approveEpicPlan, buildConductorJiraUrl, conductorGetHeaders, createEpicRunWithDisposition, decodeCanonicalProvenance, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, resolveEpicLaunchBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
81
|
+
import { approveEpicPlan, buildConductorJiraUrl, conductorGetHeaders, createEpicRunWithDisposition, decodeCanonicalProvenance, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, resolveEpicLaunchBridgeApiAccess, safeDiagnosticMessage, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
82
|
+
import { formatEpicLaunchChannelIdentity, getPinnedEpicLaunchPolicy, readEpicLaunchProfile, } from "./epic-launch-profile.js";
|
|
82
83
|
// BAPI-1133 (A2) — the single legacy-to-typed merge-execution-mode mapping and
|
|
83
84
|
// its total legacy resolver live in `merge-pull-request.ts` (no import cycle:
|
|
84
85
|
// that module never imports from `setup-epic.ts`). Reused here rather than
|
|
@@ -209,10 +210,11 @@ export function getSetupEpicUsage() {
|
|
|
209
210
|
"that selects no epic branch (--into-base, or a single-node plan) is unchanged",
|
|
210
211
|
"and composes no posture at all.",
|
|
211
212
|
"",
|
|
212
|
-
"Unattended runs have TWO
|
|
213
|
-
"
|
|
214
|
-
"
|
|
215
|
-
"
|
|
213
|
+
"Unattended runs have TWO repository prerequisites, refused by name before any",
|
|
214
|
+
"run is created: repository consent (ON by default; the repository owner can",
|
|
215
|
+
"switch 'Allow unattended Epic Implementer runs' off and on in the Bridge admin",
|
|
216
|
+
"panel's Setup page) and a verified notify webhook default. `drive-epic`",
|
|
217
|
+
"readiness lists both with their current state.",
|
|
216
218
|
"",
|
|
217
219
|
"Required:",
|
|
218
220
|
" --epic-key <KEY> Jira epic key (e.g. BAPI-405)",
|
|
@@ -261,7 +263,8 @@ export function getSetupEpicUsage() {
|
|
|
261
263
|
" --json Emit a single JSON result object on stdout",
|
|
262
264
|
" --launch-profile <name> (BAPI-1260) Resolve the target, repository, and bound",
|
|
263
265
|
" credential from this host-local launch profile. Passed",
|
|
264
|
-
" by drive-epic
|
|
266
|
+
" by drive-epic. The profile names its own target; it",
|
|
267
|
+
" never falls back to production by itself.",
|
|
265
268
|
" --base-url <url> (BAPI-1260) Explicit target; must agree with the profile.",
|
|
266
269
|
" -h, --help Show this help",
|
|
267
270
|
"",
|
|
@@ -1115,6 +1118,24 @@ function findCycle(keys, adjacency) {
|
|
|
1115
1118
|
}
|
|
1116
1119
|
return null;
|
|
1117
1120
|
}
|
|
1121
|
+
export function effectivePolicyFacts(policy, source) {
|
|
1122
|
+
const object = policy && typeof policy === "object" && !Array.isArray(policy)
|
|
1123
|
+
? policy : {};
|
|
1124
|
+
const numbers = (value) => {
|
|
1125
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1126
|
+
return null;
|
|
1127
|
+
const entries = Object.entries(value).filter(([key, item]) => /^[a-z][a-z0-9_]{0,63}$/.test(key) && typeof item === "number" && Number.isFinite(item));
|
|
1128
|
+
return entries.length ? Object.fromEntries(entries) : null;
|
|
1129
|
+
};
|
|
1130
|
+
return {
|
|
1131
|
+
source,
|
|
1132
|
+
serial_execution: "serial",
|
|
1133
|
+
posture: object.posture === "unattended" ? "unattended" : "attended",
|
|
1134
|
+
caps: numbers(object.caps),
|
|
1135
|
+
job_timeouts: numbers(object.job_timeouts),
|
|
1136
|
+
escalation_channel: formatEpicLaunchChannelIdentity(source === "stored" ? "unavailable" : policyDeclaresNotifyWebhook(object) ? "configured webhook" : "account email"),
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1118
1139
|
/** Propose a conductor epic feature branch name for an epic key (BAPI-655). */
|
|
1119
1140
|
export function proposeFeatureBranchName(epicKey) {
|
|
1120
1141
|
return `epic/${epicKey}`;
|
|
@@ -1235,10 +1256,10 @@ export function evaluateSetupEpicUnattendedPrerequisites(readiness, requestedPol
|
|
|
1235
1256
|
if (!unattended.conductor_allowed) {
|
|
1236
1257
|
return {
|
|
1237
1258
|
reason: "target_not_allowed",
|
|
1238
|
-
message: "
|
|
1239
|
-
"
|
|
1240
|
-
"
|
|
1241
|
-
"
|
|
1259
|
+
message: "unattended runs are off for this repository. Turn on 'Allow unattended " +
|
|
1260
|
+
"Epic Implementer runs' on the repository's Setup page in the Bridge admin " +
|
|
1261
|
+
"panel, or pass --attended to create an attended run. If it is already on, " +
|
|
1262
|
+
"contact Bridge support. No run was created.",
|
|
1242
1263
|
};
|
|
1243
1264
|
}
|
|
1244
1265
|
// Two independent sources satisfy the escalation channel, and the check has to
|
|
@@ -1253,13 +1274,21 @@ export function evaluateSetupEpicUnattendedPrerequisites(readiness, requestedPol
|
|
|
1253
1274
|
// Presence, not verification: the server probes the URL at activation and is
|
|
1254
1275
|
// the authority on whether it is reachable. This gate exists to spare the
|
|
1255
1276
|
// operator a created-and-refused run, not to duplicate the probe.
|
|
1277
|
+
//
|
|
1278
|
+
// BAPI-1274 — a third source: when no webhook is declared anywhere, the
|
|
1279
|
+
// account's email address is the escalation channel, and the server reports
|
|
1280
|
+
// its presence (never the address). A declared webhook stays authoritative;
|
|
1281
|
+
// email only fills the gap when none is declared. `notify.local_sink` still
|
|
1282
|
+
// satisfies nothing.
|
|
1256
1283
|
if (!unattended.notify_webhook_default_declared &&
|
|
1257
|
-
!policyDeclaresNotifyWebhook(requestedPolicy)
|
|
1284
|
+
!policyDeclaresNotifyWebhook(requestedPolicy) &&
|
|
1285
|
+
unattended.escalation_email_available !== true) {
|
|
1258
1286
|
return {
|
|
1259
1287
|
reason: "webhook_unverified",
|
|
1260
|
-
message: "an unattended run must have
|
|
1261
|
-
"
|
|
1262
|
-
"notify.webhook_url (notify.local_sink does not satisfy this), or
|
|
1288
|
+
message: "an unattended run must have an escalation channel, and this repository, this " +
|
|
1289
|
+
"run, and the account provide none. Supply a --policy-file declaring " +
|
|
1290
|
+
"notify.webhook_url (notify.local_sink does not satisfy this), or add an email " +
|
|
1291
|
+
"address to the account so escalation alerts can be emailed, or pass " +
|
|
1263
1292
|
"--attended to create an attended run. No run was created.",
|
|
1264
1293
|
};
|
|
1265
1294
|
}
|
|
@@ -1551,12 +1580,7 @@ composesDefaultPolicy = true) {
|
|
|
1551
1580
|
`(pass --feature-branch to force ${proposeFeatureBranchName(opts.epicKey)} anyway)`);
|
|
1552
1581
|
}
|
|
1553
1582
|
function errorDetail(err) {
|
|
1554
|
-
|
|
1555
|
-
const status = err.status !== undefined ? ` (HTTP ${err.status})` : "";
|
|
1556
|
-
const preview = err.bodyPreview ? `: ${err.bodyPreview}` : "";
|
|
1557
|
-
return `${err.message}${status}${preview}`;
|
|
1558
|
-
}
|
|
1559
|
-
return err instanceof Error ? err.message : String(err);
|
|
1583
|
+
return safeDiagnosticMessage(err, "operation failed");
|
|
1560
1584
|
}
|
|
1561
1585
|
/**
|
|
1562
1586
|
* Reconcile a policy file against a REUSED run's stored policy, applying it only
|
|
@@ -1578,11 +1602,13 @@ async function applyReusedRunPolicy(args) {
|
|
|
1578
1602
|
});
|
|
1579
1603
|
if (outcome.kind === "noop") {
|
|
1580
1604
|
args.result.policy_applied = "noop";
|
|
1605
|
+
args.result.effective_policy = effectivePolicyFacts(args.storedPolicy, "stored");
|
|
1581
1606
|
args.say("Policy: policy unchanged");
|
|
1582
1607
|
return true;
|
|
1583
1608
|
}
|
|
1584
1609
|
if (outcome.kind === "refused_divergent") {
|
|
1585
1610
|
args.result.policy_applied = "refused_divergent";
|
|
1611
|
+
args.result.effective_policy = effectivePolicyFacts(args.storedPolicy, "stored");
|
|
1586
1612
|
args.deps.errorLog(`Epic run ${args.epicRunId} is live and its stored policy differs from the ` +
|
|
1587
1613
|
`policy file. setup-epic will not silently change a running epic's policy.\n` +
|
|
1588
1614
|
`${outcome.diffLines.join("\n")}\n` +
|
|
@@ -1595,10 +1621,12 @@ async function applyReusedRunPolicy(args) {
|
|
|
1595
1621
|
}
|
|
1596
1622
|
catch (err) {
|
|
1597
1623
|
args.result.policy_applied = "refused_divergent";
|
|
1624
|
+
args.result.effective_policy = effectivePolicyFacts(args.storedPolicy, "stored");
|
|
1598
1625
|
args.deps.errorLog(`Failed to replace the run policy: ${errorDetail(err)}`);
|
|
1599
1626
|
return false;
|
|
1600
1627
|
}
|
|
1601
1628
|
args.result.policy_applied = "replaced";
|
|
1629
|
+
args.result.effective_policy = effectivePolicyFacts(args.requestedPolicy, "explicit");
|
|
1602
1630
|
args.say("Policy: policy applied: replaced");
|
|
1603
1631
|
for (const line of outcome.diffLines)
|
|
1604
1632
|
args.say(line);
|
|
@@ -1850,12 +1878,13 @@ function describeScopeTransition(state, status) {
|
|
|
1850
1878
|
* {@link runSetupEpicCli} in every observable way — same output, same refusals,
|
|
1851
1879
|
* same exit codes — and differs only in what it hands back.
|
|
1852
1880
|
*/
|
|
1853
|
-
export async function runSetupEpicWorkflow(argv, overrides = {}) {
|
|
1881
|
+
export async function runSetupEpicWorkflow(argv, overrides = {}, handoff) {
|
|
1854
1882
|
const sink = {};
|
|
1855
|
-
const exitCode = await executeSetupEpic(argv, overrides, sink);
|
|
1883
|
+
const exitCode = await executeSetupEpic(argv, overrides, sink, handoff);
|
|
1856
1884
|
return {
|
|
1857
1885
|
exitCode,
|
|
1858
1886
|
...(sink.epicRunId !== undefined ? { epicRunId: sink.epicRunId } : {}),
|
|
1887
|
+
...(sink.epicRunId !== undefined ? { runExists: true } : {}),
|
|
1859
1888
|
};
|
|
1860
1889
|
}
|
|
1861
1890
|
/**
|
|
@@ -1865,7 +1894,7 @@ export async function runSetupEpicWorkflow(argv, overrides = {}) {
|
|
|
1865
1894
|
export async function runSetupEpicCli(argv, overrides = {}) {
|
|
1866
1895
|
return (await runSetupEpicWorkflow(argv, overrides)).exitCode;
|
|
1867
1896
|
}
|
|
1868
|
-
async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
1897
|
+
async function executeSetupEpic(argv, overrides, runIdSink, handoff) {
|
|
1869
1898
|
const deps = { ...createDefaultSetupEpicDeps(), ...overrides };
|
|
1870
1899
|
const parsed = parseSetupEpicArgs(argv);
|
|
1871
1900
|
if (parsed.status === "help") {
|
|
@@ -1881,6 +1910,14 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1881
1910
|
const opts = parsed.options;
|
|
1882
1911
|
// With --json, stdout carries the single result object and nothing else.
|
|
1883
1912
|
const say = opts.json ? deps.errorLog : deps.log;
|
|
1913
|
+
if (handoff && (handoff.access.baseUrl !== handoff.selection.baseUrl ||
|
|
1914
|
+
handoff.access.repoName !== handoff.selection.repoName ||
|
|
1915
|
+
(opts.baseUrl !== undefined && opts.baseUrl !== handoff.selection.baseUrl) ||
|
|
1916
|
+
(opts.repo !== undefined && opts.repo !== handoff.selection.repoName) ||
|
|
1917
|
+
(opts.launchProfile !== undefined && opts.launchProfile !== handoff.selection.profileName))) {
|
|
1918
|
+
deps.errorLog("setup-epic REFUSED (selection-conflict): delegated target or repository disagrees with the pinned launch selection.");
|
|
1919
|
+
return 1;
|
|
1920
|
+
}
|
|
1884
1921
|
// --- Read + validate the plan sidecar (no network yet) -------------------
|
|
1885
1922
|
let raw;
|
|
1886
1923
|
try {
|
|
@@ -1912,18 +1949,137 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1912
1949
|
return 1;
|
|
1913
1950
|
}
|
|
1914
1951
|
const localHash = hashPlan(plan);
|
|
1952
|
+
// Resolve the selected target and live-run identity before choosing a policy.
|
|
1953
|
+
// The offline local-only path keeps its no-network contract.
|
|
1954
|
+
let access;
|
|
1955
|
+
let selectedProfilePolicyPath = handoff?.profile?.policyRef?.path;
|
|
1956
|
+
if (opts.localOnly && !handoff) {
|
|
1957
|
+
const profileName = opts.launchProfile ?? deps.env.BAPI_EPIC_LAUNCH_PROFILE?.trim();
|
|
1958
|
+
if (profileName) {
|
|
1959
|
+
const profileRead = await readEpicLaunchProfile(profileName, {
|
|
1960
|
+
env: deps.env,
|
|
1961
|
+
homedir: deps.homedir,
|
|
1962
|
+
readFile: deps.readFile,
|
|
1963
|
+
host: os.hostname(),
|
|
1964
|
+
});
|
|
1965
|
+
if (!profileRead.ok) {
|
|
1966
|
+
deps.errorLog(profileRead.message);
|
|
1967
|
+
return 1;
|
|
1968
|
+
}
|
|
1969
|
+
selectedProfilePolicyPath = profileRead.profile.policyRef?.path;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
let existingRunId = null;
|
|
1973
|
+
let existingStatus = null;
|
|
1974
|
+
let existingBaseBranch = null;
|
|
1975
|
+
let existingReviewPolicy = null;
|
|
1976
|
+
// BAPI-754: the live run's SERVER-REDACTED stored policy, kept for the reuse
|
|
1977
|
+
// comparison. Redacted is the only form a client can ever see.
|
|
1978
|
+
let existingPolicyJson = null;
|
|
1979
|
+
if (!opts.localOnly) {
|
|
1980
|
+
// --- Resolve access ------------------------------------------------------
|
|
1981
|
+
// BAPI-1260: a caller that selected the epic-launch contract (drive-epic
|
|
1982
|
+
// forwards --launch-profile / --base-url) gets the target-first resolver and
|
|
1983
|
+
// the bound credential; the legacy resolver is kept byte-for-byte otherwise.
|
|
1984
|
+
const accessDeps = {
|
|
1985
|
+
env: deps.env,
|
|
1986
|
+
cwd: deps.cwd,
|
|
1987
|
+
homedir: deps.homedir,
|
|
1988
|
+
platform: deps.platform,
|
|
1989
|
+
readFile: deps.readFile,
|
|
1990
|
+
stat: deps.stat,
|
|
1991
|
+
repoName: opts.repo,
|
|
1992
|
+
};
|
|
1993
|
+
const accessResult = handoff
|
|
1994
|
+
? { ok: true, access: handoff.access, profile: handoff.profile }
|
|
1995
|
+
: opts.launchProfile !== undefined || opts.baseUrl !== undefined || Boolean(deps.env.BAPI_EPIC_LAUNCH_PROFILE?.trim())
|
|
1996
|
+
? await resolveEpicLaunchBridgeApiAccess({
|
|
1997
|
+
...(opts.launchProfile ? { profileName: opts.launchProfile } : {}),
|
|
1998
|
+
...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
|
|
1999
|
+
}, accessDeps)
|
|
2000
|
+
: await resolveConductorBridgeApiAccess(accessDeps);
|
|
2001
|
+
if (!accessResult.ok) {
|
|
2002
|
+
deps.errorLog(`Cannot reach the Bridge API: ${accessResult.error}`);
|
|
2003
|
+
return 1;
|
|
2004
|
+
}
|
|
2005
|
+
access = accessResult.access;
|
|
2006
|
+
say(`Epic: ${opts.epicKey}`);
|
|
2007
|
+
say(`Repo: ${access.repoName}`);
|
|
2008
|
+
say(`Plan: v${plan.plan_version}, ${plan.nodes.length} node(s), ${plan.edges.length} edge(s)`);
|
|
2009
|
+
say(`Local hash: ${localHash}`);
|
|
2010
|
+
for (const w of warnings)
|
|
2011
|
+
say(` [warn] ${w}`);
|
|
2012
|
+
selectedProfilePolicyPath = "profile" in accessResult
|
|
2013
|
+
? accessResult.profile?.policyRef?.path
|
|
2014
|
+
: undefined;
|
|
2015
|
+
// --- Step 0: pre-check ---------------------------------------------------
|
|
2016
|
+
// Never create on an ambiguous read. A wrong answer here mints a duplicate run.
|
|
2017
|
+
try {
|
|
2018
|
+
const state = await fetchEpicRunState(access, opts.epicKey, deps.fetch);
|
|
2019
|
+
existingRunId = state.epic_run?.epic_run_id ?? null;
|
|
2020
|
+
existingStatus = state.epic_run?.status ?? null;
|
|
2021
|
+
const existingPolicy = state.epic_run?.policy_json;
|
|
2022
|
+
existingPolicyJson = existingPolicy ?? null;
|
|
2023
|
+
const existingBase = existingPolicy && typeof existingPolicy === "object"
|
|
2024
|
+
? existingPolicy.base_branch
|
|
2025
|
+
: undefined;
|
|
2026
|
+
existingBaseBranch = typeof existingBase === "string" && existingBase.trim() !== ""
|
|
2027
|
+
? existingBase
|
|
2028
|
+
: null;
|
|
2029
|
+
// BAPI-679: read the existing run's review policy for the conflict guard
|
|
2030
|
+
// below. Absent/malformed reads as null — "the resolver's default applies" —
|
|
2031
|
+
// which is deliberately NOT the same as an explicitly selected source.
|
|
2032
|
+
const existingReview = existingPolicy && typeof existingPolicy === "object"
|
|
2033
|
+
? existingPolicy.review_policy
|
|
2034
|
+
: undefined;
|
|
2035
|
+
const existingReviewSource = existingReview && typeof existingReview === "object"
|
|
2036
|
+
? existingReview.source
|
|
2037
|
+
: undefined;
|
|
2038
|
+
existingReviewPolicy =
|
|
2039
|
+
typeof existingReviewSource === "string" && existingReviewSource.trim() !== ""
|
|
2040
|
+
? existingReviewSource
|
|
2041
|
+
: null;
|
|
2042
|
+
}
|
|
2043
|
+
catch (err) {
|
|
2044
|
+
if (err instanceof ConductorBridgeApiError && err.status === 404) {
|
|
2045
|
+
existingRunId = null; // No live run — the normal first-run path.
|
|
2046
|
+
}
|
|
2047
|
+
else if (err instanceof ConductorBridgeApiError && err.status === 409) {
|
|
2048
|
+
deps.errorLog(`Epic ${opts.epicKey} has MULTIPLE active runs — it is wedged, and every ` +
|
|
2049
|
+
`plan call will keep failing. Abandon the duplicate before retrying:\n` +
|
|
2050
|
+
` epic-implementer abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
|
|
2051
|
+
`Detail: ${errorDetail(err)}`);
|
|
2052
|
+
return 1;
|
|
2053
|
+
}
|
|
2054
|
+
else {
|
|
2055
|
+
deps.errorLog(`Could not read the epic run state, so creating one would risk a duplicate ` +
|
|
2056
|
+
`(which wedges the epic permanently). Refusing to continue.\n` +
|
|
2057
|
+
`Detail: ${errorDetail(err)}`);
|
|
2058
|
+
return 1;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
1915
2062
|
// --- Read the policy file (still no network) ------------------------------
|
|
1916
2063
|
// BAPI-754: parsed here so a malformed file, or a contradiction between the
|
|
1917
2064
|
// file and the CLI flags, fails before credentials are resolved or any request
|
|
1918
2065
|
// is dispatched.
|
|
1919
2066
|
let filePolicy;
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2067
|
+
const selectedPolicyPath = opts.policyFile ?? (existingRunId === null ? selectedProfilePolicyPath : undefined);
|
|
2068
|
+
const selectedPolicySource = existingRunId !== null
|
|
2069
|
+
? "stored" : opts.policyFile !== undefined ? "explicit" : selectedProfilePolicyPath !== undefined ? "profile" : "default";
|
|
2070
|
+
if (selectedPolicyPath !== undefined) {
|
|
2071
|
+
const pinned = handoff ? getPinnedEpicLaunchPolicy(handoff.selection) : undefined;
|
|
2072
|
+
if (pinned !== undefined) {
|
|
2073
|
+
filePolicy = pinned;
|
|
2074
|
+
}
|
|
2075
|
+
else {
|
|
2076
|
+
const policyRead = await readSetupEpicPolicyFile(selectedPolicyPath, deps.readFile);
|
|
2077
|
+
if (!policyRead.ok) {
|
|
2078
|
+
deps.errorLog(policyRead.error);
|
|
2079
|
+
return 1;
|
|
2080
|
+
}
|
|
2081
|
+
filePolicy = policyRead.policy;
|
|
1925
2082
|
}
|
|
1926
|
-
filePolicy = policyRead.policy;
|
|
1927
2083
|
}
|
|
1928
2084
|
const policyDeclaresBranch = filePolicy !== undefined &&
|
|
1929
2085
|
["base_branch", "baseBranch"].some((key) => key in filePolicy && filePolicy[key] !== undefined && filePolicy[key] !== null);
|
|
@@ -1953,7 +2109,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1953
2109
|
const policyGate = preflightSetupEpicPolicy({
|
|
1954
2110
|
intoBase: opts.intoBase,
|
|
1955
2111
|
attended: opts.attended,
|
|
1956
|
-
policyFile:
|
|
2112
|
+
policyFile: selectedPolicyPath,
|
|
1957
2113
|
filePolicy,
|
|
1958
2114
|
policyDeclaresBranch,
|
|
1959
2115
|
featureBranch,
|
|
@@ -1991,42 +2147,14 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1991
2147
|
plan_version: plan.plan_version,
|
|
1992
2148
|
local_plan_hash: localHash,
|
|
1993
2149
|
server_validated: false,
|
|
2150
|
+
feature_branch: opts.intoBase ? null : resolveEffectiveFeatureBranch(featureBranch, policyGate.policy) ?? null,
|
|
1994
2151
|
policy_applied: "absent",
|
|
2152
|
+
effective_policy: effectivePolicyFacts(policyGate.policy, selectedPolicySource),
|
|
1995
2153
|
warnings,
|
|
1996
2154
|
}, null, 2));
|
|
1997
2155
|
}
|
|
1998
2156
|
return 0;
|
|
1999
2157
|
}
|
|
2000
|
-
// --- Resolve access ------------------------------------------------------
|
|
2001
|
-
// BAPI-1260: a caller that selected the epic-launch contract (drive-epic
|
|
2002
|
-
// forwards --launch-profile / --base-url) gets the target-first resolver and
|
|
2003
|
-
// the bound credential; the legacy resolver is kept byte-for-byte otherwise.
|
|
2004
|
-
const accessDeps = {
|
|
2005
|
-
env: deps.env,
|
|
2006
|
-
cwd: deps.cwd,
|
|
2007
|
-
homedir: deps.homedir,
|
|
2008
|
-
platform: deps.platform,
|
|
2009
|
-
readFile: deps.readFile,
|
|
2010
|
-
stat: deps.stat,
|
|
2011
|
-
repoName: opts.repo,
|
|
2012
|
-
};
|
|
2013
|
-
const accessResult = opts.launchProfile !== undefined || opts.baseUrl !== undefined
|
|
2014
|
-
? await resolveEpicLaunchBridgeApiAccess({
|
|
2015
|
-
...(opts.launchProfile ? { profileName: opts.launchProfile } : {}),
|
|
2016
|
-
...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
|
|
2017
|
-
}, accessDeps)
|
|
2018
|
-
: await resolveConductorBridgeApiAccess(accessDeps);
|
|
2019
|
-
if (!accessResult.ok) {
|
|
2020
|
-
deps.errorLog(`Cannot reach the Bridge API: ${accessResult.error}`);
|
|
2021
|
-
return 1;
|
|
2022
|
-
}
|
|
2023
|
-
const access = accessResult.access;
|
|
2024
|
-
say(`Epic: ${opts.epicKey}`);
|
|
2025
|
-
say(`Repo: ${access.repoName}`);
|
|
2026
|
-
say(`Plan: v${plan.plan_version}, ${plan.nodes.length} node(s), ${plan.edges.length} edge(s)`);
|
|
2027
|
-
say(`Local hash: ${localHash}`);
|
|
2028
|
-
for (const w of warnings)
|
|
2029
|
-
say(` [warn] ${w}`);
|
|
2030
2158
|
// --- Branch strategy (BAPI-1009) ----------------------------------------
|
|
2031
2159
|
// Already resolved above, before the network was touched. Reported HERE, ahead
|
|
2032
2160
|
// of any secondary policy detail, because it is the one decision that changes
|
|
@@ -2083,6 +2211,11 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2083
2211
|
reviewPolicy: opts.reviewPolicy,
|
|
2084
2212
|
mergeExecutionMode: declaredMergeExecutionMode,
|
|
2085
2213
|
});
|
|
2214
|
+
const selectedEffectivePolicyFacts = effectivePolicyFacts(existingRunId !== null ? existingPolicyJson : composedForCreate, selectedPolicySource);
|
|
2215
|
+
say(`Policy: effective ${selectedEffectivePolicyFacts.source}; serial execution; ` +
|
|
2216
|
+
`caps ${selectedEffectivePolicyFacts.caps ? JSON.stringify(selectedEffectivePolicyFacts.caps) : "unavailable"}; ` +
|
|
2217
|
+
`timeouts ${selectedEffectivePolicyFacts.job_timeouts ? JSON.stringify(selectedEffectivePolicyFacts.job_timeouts) : "unavailable"}; ` +
|
|
2218
|
+
`channel ${selectedEffectivePolicyFacts.escalation_channel}`);
|
|
2086
2219
|
// BAPI-1133 (A2). Reported only when THIS command stamped the key — i.e. it
|
|
2087
2220
|
// is present in the composed policy and was absent from whatever the
|
|
2088
2221
|
// operator supplied — as low-emphasis policy metadata, mirroring the
|
|
@@ -2118,59 +2251,6 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2118
2251
|
say(`Feature: ${effectiveFeatureBranch} (from the policy file; cut on origin with local ` +
|
|
2119
2252
|
`git at the canonical indexed commit; validated server-side at approval)`);
|
|
2120
2253
|
}
|
|
2121
|
-
// --- Step 0: pre-check ---------------------------------------------------
|
|
2122
|
-
// Never create on an ambiguous read. A wrong answer here mints a duplicate run.
|
|
2123
|
-
let existingRunId = null;
|
|
2124
|
-
let existingStatus = null;
|
|
2125
|
-
let existingBaseBranch = null;
|
|
2126
|
-
let existingReviewPolicy = null;
|
|
2127
|
-
// BAPI-754: the live run's SERVER-REDACTED stored policy, kept for the reuse
|
|
2128
|
-
// comparison. Redacted is the only form a client can ever see.
|
|
2129
|
-
let existingPolicyJson = null;
|
|
2130
|
-
try {
|
|
2131
|
-
const state = await fetchEpicRunState(access, opts.epicKey, deps.fetch);
|
|
2132
|
-
existingRunId = state.epic_run?.epic_run_id ?? null;
|
|
2133
|
-
existingStatus = state.epic_run?.status ?? null;
|
|
2134
|
-
const existingPolicy = state.epic_run?.policy_json;
|
|
2135
|
-
existingPolicyJson = existingPolicy ?? null;
|
|
2136
|
-
const existingBase = existingPolicy && typeof existingPolicy === "object"
|
|
2137
|
-
? existingPolicy.base_branch
|
|
2138
|
-
: undefined;
|
|
2139
|
-
existingBaseBranch = typeof existingBase === "string" && existingBase.trim() !== ""
|
|
2140
|
-
? existingBase
|
|
2141
|
-
: null;
|
|
2142
|
-
// BAPI-679: read the existing run's review policy for the conflict guard
|
|
2143
|
-
// below. Absent/malformed reads as null — "the resolver's default applies" —
|
|
2144
|
-
// which is deliberately NOT the same as an explicitly selected source.
|
|
2145
|
-
const existingReview = existingPolicy && typeof existingPolicy === "object"
|
|
2146
|
-
? existingPolicy.review_policy
|
|
2147
|
-
: undefined;
|
|
2148
|
-
const existingReviewSource = existingReview && typeof existingReview === "object"
|
|
2149
|
-
? existingReview.source
|
|
2150
|
-
: undefined;
|
|
2151
|
-
existingReviewPolicy =
|
|
2152
|
-
typeof existingReviewSource === "string" && existingReviewSource.trim() !== ""
|
|
2153
|
-
? existingReviewSource
|
|
2154
|
-
: null;
|
|
2155
|
-
}
|
|
2156
|
-
catch (err) {
|
|
2157
|
-
if (err instanceof ConductorBridgeApiError && err.status === 404) {
|
|
2158
|
-
existingRunId = null; // No live run — the normal first-run path.
|
|
2159
|
-
}
|
|
2160
|
-
else if (err instanceof ConductorBridgeApiError && err.status === 409) {
|
|
2161
|
-
deps.errorLog(`Epic ${opts.epicKey} has MULTIPLE active runs — it is wedged, and every ` +
|
|
2162
|
-
`plan call will keep failing. Abandon the duplicate before retrying:\n` +
|
|
2163
|
-
` epic-implementer abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
|
|
2164
|
-
`Detail: ${errorDetail(err)}`);
|
|
2165
|
-
return 1;
|
|
2166
|
-
}
|
|
2167
|
-
else {
|
|
2168
|
-
deps.errorLog(`Could not read the epic run state, so creating one would risk a duplicate ` +
|
|
2169
|
-
`(which wedges the epic permanently). Refusing to continue.\n` +
|
|
2170
|
-
`Detail: ${errorDetail(err)}`);
|
|
2171
|
-
return 1;
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
2254
|
// --- Feature-branch conflict guard against an existing live run ----------
|
|
2175
2255
|
// A selected branch that disagrees with the existing run's stored base branch
|
|
2176
2256
|
// must fail closed rather than patch or silently change that run. A matching
|
|
@@ -2498,6 +2578,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2498
2578
|
// "absent"; server_validated says whether the check was authoritative.
|
|
2499
2579
|
server_validated: validated !== null,
|
|
2500
2580
|
policy_applied: "absent",
|
|
2581
|
+
effective_policy: selectedEffectivePolicyFacts,
|
|
2501
2582
|
...(validated
|
|
2502
2583
|
? {
|
|
2503
2584
|
server_plan_hash: validated.planHash,
|
|
@@ -2569,6 +2650,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2569
2650
|
plan_approved: false,
|
|
2570
2651
|
server_validated: validated !== null,
|
|
2571
2652
|
policy_applied: "absent",
|
|
2653
|
+
effective_policy: selectedEffectivePolicyFacts,
|
|
2572
2654
|
warnings,
|
|
2573
2655
|
};
|
|
2574
2656
|
// BAPI-850: the EFFECTIVE feature branch (flag or policy file), so a
|
|
@@ -2946,10 +3028,11 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2946
3028
|
deps.errorLog(`The plan was NOT approved: this repository is no longer authorized for ` +
|
|
2947
3029
|
`unattended runs. The run WAS created and is left in 'planning' with no ` +
|
|
2948
3030
|
`activation and no executor dispatch — nothing was half-applied.\n` +
|
|
2949
|
-
`
|
|
2950
|
-
`
|
|
2951
|
-
`
|
|
2952
|
-
`supported operator flow
|
|
3031
|
+
`Turn 'Allow unattended Epic Implementer runs' back on in the repository's ` +
|
|
3032
|
+
`Setup page in the Bridge admin panel and re-run setup-epic, or ` +
|
|
3033
|
+
`deliberately convert this run to an attended posture through the ` +
|
|
3034
|
+
`supported operator flow. If it is already on, contact Bridge support.` +
|
|
3035
|
+
`\nDetail: ${errorDetail(err)}`);
|
|
2953
3036
|
}
|
|
2954
3037
|
else {
|
|
2955
3038
|
deps.errorLog(`Failed to approve the plan: ${errorDetail(err)}`);
|
|
@@ -3117,14 +3200,14 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
3117
3200
|
else {
|
|
3118
3201
|
say("The server-side reconciler will pick it up within ~30s.");
|
|
3119
3202
|
}
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3203
|
+
if (handoff) {
|
|
3204
|
+
say("The launcher starts or reuses the executor appropriate for this run.");
|
|
3205
|
+
}
|
|
3206
|
+
else {
|
|
3207
|
+
say("To execute this run's claimed jobs on this machine, run:");
|
|
3208
|
+
say(` npx -y ${MCP_PACKAGE_NAME} executor --repo ${access.repoName} ` +
|
|
3209
|
+
`--epic-run-id ${result.epic_run_id}`);
|
|
3210
|
+
}
|
|
3128
3211
|
}
|
|
3129
3212
|
return exitCode;
|
|
3130
3213
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* prevent the others from running.
|
|
7
7
|
*/
|
|
8
8
|
import { SFCC_VERSIONS, getSfccVersionConfig } from "./config.js";
|
|
9
|
+
import { publishSfccVersion } from "./tools-configured.js";
|
|
9
10
|
import { getAmToken } from "./client.js";
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Implementation
|
|
@@ -38,6 +39,7 @@ export async function sfccSetupStatusTool(deps) {
|
|
|
38
39
|
if (apiKeyOk && repoOk) {
|
|
39
40
|
try {
|
|
40
41
|
resolvedVersion = await getSfccVersionConfig(deps.buildGetUrl, deps.getGetHeaders, deps.repoName);
|
|
42
|
+
publishSfccVersion(resolvedVersion);
|
|
41
43
|
if (resolvedVersion === null) {
|
|
42
44
|
versionStatus = "✗ Not set (configure the 'version' field in Bridge API project settings)";
|
|
43
45
|
}
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
* no per-tool opt-in and no exemption flag.
|
|
63
63
|
*/
|
|
64
64
|
import { SFCC_VERSIONS, getSfccVersionConfig } from "./config.js";
|
|
65
|
+
import { publishSfccVersion } from "./tools-configured.js";
|
|
65
66
|
import { resolveSfccCredentials } from "./credentials.js";
|
|
66
67
|
import { formatSfccFailure } from "./sfcc-result.js";
|
|
67
68
|
// ---------------------------------------------------------------------------
|
|
@@ -385,6 +386,7 @@ export function withSfccGate(deps, handler) {
|
|
|
385
386
|
}
|
|
386
387
|
// (a) Read /config-field/version
|
|
387
388
|
const version = await getSfccVersionConfig(deps.buildGetUrl, deps.getGetHeaders, deps.repoName);
|
|
389
|
+
publishSfccVersion(version);
|
|
388
390
|
if (version === null) {
|
|
389
391
|
return notConfigured("bridge-auth", "Could not read the SFCC version from Bridge API (/config-field/version). " +
|
|
390
392
|
"Ensure your Bridge API key is set and the repo is configured. " +
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Process-local, secret-free SFCC setup verdict for authenticated Bridge requests. */
|
|
2
|
+
import { access } from "fs/promises";
|
|
3
|
+
import { constants } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { resolveSfccCredentials } from "./credentials.js";
|
|
6
|
+
import { getSfccVersionConfig, SFCC_VERSIONS } from "./config.js";
|
|
7
|
+
const DIAGNOSTIC = "SFCC tools header omitted: version lookup did not establish SFCC setup; check setup status.";
|
|
8
|
+
/** Existence and readability are enough to establish the local gate. */
|
|
9
|
+
export async function hasRootDwJson() {
|
|
10
|
+
try {
|
|
11
|
+
await access(path.join(process.cwd(), "dw.json"), constants.R_OK);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Isolated state is injectable for offline tests; the MCP uses one instance. */
|
|
19
|
+
export function createSfccToolsHeaderState(deps) {
|
|
20
|
+
let credentialVerdict;
|
|
21
|
+
let knownVersion;
|
|
22
|
+
let lookupStarted = false;
|
|
23
|
+
let diagnosticPrinted = false;
|
|
24
|
+
function getCredentialVerdict() {
|
|
25
|
+
credentialVerdict ??= Promise.resolve().then(() => deps.resolveCredentials(deps.env, {
|
|
26
|
+
...deps.credentialDeps,
|
|
27
|
+
writeFile: async () => { },
|
|
28
|
+
mkdir: async () => { },
|
|
29
|
+
})).then((result) => result.ok ? "yes" : "no")
|
|
30
|
+
.catch(() => "no");
|
|
31
|
+
return credentialVerdict;
|
|
32
|
+
}
|
|
33
|
+
function publishVersion(version) {
|
|
34
|
+
if (version !== null && knownVersion === undefined)
|
|
35
|
+
knownVersion = version;
|
|
36
|
+
if (version !== null && SFCC_VERSIONS.includes(version))
|
|
37
|
+
knownVersion = version;
|
|
38
|
+
}
|
|
39
|
+
function reportUnresolved() {
|
|
40
|
+
if (diagnosticPrinted)
|
|
41
|
+
return;
|
|
42
|
+
diagnosticPrinted = true;
|
|
43
|
+
deps.diagnostic(DIAGNOSTIC);
|
|
44
|
+
}
|
|
45
|
+
async function withHeader(headers, options) {
|
|
46
|
+
if (options.skipSfccToolsHeader)
|
|
47
|
+
return headers;
|
|
48
|
+
try {
|
|
49
|
+
const localFile = await deps.hasDwJson();
|
|
50
|
+
if (knownVersion === undefined && !lookupStarted && options.repoName) {
|
|
51
|
+
lookupStarted = true;
|
|
52
|
+
void deps.getVersion(options.buildGetUrl, options.getLookupHeaders, options.repoName)
|
|
53
|
+
.then((version) => {
|
|
54
|
+
if (version !== null)
|
|
55
|
+
publishVersion(version);
|
|
56
|
+
if (knownVersion === undefined) {
|
|
57
|
+
knownVersion = null;
|
|
58
|
+
if (!localFile)
|
|
59
|
+
reportUnresolved();
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.catch(() => {
|
|
63
|
+
if (knownVersion === undefined) {
|
|
64
|
+
knownVersion = null;
|
|
65
|
+
if (!localFile)
|
|
66
|
+
reportUnresolved();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const isSfcc = knownVersion !== undefined && knownVersion !== null
|
|
71
|
+
&& SFCC_VERSIONS.includes(knownVersion);
|
|
72
|
+
if (!localFile && !isSfcc) {
|
|
73
|
+
if (!options.repoName)
|
|
74
|
+
reportUnresolved();
|
|
75
|
+
return headers;
|
|
76
|
+
}
|
|
77
|
+
headers["X-Bridge-SFCC-Tools"] = await getCredentialVerdict();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Optional enrichment cannot interrupt authenticated requests.
|
|
81
|
+
}
|
|
82
|
+
return headers;
|
|
83
|
+
}
|
|
84
|
+
return { getCredentialVerdict, publishVersion, withHeader };
|
|
85
|
+
}
|
|
86
|
+
const processState = createSfccToolsHeaderState({
|
|
87
|
+
hasDwJson: hasRootDwJson,
|
|
88
|
+
resolveCredentials: (env, credentialDeps) => resolveSfccCredentials(undefined, env, credentialDeps),
|
|
89
|
+
credentialDeps: {},
|
|
90
|
+
env: process.env,
|
|
91
|
+
getVersion: getSfccVersionConfig,
|
|
92
|
+
diagnostic: (message) => console.error(message),
|
|
93
|
+
});
|
|
94
|
+
export const getSfccCredentialVerdict = processState.getCredentialVerdict;
|
|
95
|
+
export const publishSfccVersion = processState.publishVersion;
|
|
96
|
+
export const withSfccToolsHeader = processState.withHeader;
|