@bridge_gpt/mcp-server 0.2.66 → 0.2.68

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 (171) hide show
  1. package/README.md +421 -83
  2. package/build/agent-capabilities/probes.js +15 -5
  3. package/build/agent-launchers/claude-executor-adapter.js +70 -16
  4. package/build/agent-launchers/executor-adapter.js +7 -4
  5. package/build/agents.generated.js +22 -2
  6. package/build/bitbucket-workspace-remote.js +136 -0
  7. package/build/bootstrap-project-identity.js +24 -0
  8. package/build/bounded-wait.js +67 -10
  9. package/build/bridge-permissions.js +829 -0
  10. package/build/claude-settings.js +18 -0
  11. package/build/cli-help.js +21 -0
  12. package/build/command-assets-doctor.js +24 -70
  13. package/build/command-provisioning.js +29 -5
  14. package/build/commands.generated.js +12 -12
  15. package/build/conductor/bridge-api-client.js +857 -1
  16. package/build/conductor/bring-up-facts.js +1 -1
  17. package/build/conductor/command-drift-production.js +92 -0
  18. package/build/conductor/command-drift.js +89 -0
  19. package/build/conductor/doctor.js +72 -29
  20. package/build/conductor/epic-branch-ci-inspection.js +1375 -0
  21. package/build/conductor/epic-implementer-cli.js +98 -28
  22. package/build/conductor/errors.js +4 -3
  23. package/build/conductor/gate-discovery.js +890 -0
  24. package/build/conductor/guided-repair.js +6 -2
  25. package/build/conductor/hook-runtime-qualification.js +1 -1
  26. package/build/conductor/install-doctor.js +15 -3
  27. package/build/conductor/json-output.js +63 -0
  28. package/build/conductor/merge-execution-contract.js +27 -8
  29. package/build/conductor/merge-execution-eligibility.js +3 -2
  30. package/build/conductor/pr-ci-producer.js +1 -1
  31. package/build/conductor/readiness-cli.js +113 -12
  32. package/build/conductor/readiness-sections.js +410 -9
  33. package/build/conductor/readiness.js +388 -33
  34. package/build/conductor/recovery-cli.js +310 -12
  35. package/build/conductor/recovery-operations.js +167 -3
  36. package/build/conductor/repair-contract.js +54 -3
  37. package/build/conductor/repository-workflow-remedy.js +125 -0
  38. package/build/conductor/tools.js +2 -2
  39. package/build/conductor-bundle-artifacts.js +8 -0
  40. package/build/connect-bitbucket-api.js +41 -6
  41. package/build/connect-bitbucket.js +106 -33
  42. package/build/credential-store.js +207 -47
  43. package/build/docs.generated.js +62 -3
  44. package/build/doctor.js +92 -1
  45. package/build/drive-epic-key.js +6 -5
  46. package/build/drive-epic.js +129 -61
  47. package/build/epic-coherence-input.js +2 -1
  48. package/build/epic-coherence-input.test-fixtures.js +4 -3
  49. package/build/epic-implementer-bin.js +1379 -28
  50. package/build/epic-implementer-bundle-cli.js +2 -2
  51. package/build/epic-workarounds-path.js +39 -0
  52. package/build/executor/cli.js +33 -2
  53. package/build/executor/completion-contract.js +66 -10
  54. package/build/executor/completion-supervision.js +4 -0
  55. package/build/executor/deps.js +4 -1
  56. package/build/executor/env.js +19 -0
  57. package/build/executor/gate-qualification-job.js +499 -0
  58. package/build/executor/guarded-fast-forward-push.js +129 -0
  59. package/build/executor/heartbeat.js +1 -1
  60. package/build/executor/http-client.js +20 -0
  61. package/build/executor/integration-job.js +122 -8
  62. package/build/executor/job-errors.js +33 -1
  63. package/build/executor/job-runner.js +813 -87
  64. package/build/executor/likely-tests.js +290 -0
  65. package/build/executor/live-worker-registry.js +1 -1
  66. package/build/executor/observation.js +37 -3
  67. package/build/executor/packaged-recipe.js +106 -0
  68. package/build/executor/permissions.js +115 -28
  69. package/build/executor/platform.js +1 -1
  70. package/build/executor/preflight.js +112 -26
  71. package/build/executor/process-containment.js +36 -12
  72. package/build/executor/process.js +15 -7
  73. package/build/executor/prompt-spec.js +33 -1
  74. package/build/executor/recently-failed-tests-file.js +71 -0
  75. package/build/executor/reconcile-overlap-job.js +41 -23
  76. package/build/executor/required-verification.js +1 -1
  77. package/build/executor/required-work-ledger.js +111 -18
  78. package/build/executor/resume-pre-spawn.js +2 -0
  79. package/build/executor/retirement.js +69 -0
  80. package/build/executor/runner.js +122 -13
  81. package/build/executor/service-unit.js +7 -1
  82. package/build/executor/test-clock.js +15 -0
  83. package/build/executor/types.js +32 -0
  84. package/build/executor/viewer-tabs.js +58 -8
  85. package/build/executor/worker-command.js +23 -7
  86. package/build/executor/worker-diagnostics.js +37 -0
  87. package/build/executor/worker-finalization.js +113 -34
  88. package/build/executor/worker-guard-hook.js +316 -58
  89. package/build/executor/worker-log.js +4 -3
  90. package/build/executor/worker-verification-guidance.js +37 -0
  91. package/build/executor/worktree.js +166 -8
  92. package/build/first-build-advisory.js +97 -0
  93. package/build/generated-assets-advisory.js +14 -0
  94. package/build/generated-assets-catalog.js +66 -0
  95. package/build/generated-assets-context.js +46 -0
  96. package/build/generated-assets-dependencies.js +26 -0
  97. package/build/generated-assets-doctor.js +50 -0
  98. package/build/generated-assets-health.js +65 -0
  99. package/build/generated-assets-host.js +17 -0
  100. package/build/generated-assets-inventory.js +17 -0
  101. package/build/generated-assets-paths.js +60 -0
  102. package/build/generated-assets-publisher.js +133 -0
  103. package/build/generated-assets-remedy.js +25 -0
  104. package/build/generated-assets-repair.js +72 -0
  105. package/build/generated-assets-session.js +71 -0
  106. package/build/generated-assets-test-support.js +35 -0
  107. package/build/index.js +1224 -1054
  108. package/build/init.js +54 -177
  109. package/build/install-bridge-conductor-gate.js +1372 -0
  110. package/build/install-bridge-conductor.js +296 -41
  111. package/build/install-bridge.js +547 -40
  112. package/build/mcp-install-state.js +50 -4
  113. package/build/merge-workflow-install.js +3 -4
  114. package/build/phase-context-git-probe.js +68 -0
  115. package/build/phase-result-artifacts.js +173 -5
  116. package/build/pipeline-utils.js +6 -2
  117. package/build/pipelines.generated.js +34 -160
  118. package/build/plan-execution-ledger.js +2 -32
  119. package/build/plane/alembic-head.js +12 -3
  120. package/build/plane/base-worktree-guard.js +147 -0
  121. package/build/plane/cli.js +217 -17
  122. package/build/plane/code-snapshot.js +468 -0
  123. package/build/plane/defaults.js +48 -3
  124. package/build/plane/endpoint.js +41 -0
  125. package/build/plane/manifest.js +136 -2
  126. package/build/plane/member-roster.js +94 -19
  127. package/build/plane/preflight.js +83 -2
  128. package/build/plane/process-cwd.js +67 -0
  129. package/build/plane/restart.js +542 -0
  130. package/build/plane/shutdown.js +31 -0
  131. package/build/plane/status.js +143 -8
  132. package/build/plane/supervisor.js +586 -179
  133. package/build/plane/types.js +54 -2
  134. package/build/printed-remedy-commands.js +52 -0
  135. package/build/readiness-check.js +1 -1
  136. package/build/readme.generated.js +1 -1
  137. package/build/repair-assets.js +93 -0
  138. package/build/review-generation.js +22 -0
  139. package/build/run-unit-tests-launcher.js +130 -49
  140. package/build/secret-redaction.js +33 -0
  141. package/build/setup-epic.js +152 -22
  142. package/build/sfcc/config.js +9 -0
  143. package/build/sfcc/credentials.js +100 -38
  144. package/build/sfcc/hostname.js +67 -0
  145. package/build/sfcc/permissions.js +17 -13
  146. package/build/sfcc/register.js +1 -1
  147. package/build/sfcc/tool-wrapper.js +39 -43
  148. package/build/sfcc/write-grants.js +17 -11
  149. package/build/sfcc/write-result.js +4 -3
  150. package/build/sfcc/writes-site-preference.js +20 -8
  151. package/build/start-tickets-prereqs.js +70 -16
  152. package/build/start-tickets.js +208 -36
  153. package/build/tool-error-envelope.js +61 -0
  154. package/build/tool-surface-gating.js +36 -0
  155. package/build/upgrade-cli.js +55 -1
  156. package/build/version.generated.js +3 -3
  157. package/build/visual-diff.js +22 -1
  158. package/build/worker-guard-hook-bin.js +4 -6
  159. package/build/worker-guard-hook-main.js +47 -0
  160. package/build/worktree-core.js +145 -6
  161. package/docs/EPIC-IMPLEMENTER-INSTALL.md +867 -0
  162. package/docs/EPIC-IMPLEMENTER.md +728 -0
  163. package/docs/install/mcp-tool-integrations.md +24 -15
  164. package/docs/install/sfcc-integration.md +12 -4
  165. package/package.json +3 -3
  166. package/pipelines/greenfield-setup.json +4 -128
  167. package/pipelines/implement-ticket.json +3 -2
  168. package/pipelines/plan-epic.json +0 -5
  169. package/pipelines/review-ticket.json +1 -1
  170. package/docs/CONDUCTOR.md +0 -605
  171. package/pipelines/idea-to-pr.json +0 -49
@@ -37,6 +37,7 @@
37
37
  * {@link resolveCredentialBundle}, and the durable source of truth is the
38
38
  * user-scoped `~/.config/bridge/credentials.json` store written here.
39
39
  */
40
+ import { validateBootstrapDisplayName, validateBootstrapRepoName } from "./bootstrap-project-identity.js";
40
41
  import path from "path";
41
42
  import { diagnosticComponent } from "./epic-launch-diagnostic.js";
42
43
  // ---------------------------------------------------------------------------
@@ -336,37 +337,30 @@ function defaultTempSuffix() {
336
337
  // ---------------------------------------------------------------------------
337
338
  /** Poll interval while waiting for a contended lock. */
338
339
  const LOCK_POLL_INTERVAL_MS = 50;
339
- /** Bounded wait before a held lock is treated as abandoned. */
340
+ /** Bounded acquisition wait; elapsed time never establishes abandonment. */
340
341
  const LOCK_TIMEOUT_MS = 5_000;
341
342
  /** Lock path, adjacent to the primary store. Never carries secret material. */
342
343
  export function getCredentialStoreLockPath(deps) {
343
344
  return `${getPrimaryCredentialStorePath(deps)}.lock`;
344
345
  }
345
346
  /**
346
- * Acquire the exclusive credential-store lock: an atomically created (`wx`) lock
347
- * file beside the store, with a bounded wait.
348
- *
349
- * When `deps.open` is absent there is no lock primitive available, so the caller
350
- * runs unlocked — exactly the pre-BAPI-606 behavior, preserved so existing
351
- * builders and in-memory test doubles keep working.
352
- *
353
- * Stale-lock recovery: a lock still held after {@link LOCK_TIMEOUT_MS} (orders of
354
- * magnitude longer than a legitimate sub-millisecond read-modify-write) is
355
- * treated as abandoned by a crashed process and stolen exactly once. The
356
- * alternative — refusing forever — wedges every future install behind a lock file
357
- * no user knows to delete.
347
+ * Exclusive creation with bounded fail-closed contention. No writer steals or
348
+ * replaces a held lock; only the successful owner can release it. Abandonment
349
+ * cannot be established by this portable primitive, so recovery requires support.
358
350
  */
359
351
  async function acquireCredentialStoreLock(deps) {
360
352
  const open = deps.open;
361
- if (!open)
362
- return { ok: true, release: async () => { } };
353
+ if (!open || !deps.unlink)
354
+ return { ok: false, error: "Exclusive credential storage is unavailable. Resume from the original folder with installation support." };
363
355
  const lockPath = getCredentialStoreLockPath(deps);
364
356
  const isPosix = deps.platform !== "win32";
365
357
  const now = deps.now ?? (() => Date.now());
366
358
  const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
359
+ let released = false;
367
360
  const release = async () => {
368
- if (!deps.unlink)
361
+ if (released || !deps.unlink)
369
362
  return;
363
+ released = true;
370
364
  try {
371
365
  await deps.unlink(lockPath);
372
366
  }
@@ -403,14 +397,9 @@ async function acquireCredentialStoreLock(deps) {
403
397
  break;
404
398
  await sleep(LOCK_POLL_INTERVAL_MS);
405
399
  }
406
- // Bounded wait exhausted — steal the abandoned lock, once.
407
- await release();
408
- const stolen = await tryAcquire();
409
- if (stolen.ok)
410
- return { ok: true, release };
411
400
  return {
412
401
  ok: false,
413
- error: `Timed out waiting for the credentials lock at ${lockPath} (another install may be running).`,
402
+ error: `Timed out waiting for exclusive credential storage at ${lockPath}. Another installer may still be running. Resume from the original folder with installation support; preserve recovery proof.`,
414
403
  };
415
404
  }
416
405
  /**
@@ -418,7 +407,9 @@ async function acquireCredentialStoreLock(deps) {
418
407
  * lock is always released, and `onLockError` maps an acquisition failure into the
419
408
  * caller's own (secret-free) result shape.
420
409
  */
421
- export async function withCredentialStoreLock(deps, fn, onLockError) {
410
+ export async function withCredentialStoreLock(deps, fn, onLockError, requireExclusive = true) {
411
+ if (!requireExclusive && !deps.open)
412
+ return fn();
422
413
  const lock = await acquireCredentialStoreLock(deps);
423
414
  if (!lock.ok)
424
415
  return onLockError(lock.error);
@@ -457,7 +448,7 @@ export async function durablyReplaceCredentialStoreJson(primaryPath, value, deps
457
448
  let handle;
458
449
  try {
459
450
  await deps.mkdir(dir, { recursive: true });
460
- handle = await open(tempPath, "w", isPosix ? 0o600 : undefined);
451
+ handle = await open(tempPath, "wx", isPosix ? 0o600 : undefined);
461
452
  await handle.writeFile(json, { encoding: "utf-8" });
462
453
  await handle.sync();
463
454
  await handle.close();
@@ -490,19 +481,22 @@ export async function durablyReplaceCredentialStoreJson(primaryPath, value, deps
490
481
  error: `Failed to durably write the credentials file at ${primaryPath}.`,
491
482
  };
492
483
  }
493
- try {
494
- const dirHandle = await open(dir, "r");
484
+ // Windows cannot open directory handles through Node's portable fs API.
485
+ // File sync plus atomic rename still apply; directory durability is limited.
486
+ if (deps.platform !== "win32") {
495
487
  try {
496
- await dirHandle.sync();
488
+ const dirHandle = await open(dir, "r");
489
+ try {
490
+ await dirHandle.sync();
491
+ }
492
+ finally {
493
+ await dirHandle.close();
494
+ }
497
495
  }
498
- finally {
499
- await dirHandle.close();
496
+ catch {
497
+ return { ok: false, kind: "post-rename-uncertain", error: "Credential replacement completed but durability could not be confirmed. Preserve recovery proof and rerun from the original folder." };
500
498
  }
501
499
  }
502
- catch {
503
- /* directory fsync is unsupported on some platforms; the file fsync above is
504
- the load-bearing one and already succeeded. */
505
- }
506
500
  return { ok: true };
507
501
  }
508
502
  // ---------------------------------------------------------------------------
@@ -597,8 +591,10 @@ async function loadStoreForMutation(deps) {
597
591
  if (primary.state === "present") {
598
592
  return { ok: true, base: { ...primary.value } };
599
593
  }
600
- const seeded = await mergeFallbackCredentialStoreOnFirstPrimaryWrite(deps);
601
- return { ok: true, base: seeded.base };
594
+ const fallback = await readCredentialStoreJsonIfPresent(getFallbackCredentialStorePath(deps), deps);
595
+ if (fallback.state === "error")
596
+ return { ok: false, kind: fallback.kind, error: "Recovery storage could not be read. Preserve it and contact installation support." };
597
+ return { ok: true, base: fallback.state === "present" ? { ...fallback.value } : {} };
602
598
  }
603
599
  /** Does a real (non-empty) `BAPI_API_KEY` already exist for `bapi:<repo>`? */
604
600
  function hasExistingBapiKey(store, repoName) {
@@ -704,17 +700,8 @@ function storedPendingIsSelfServe(store, repoName) {
704
700
  * (an ordinary, fingerprint-only record) keeps the original fail-closed guidance,
705
701
  * because there the CLI genuinely cannot replay it.
706
702
  */
707
- function pendingConflictError(target, primaryPath, storedIsSelfServe) {
708
- if (storedIsSelfServe) {
709
- return (`A pending self-serve signup for a DIFFERENT invite already exists at ${target} in ` +
710
- `${primaryPath}. It is the only proof that can replay that signup, so it will not be ` +
711
- "overwritten. Re-run install-bridge and choose the email option — it will resume that " +
712
- "signup automatically. Do NOT remove the entry by hand.");
713
- }
714
- return (`A pending bootstrap-invite credential for a DIFFERENT invite already exists at ${target} ` +
715
- `in ${primaryPath}. It is the only proof that can replay that redemption, so it will not be ` +
716
- "overwritten. Complete that redemption first, or — only if you are certain its invite was " +
717
- "never exchanged — remove the entry from the store by hand.");
703
+ function pendingConflictError(_target, _primaryPath, _storedIsSelfServe) {
704
+ return "An earlier installation has saved recovery proof. Resume from its original folder with its original invite or signup flow, or contact installation support. Preserve the saved proof.";
718
705
  }
719
706
  /**
720
707
  * Generate (or REUSE) the bootstrap `key_secret` and durably persist it as a
@@ -1413,7 +1400,7 @@ export async function upsertBapiCredential(repoName, apiKey, deps) {
1413
1400
  };
1414
1401
  }
1415
1402
  return { ok: true, path: primaryPath, target, action, migratedFallback };
1416
- }, (error) => ({ ok: false, path: primaryPath, target, kind: "lock-error", error }));
1403
+ }, (error) => ({ ok: false, path: primaryPath, target, kind: "lock-error", error }), false);
1417
1404
  }
1418
1405
  // ---------------------------------------------------------------------------
1419
1406
  // Epic-launch credential binding (BAPI-1260)
@@ -1603,3 +1590,176 @@ export async function bindEpicLaunchCredential(repoName, targetBaseUrl, deps) {
1603
1590
  return { ok: true, target, action: previous === null ? "bound" : "rebound", path: primaryPath };
1604
1591
  }, (error) => ({ ok: false, target, kind: "lock-error", error }));
1605
1592
  }
1593
+ const modernRecoveryError = "Installation recovery is inconsistent or unavailable. Preserve proof and resume with the original folder and invite, or contact installation support.";
1594
+ function parseModernBootstrapRecord(entry) {
1595
+ if (!entry)
1596
+ return null;
1597
+ const raw = JSON.parse(entry.RECORD);
1598
+ if (raw.version !== 1 || raw.mode !== "server_assigned" || typeof raw.baseUrl !== "string" || typeof raw.folder !== "string" || typeof raw.label !== "string" || typeof raw.keySecret !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(raw.keySecret))
1599
+ throw new Error(modernRecoveryError);
1600
+ validateBootstrapDisplayName(raw.label);
1601
+ if (raw.fingerprint !== undefined && !/^[a-f0-9]{64}$/.test(raw.fingerprint))
1602
+ throw new Error(modernRecoveryError);
1603
+ if (raw.canonical !== undefined) {
1604
+ validateBootstrapRepoName(raw.canonical);
1605
+ validateBootstrapDisplayName(raw.serverLabel);
1606
+ }
1607
+ return raw;
1608
+ }
1609
+ export async function mutateModernBootstrapRecord(scope, deps, change, previousLocator) {
1610
+ const primaryPath = getPrimaryCredentialStorePath(deps);
1611
+ return withCredentialStoreLock(deps, async () => {
1612
+ try {
1613
+ const loaded = await loadStoreForMutation(deps);
1614
+ if (!loaded.ok)
1615
+ return { ok: false, error: modernRecoveryError };
1616
+ const store = loaded.base;
1617
+ const reference = store[scope.folderReference];
1618
+ const actualLocator = reference?.LOCATOR ?? scope.locator;
1619
+ if (reference && (reference.FOLDER !== scope.folder || reference.BASE_URL !== scope.baseUrl || (actualLocator !== scope.locator && actualLocator !== previousLocator)))
1620
+ throw new Error(modernRecoveryError);
1621
+ const record = parseModernBootstrapRecord(store[actualLocator]);
1622
+ if (reference && !record)
1623
+ throw new Error(modernRecoveryError);
1624
+ if (record && (!reference || record.folder !== scope.folder || record.baseUrl !== scope.baseUrl))
1625
+ throw new Error(modernRecoveryError);
1626
+ const next = change(record);
1627
+ if (next.folder !== scope.folder || next.baseUrl !== scope.baseUrl || (record && (record.keySecret !== next.keySecret || record.label !== next.label)))
1628
+ throw new Error(modernRecoveryError);
1629
+ const destination = store[scope.locator];
1630
+ if (actualLocator !== scope.locator && destination && JSON.stringify(parseModernBootstrapRecord(destination)) !== JSON.stringify(next))
1631
+ throw new Error(modernRecoveryError);
1632
+ const updated = { ...store, [scope.locator]: { RECORD: JSON.stringify(next) }, [scope.folderReference]: { LOCATOR: scope.locator, FOLDER: scope.folder, BASE_URL: scope.baseUrl } };
1633
+ if (actualLocator !== scope.locator)
1634
+ delete updated[actualLocator];
1635
+ const written = await durablyReplaceCredentialStoreJson(primaryPath, updated, deps);
1636
+ return written.ok ? { ok: true, record: next, path: primaryPath } : { ok: false, error: written.error };
1637
+ }
1638
+ catch {
1639
+ return { ok: false, error: modernRecoveryError };
1640
+ }
1641
+ }, () => ({ ok: false, error: modernRecoveryError }));
1642
+ }
1643
+ export async function lookupModernBootstrapRecord(scope, deps) {
1644
+ return withCredentialStoreLock(deps, async () => {
1645
+ try {
1646
+ const loaded = await loadStoreForMutation(deps);
1647
+ if (!loaded.ok)
1648
+ throw new Error(modernRecoveryError);
1649
+ const reference = loaded.base[scope.folderReference];
1650
+ if (!reference) {
1651
+ // A stray modern or legacy record could be a redeemed proof; never replace it.
1652
+ if (loaded.base[scope.locator])
1653
+ throw new Error(modernRecoveryError);
1654
+ return { ok: true, record: null, path: getPrimaryCredentialStorePath(deps) };
1655
+ }
1656
+ if (reference.FOLDER !== scope.folder || reference.BASE_URL !== scope.baseUrl)
1657
+ throw new Error(modernRecoveryError);
1658
+ const record = parseModernBootstrapRecord(loaded.base[reference.LOCATOR]);
1659
+ if (!record || record.folder !== scope.folder || record.baseUrl !== scope.baseUrl)
1660
+ throw new Error(modernRecoveryError);
1661
+ if (scope.locator.startsWith("bootstrap-pending:invite:") && reference.LOCATOR !== scope.locator)
1662
+ throw new Error(modernRecoveryError);
1663
+ return { ok: true, record, path: getPrimaryCredentialStorePath(deps) };
1664
+ }
1665
+ catch {
1666
+ return { ok: false, error: modernRecoveryError };
1667
+ }
1668
+ }, () => ({ ok: false, error: modernRecoveryError }));
1669
+ }
1670
+ export async function prepareModernBootstrapInvite(scope, fingerprint, label, generateKeySecret, deps) {
1671
+ return mutateModernBootstrapRecord(scope, deps, record => {
1672
+ if (record) {
1673
+ if (record.fingerprint !== fingerprint)
1674
+ throw new Error(modernRecoveryError);
1675
+ return record;
1676
+ }
1677
+ return { version: 1, mode: "server_assigned", baseUrl: scope.baseUrl, folder: scope.folder,
1678
+ label: validateBootstrapDisplayName(label), keySecret: generateKeySecret(), fingerprint };
1679
+ });
1680
+ }
1681
+ export async function lookupLegacyBootstrapInvite(fingerprint, deps) {
1682
+ return withCredentialStoreLock(deps, async () => {
1683
+ try {
1684
+ const loaded = await loadStoreForMutation(deps);
1685
+ if (!loaded.ok)
1686
+ throw new Error(modernRecoveryError);
1687
+ const candidates = Object.entries(loaded.base).filter(([target, entry]) => target.startsWith(BOOTSTRAP_PENDING_TARGET_PREFIX) && entry.BOOTSTRAP_INVITE_FINGERPRINT === fingerprint);
1688
+ if (candidates.length > 1)
1689
+ throw new Error(modernRecoveryError);
1690
+ if (!candidates.length)
1691
+ return { ok: true };
1692
+ const [target, entry] = candidates[0];
1693
+ const repoName = validateBootstrapRepoName(target.slice(BOOTSTRAP_PENDING_TARGET_PREFIX.length));
1694
+ if (!/^[A-Za-z0-9_-]{43}$/.test(entry.BAPI_API_KEY))
1695
+ throw new Error(modernRecoveryError);
1696
+ return { ok: true, repoName };
1697
+ }
1698
+ catch {
1699
+ return { ok: false, error: modernRecoveryError };
1700
+ }
1701
+ }, () => ({ ok: false, error: modernRecoveryError }));
1702
+ }
1703
+ export async function prepareModernBootstrapSignup(scope, label, email, generateKeySecret, deps) {
1704
+ return mutateModernBootstrapRecord(scope, deps, record => {
1705
+ if (record)
1706
+ return record;
1707
+ return { version: 1, mode: "server_assigned", baseUrl: scope.baseUrl, folder: scope.folder,
1708
+ label: validateBootstrapDisplayName(label), keySecret: generateKeySecret(), signupEmail: email };
1709
+ });
1710
+ }
1711
+ export async function transitionModernBootstrapSignup(signupScope, inviteScope, fingerprint, replayToken, challengeHandle, deps) {
1712
+ return mutateModernBootstrapRecord(inviteScope, deps, record => {
1713
+ if (!record || record.challengeHandle !== challengeHandle)
1714
+ throw new Error(modernRecoveryError);
1715
+ if (record.fingerprint && (record.fingerprint !== fingerprint || record.replayToken !== replayToken))
1716
+ throw new Error(modernRecoveryError);
1717
+ return { ...record, fingerprint, replayToken };
1718
+ }, signupScope.locator);
1719
+ }
1720
+ export async function checkpointModernBootstrapIdentity(scope, proof, canonical, serverLabel, deps) {
1721
+ return mutateModernBootstrapRecord(scope, deps, record => {
1722
+ if (!record || record.keySecret !== proof.keySecret || record.fingerprint !== proof.fingerprint ||
1723
+ (record.canonical && record.canonical !== canonical))
1724
+ throw new Error(modernRecoveryError);
1725
+ return { ...record, canonical: validateBootstrapRepoName(canonical), serverLabel: validateBootstrapDisplayName(serverLabel) };
1726
+ });
1727
+ }
1728
+ export async function promoteModernBootstrapCredential(scope, proof, deps, allowOverwrite = false, consentedKey) {
1729
+ return withCredentialStoreLock(deps, async () => {
1730
+ try {
1731
+ if (!proof.canonical || !proof.fingerprint)
1732
+ throw new Error(modernRecoveryError);
1733
+ const loaded = await loadStoreForMutation(deps);
1734
+ if (!loaded.ok)
1735
+ throw new Error(modernRecoveryError);
1736
+ const store = loaded.base;
1737
+ const target = getBapiTarget(validateBootstrapRepoName(proof.canonical));
1738
+ const existingKey = store[target]?.BAPI_API_KEY;
1739
+ const record = parseModernBootstrapRecord(store[scope.locator]);
1740
+ const reference = store[scope.folderReference];
1741
+ // A completed uncertain/concurrent promotion is recognized by exact proof.
1742
+ if (!record && !reference && existingKey === proof.keySecret)
1743
+ return { ok: true, path: getPrimaryCredentialStorePath(deps) };
1744
+ if (!record || record.keySecret !== proof.keySecret || record.canonical !== proof.canonical || record.fingerprint !== proof.fingerprint || record.folder !== scope.folder || record.baseUrl !== scope.baseUrl || reference?.LOCATOR !== scope.locator || reference.FOLDER !== scope.folder || reference.BASE_URL !== scope.baseUrl)
1745
+ throw new Error(modernRecoveryError);
1746
+ if (existingKey && existingKey !== proof.keySecret) {
1747
+ if (!allowOverwrite)
1748
+ return { ok: false, error: "The canonical credential already exists; overwrite consent is required.", needsConsent: true, observedKey: existingKey };
1749
+ if (consentedKey !== undefined && existingKey !== consentedKey)
1750
+ return { ok: false, error: "Destination credential changed after consent. Resume and confirm again." };
1751
+ }
1752
+ const entry = { ...store[target], BAPI_API_KEY: proof.keySecret };
1753
+ if (existingKey !== proof.keySecret)
1754
+ delete entry.BAPI_EPIC_LAUNCH_BOUND_BASE_URL;
1755
+ const updated = { ...store, [target]: entry };
1756
+ delete updated[scope.locator];
1757
+ delete updated[scope.folderReference];
1758
+ const written = await durablyReplaceCredentialStoreJson(getPrimaryCredentialStorePath(deps), updated, deps);
1759
+ return written.ok ? { ok: true, path: getPrimaryCredentialStorePath(deps) } : { ok: false, error: written.error };
1760
+ }
1761
+ catch {
1762
+ return { ok: false, error: modernRecoveryError };
1763
+ }
1764
+ }, () => ({ ok: false, error: modernRecoveryError }));
1765
+ }