@bridge_gpt/mcp-server 0.2.18 → 0.2.20

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 (100) hide show
  1. package/CONDUCTOR.md +75 -0
  2. package/README.md +8 -5
  3. package/build/agent-capabilities/probe-context.js +13 -3
  4. package/build/agent-capabilities/probes.js +262 -11
  5. package/build/agent-capabilities/reporter.js +1 -0
  6. package/build/agents.generated.js +1 -1
  7. package/build/backend-warnings.js +44 -0
  8. package/build/claude-settings.js +129 -0
  9. package/build/commands.generated.js +5 -3
  10. package/build/conductor/bridge-api-client.js +7 -7
  11. package/build/conductor/cli.js +65 -12
  12. package/build/conductor/deny-enforcement-preflight.js +96 -0
  13. package/build/conductor/doctor.js +183 -2
  14. package/build/conductor/epic-reconcile.js +9 -1
  15. package/build/conductor/epic-runtime.js +403 -43
  16. package/build/conductor/epic-state.js +7 -0
  17. package/build/conductor/errors.js +115 -3
  18. package/build/conductor/event-accessors.js +28 -10
  19. package/build/conductor/local-merge.js +458 -95
  20. package/build/conductor/merge-ledger.js +6 -4
  21. package/build/conductor/pr-ci-producer.js +17 -2
  22. package/build/conductor/producer-ledger.js +1 -1
  23. package/build/conductor/store.js +161 -18
  24. package/build/conductor/supervisor-merge.js +32 -5
  25. package/build/conductor/taxonomy.js +8 -0
  26. package/build/conductor/tools.js +28 -6
  27. package/build/conductor/worker-ledger-cli.js +244 -0
  28. package/build/conductor-bin.js +1884 -6917
  29. package/build/doctor.js +8 -0
  30. package/build/estimate-epic.js +84 -0
  31. package/build/executor/cli.js +229 -0
  32. package/build/executor/credentials.js +65 -0
  33. package/build/executor/deps.js +117 -0
  34. package/build/executor/env.js +79 -0
  35. package/build/executor/heartbeat.js +59 -0
  36. package/build/executor/http-client.js +131 -0
  37. package/build/executor/index.js +10 -0
  38. package/build/executor/job-errors.js +55 -0
  39. package/build/executor/job-log-registry.js +110 -0
  40. package/build/executor/job-runner.js +822 -0
  41. package/build/executor/job-types.js +60 -0
  42. package/build/executor/merge-job.js +229 -0
  43. package/build/executor/observation.js +123 -0
  44. package/build/executor/permissions.js +79 -0
  45. package/build/executor/preflight.js +144 -0
  46. package/build/executor/process.js +81 -0
  47. package/build/executor/prompt-spec.js +235 -0
  48. package/build/executor/results.js +134 -0
  49. package/build/executor/resume-pre-spawn.js +179 -0
  50. package/build/executor/runner.js +98 -0
  51. package/build/executor/terminal-mutation.js +34 -0
  52. package/build/executor/test-clock.js +109 -0
  53. package/build/executor/types.js +18 -0
  54. package/build/executor/verdict-artifact.js +53 -0
  55. package/build/executor/viewer-tabs.js +78 -0
  56. package/build/executor/watch-cli.js +113 -0
  57. package/build/executor/worker-command.js +106 -0
  58. package/build/executor/worker-finalization.js +177 -0
  59. package/build/executor/worker-log.js +92 -0
  60. package/build/executor/worktree-gc.js +134 -0
  61. package/build/executor/worktree-inspection.js +86 -0
  62. package/build/executor/worktree.js +103 -0
  63. package/build/index.js +14534 -10416
  64. package/build/mcp-invoke.js +19 -3
  65. package/build/mcp-provisioning.js +31 -25
  66. package/build/mcp-registration-doctor.js +27 -7
  67. package/build/mcp-server-invocation.js +152 -0
  68. package/build/pipelines.generated.js +16 -20
  69. package/build/readme.generated.js +1 -1
  70. package/build/sfcc/client.js +192 -50
  71. package/build/sfcc/ocapi-write-faults.js +94 -0
  72. package/build/sfcc/permissions.js +7 -22
  73. package/build/sfcc/reads-site-preference.js +52 -19
  74. package/build/sfcc/register.js +9 -0
  75. package/build/sfcc/write-grants.js +80 -0
  76. package/build/sfcc/write-guard.js +39 -0
  77. package/build/sfcc/write-result.js +47 -0
  78. package/build/sfcc/write-tool-common.js +85 -0
  79. package/build/sfcc/writes-custom-object-def.js +141 -0
  80. package/build/sfcc/writes-object-attribute-payloads.js +97 -0
  81. package/build/sfcc/writes-site-preference-payloads.js +59 -0
  82. package/build/sfcc/writes-site-preference.js +96 -0
  83. package/build/sfcc/writes-system-object-payloads.js +213 -0
  84. package/build/sfcc/writes-system-object.js +348 -0
  85. package/build/sfcc/writes.js +66 -0
  86. package/build/start-tickets-conductor.js +25 -93
  87. package/build/start-tickets-prereqs.js +152 -1
  88. package/build/start-tickets.js +96 -158
  89. package/build/version.generated.js +1 -1
  90. package/build/visual-diff-worker.js +313 -0
  91. package/build/visual-diff.js +632 -0
  92. package/build/worktree-core.js +202 -0
  93. package/package.json +8 -4
  94. package/pipelines/idea-to-ticket.json +7 -0
  95. package/pipelines/review-ticket.json +5 -18
  96. package/public/css/main.min.css +1612 -107
  97. package/public/css/main.min.css.map +1 -1
  98. package/public/js/main.min.js +10267 -1
  99. package/public/js/main.min.js.map +1 -1
  100. package/smoke-test/SMOKE-TEST.md +2 -1
@@ -23,6 +23,11 @@ async function _fetchGateConfigDefault(access) {
23
23
  }
24
24
  import { resolvePrHeadBinding } from "./pr-discovery.js";
25
25
  import { emitConductorEventIfNew } from "./producer-ledger.js";
26
+ // In-process ledger writer — the DEFAULT emit sink for conductor-OWNED paths
27
+ // (the CLI/supervisor/epic runtimes that legitimately own the better-sqlite3
28
+ // load). The worker `wait_for_done_gate` MCP path injects a CLI-subprocess emitter
29
+ // instead (BAPI-527) so the worker Node never loads the native binary.
30
+ import { emitConductorEvent as emitConductorEventInProcess } from "./store.js";
26
31
  const PRODUCER_OBSERVED_VIA = "pr-ci-producer";
27
32
  /** Bounded wait constants, consistent with the existing wait_for_event pattern. */
28
33
  export const WAIT_FOR_GATE_TIMEOUT_MAX_MS = 120_000;
@@ -124,7 +129,15 @@ function defaultSleep(ms) {
124
129
  * resolved exactly once by the caller.
125
130
  */
126
131
  async function observeWithResolved(binding, access, gateConfig, deps) {
127
- const emitIfNew = deps.emitIfNew ?? emitConductorEventIfNew;
132
+ // The event-WRITE sink defaults to the in-process store; the worker gate path
133
+ // injects a CLI-subprocess emitter (BAPI-527). When a caller supplies its own
134
+ // `emitIfNew` (e.g. unit tests / conductor-owned callers), that fully overrides
135
+ // this and the injected write sink is not consulted.
136
+ const emitConductorEventFn = deps.emitConductorEvent ?? emitConductorEventInProcess;
137
+ const emitIfNew = deps.emitIfNew ??
138
+ ((input, dimensions) => emitConductorEventIfNew(input, dimensions, {
139
+ emitEvent: emitConductorEventFn,
140
+ }));
128
141
  const pollCi = deps.pollCi ?? pollCiChecksForCommit;
129
142
  const now = deps.now ?? (() => new Date().toISOString());
130
143
  // Resolve run_id/worker_id for attribution. Env takes precedence; fall back
@@ -193,7 +206,9 @@ async function observeWithResolved(binding, access, gateConfig, deps) {
193
206
  let reviewSnapshot = null;
194
207
  try {
195
208
  const reviewObservation = await observeReviewWithResolved(binding, access, gateConfig, {
196
- emitIfNew: deps.emitIfNew ?? emitConductorEventIfNew,
209
+ // Reuse the resolved `emitIfNew` so review-state events emitted alongside CI
210
+ // route through the SAME (possibly CLI-subprocess) write sink (BAPI-527).
211
+ emitIfNew,
197
212
  env: deps.env,
198
213
  });
199
214
  reviewSnapshot = reviewObservation.snapshot;
@@ -37,7 +37,7 @@ export function makeStableProducerEventId(dedupeKey) {
37
37
  return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
38
38
  }
39
39
  /** Heuristically detect a SQLite duplicate-id / UNIQUE constraint failure. */
40
- function isDuplicateConstraintError(error) {
40
+ export function isDuplicateConstraintError(error) {
41
41
  if (!error || typeof error !== "object")
42
42
  return false;
43
43
  const code = error.code;
@@ -38,6 +38,68 @@ export class ConductorPersistenceUnavailableError extends Error {
38
38
  this.name = "ConductorPersistenceUnavailableError";
39
39
  }
40
40
  }
41
+ /** The native module whose load status this store owns. */
42
+ const LEDGER_NATIVE_MODULE_NAME = "better-sqlite3";
43
+ /**
44
+ * Thrown when `better-sqlite3` is present but fails to LOAD for this Node runtime
45
+ * (ABI/`NODE_MODULE_VERSION` mismatch or a generic `dlopen` failure) — distinct
46
+ * from "module simply not installed", which stays on the base
47
+ * {@link ConductorPersistenceUnavailableError} (503) path. Subclassing the base
48
+ * keeps existing `instanceof`/name-based degradation working while carrying the
49
+ * structured, secret-free {@link ConductorNativeModuleLoadDetails}. The raw
50
+ * caught error, stack, path, and message are deliberately NOT stored on the
51
+ * public instance.
52
+ */
53
+ export class ConductorNativeModuleLoadError extends ConductorPersistenceUnavailableError {
54
+ details;
55
+ failureKind;
56
+ constructor(failureKind, details) {
57
+ super("Conductor ledger native module failed to load for this Node runtime.");
58
+ this.name = "ConductorNativeModuleLoadError";
59
+ this.failureKind = failureKind;
60
+ this.details = details;
61
+ }
62
+ }
63
+ /** Build the allowlisted runtime details for the CURRENT process. */
64
+ export function buildNativeModuleLoadDetails() {
65
+ return {
66
+ node_version: process.version,
67
+ node_modules_abi: process.versions.modules ?? "unknown",
68
+ module: LEDGER_NATIVE_MODULE_NAME,
69
+ };
70
+ }
71
+ /**
72
+ * Classify a caught `better-sqlite3` load failure. Inspects ONLY the error `code`
73
+ * and `message` locally; the raw text never leaves this helper.
74
+ *
75
+ * - `MODULE_NOT_FOUND` / `ERR_MODULE_NOT_FOUND` -> base
76
+ * {@link ConductorPersistenceUnavailableError} ("not installed", 503).
77
+ * - `NODE_MODULE_VERSION` / "compiled against a different Node.js version" /
78
+ * "module version mismatch" -> native-load `abi_mismatch`.
79
+ * - `ERR_DLOPEN_FAILED` / generic `dlopen` -> native-load `dlopen_failed`.
80
+ * - Anything else -> base {@link ConductorPersistenceUnavailableError}.
81
+ */
82
+ export function classifyDatabaseModuleLoadFailure(error) {
83
+ const code = error && typeof error === "object" ? error.code : undefined;
84
+ const rawMessage = error && typeof error === "object" && typeof error.message === "string"
85
+ ? (error.message)
86
+ : "";
87
+ const lowered = rawMessage.toLowerCase();
88
+ if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
89
+ return new ConductorPersistenceUnavailableError();
90
+ }
91
+ const isAbiMismatch = lowered.includes("node_module_version") ||
92
+ lowered.includes("compiled against a different node.js version") ||
93
+ lowered.includes("module version mismatch");
94
+ if (isAbiMismatch) {
95
+ return new ConductorNativeModuleLoadError("abi_mismatch", buildNativeModuleLoadDetails());
96
+ }
97
+ const isDlopen = code === "ERR_DLOPEN_FAILED" || lowered.includes("dlopen");
98
+ if (isDlopen) {
99
+ return new ConductorNativeModuleLoadError("dlopen_failed", buildNativeModuleLoadDetails());
100
+ }
101
+ return new ConductorPersistenceUnavailableError();
102
+ }
41
103
  /**
42
104
  * Cached, concurrency-safe async loader for the optional `better-sqlite3` module.
43
105
  *
@@ -50,13 +112,21 @@ export class ConductorPersistenceUnavailableError extends Error {
50
112
  * warning is emitted exactly once and every subsequent call throws
51
113
  * {@link ConductorPersistenceUnavailableError} immediately, without re-attempting the
52
114
  * failing import on every Conductor operation.
115
+ *
116
+ * BAPI-526: the caught error is CLASSIFIED (not swallowed) into a TYPED failure —
117
+ * ABI/dlopen mismatches become {@link ConductorNativeModuleLoadError} carrying
118
+ * `node_version`/`node_modules_abi`, while "module not installed" stays the base
119
+ * {@link ConductorPersistenceUnavailableError}. That typed failure is cached and
120
+ * re-thrown on every later call so the classification is never lost.
53
121
  */
54
122
  let databaseModulePromise = null;
55
- let dbLoadDegraded = false;
123
+ let dbLoadFailure = null;
124
+ let dbLoadDiagnosticEmitted = false;
56
125
  async function getDatabaseModule() {
57
- // Cached degraded state: never re-attempt a known-failing native import.
58
- if (dbLoadDegraded) {
59
- throw new ConductorPersistenceUnavailableError();
126
+ // Cached typed failure: never re-attempt a known-failing native import, and
127
+ // preserve the ABI/dlopen classification across every subsequent call.
128
+ if (dbLoadFailure) {
129
+ throw dbLoadFailure;
60
130
  }
61
131
  if (databaseModulePromise)
62
132
  return databaseModulePromise;
@@ -67,13 +137,25 @@ async function getDatabaseModule() {
67
137
  const ctor = (mod.default ?? mod);
68
138
  return ctor;
69
139
  }
70
- catch {
71
- if (!dbLoadDegraded) {
72
- dbLoadDegraded = true;
73
- console.error("[conductor] Conductor persistence is unavailable: the optional 'better-sqlite3' native module could not be loaded. " +
140
+ catch (err) {
141
+ const classified = classifyDatabaseModuleLoadFailure(err);
142
+ dbLoadFailure = classified;
143
+ // Drop the rejected promise so the typed `dbLoadFailure` above is the sole
144
+ // cached state (the rejected promise itself must not be the only record).
145
+ databaseModulePromise = null;
146
+ if (!dbLoadDiagnosticEmitted) {
147
+ dbLoadDiagnosticEmitted = true;
148
+ // Fixed, path-free, secret-free diagnostic: module name, current Node
149
+ // version, current ABI, and coarse failure kind ONLY. Never the raw
150
+ // dlopen path, stack, or caught message.
151
+ const kind = classified instanceof ConductorNativeModuleLoadError
152
+ ? classified.failureKind
153
+ : "unavailable";
154
+ console.error(`[conductor] ${LEDGER_NATIVE_MODULE_NAME} native module could not be loaded ` +
155
+ `(node=${process.version} abi=${process.versions.modules ?? "unknown"} kind=${kind}). ` +
74
156
  "Conductor coordination features are disabled for this session; core MCP tools are unaffected.");
75
157
  }
76
- throw new ConductorPersistenceUnavailableError();
158
+ throw classified;
77
159
  }
78
160
  })();
79
161
  return databaseModulePromise;
@@ -183,11 +265,15 @@ export function resolveConductorStoreConfig(env = process.env) {
183
265
  * CHECK vocabulary. Bumped to 7 because the durable parse-after-merge fold adds
184
266
  * the `parse.triggered` event type to the same `events.type` CHECK vocabulary
185
267
  * (replacing the in-memory parse-wait map so stateless epic-tick invocations can
186
- * fold a merged ticket to `done`). Older ledgers stamped at a lower version are
187
- * rebuilt by {@link migrateConductorSchemaIfNeeded} so their CHECK clause accepts
188
- * the current taxonomy.
268
+ * fold a merged ticket to `done`). Bumped to 8 in BAPI-527 because the durable
269
+ * parse-after-merge TERMINAL fold adds the `parse.succeeded` and `parse.failed`
270
+ * event types to the same `events.type` CHECK vocabulary (so the stateless
271
+ * epic-tick can fold a merged ticket to `done` on terminal parse success and
272
+ * `blocked` on permanent parse failure directly from the ledger). Older ledgers
273
+ * stamped at a lower version are rebuilt by {@link migrateConductorSchemaIfNeeded}
274
+ * so their CHECK clause accepts the current taxonomy.
189
275
  */
190
- export const CURRENT_CONDUCTOR_SCHEMA_VERSION = 7;
276
+ export const CURRENT_CONDUCTOR_SCHEMA_VERSION = 8;
191
277
  /** Render the taxonomy `CHECK (type IN (...))` clause from the single source of truth. */
192
278
  function buildTypeCheckClause() {
193
279
  const list = SEMANTIC_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
@@ -327,15 +413,15 @@ export function migrateConductorSchemaIfNeeded(db) {
327
413
  return;
328
414
  }
329
415
  const migrate = db.transaction(() => {
330
- // Scratch table named for the current schema version (v4, BAPI-398) so the
331
- // migration rebuild never reads as a stale pre-merge-taxonomy artifact.
332
- db.exec(buildEventsTableSql("events_migrated_v5"));
333
- db.exec(`INSERT INTO events_migrated_v5
416
+ // Scratch table named for the current schema version (v8, BAPI-527) so the
417
+ // migration rebuild never reads as a stale pre-parse-terminal-taxonomy artifact.
418
+ db.exec(buildEventsTableSql("events_migrated_v8"));
419
+ db.exec(`INSERT INTO events_migrated_v8
334
420
  (seq, id, source, type, subject, run_id, worker_id, producer, schema_version, time, data_json, confidence, observed_via, created_at)
335
421
  SELECT seq, id, source, type, subject, run_id, worker_id, producer, schema_version, time, data_json, confidence, observed_via, created_at
336
422
  FROM events`);
337
423
  db.exec("DROP TABLE events");
338
- db.exec("ALTER TABLE events_migrated_v5 RENAME TO events");
424
+ db.exec("ALTER TABLE events_migrated_v8 RENAME TO events");
339
425
  db.exec(buildEventsIndexesSql());
340
426
  // Ensure auxiliary tables and their relay indexes exist on older ledgers too.
341
427
  db.exec(buildAuxiliaryTablesSql());
@@ -818,6 +904,56 @@ export async function upsertSupervisorProjection(input, config = resolveConducto
818
904
  * and network-home degradation. Never creates the DB, inserts a diagnostic
819
905
  * event, or mutates WAL/schema state.
820
906
  */
907
+ /**
908
+ * Read-only probe of whether the ledger native binding actually loads for this
909
+ * Node runtime (BAPI-526). Reuses the lazy loader so it reflects the REAL load
910
+ * outcome, and NEVER opens the SQLite database, runs migrations, creates files,
911
+ * or emits events. On success reports `loads: true`; on a native-load failure
912
+ * reports the coarse `failure_kind` and marks the host degraded. All returned
913
+ * fields are secret-free and path-free.
914
+ */
915
+ export async function inspectConductorLedgerNativeModuleLoad() {
916
+ const details = buildNativeModuleLoadDetails();
917
+ try {
918
+ await getDatabaseModule();
919
+ return {
920
+ module: details.module,
921
+ node_version: details.node_version,
922
+ node_modules_abi: details.node_modules_abi,
923
+ loads: true,
924
+ failure_kind: "none",
925
+ degraded: false,
926
+ warning: null,
927
+ };
928
+ }
929
+ catch (err) {
930
+ if (err instanceof ConductorNativeModuleLoadError) {
931
+ return {
932
+ ...err.details,
933
+ loads: false,
934
+ failure_kind: err.failureKind,
935
+ degraded: true,
936
+ warning: "better-sqlite3 failed to load for this Node runtime; conductor ledger operations are degraded.",
937
+ };
938
+ }
939
+ if (err instanceof ConductorPersistenceUnavailableError) {
940
+ return {
941
+ ...details,
942
+ loads: false,
943
+ failure_kind: "module_not_found",
944
+ degraded: true,
945
+ warning: "better-sqlite3 is not installed; conductor ledger operations are unavailable.",
946
+ };
947
+ }
948
+ return {
949
+ ...details,
950
+ loads: false,
951
+ failure_kind: "unknown",
952
+ degraded: true,
953
+ warning: "better-sqlite3 could not be loaded; conductor ledger operations are degraded.",
954
+ };
955
+ }
956
+ }
821
957
  export async function doctorConductorLedger(config = resolveConductorStoreConfig()) {
822
958
  const health = getConductorPathHealth();
823
959
  const network = detectNetworkMountedHome();
@@ -825,6 +961,12 @@ export async function doctorConductorLedger(config = resolveConductorStoreConfig
825
961
  if (network.network_mounted && network.warning) {
826
962
  warnings.push(network.warning);
827
963
  }
964
+ // Read-only native-binding load probe (BAPI-526). Reflects the real lazy-loader
965
+ // outcome; never opens/creates/migrates the ledger.
966
+ const native_load = await inspectConductorLedgerNativeModuleLoad();
967
+ if (native_load.degraded && native_load.warning) {
968
+ warnings.push(native_load.warning);
969
+ }
828
970
  let schemaPresent = false;
829
971
  let journalMode = null;
830
972
  let eventCount = null;
@@ -873,6 +1015,7 @@ export async function doctorConductorLedger(config = resolveConductorStoreConfig
873
1015
  message_relay: {
874
1016
  message_cooldown_ms: config.message_cooldown_ms,
875
1017
  },
1018
+ native_load,
876
1019
  degraded: network.network_mounted,
877
1020
  warnings,
878
1021
  };
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * Supervisor merge pipeline wrapper (Conductor C6, BAPI-398).
3
3
  *
4
- * Processes an eligible worker-scoped `gate.met` event by calling the API-owned
5
- * merge decision/execution endpoint and recording the returned `merge.*` ledger
6
- * events into the LOCAL conductor ledger. This module:
4
+ * Processes an eligible PR-bound `gate.met` event by calling the API-owned merge
5
+ * decision/execution endpoint and recording the returned `merge.*` ledger events
6
+ * into the LOCAL conductor ledger. The merge decision depends ONLY on the PR/gate
7
+ * identity `(repo, pr_number, head_sha, gate)` — `worker_id` is attribution
8
+ * metadata that is stamped onto the emitted `merge.*` events for audit but never
9
+ * gates the merge (BAPI-525 Change A). When the source `gate.met` lacks a
10
+ * `worker_id` (e.g. the autonomous git-hook chain), attribution is resolved from
11
+ * durable dispatch state via an optional `resolveWorkerIdForGateEvent` seam. This
12
+ * module:
7
13
  *
8
14
  * - never performs a provider merge locally and never handles VCS write
9
15
  * credentials (no `gh`, no GITHUB_TOKEN, no installation token, no shell-out),
@@ -52,6 +58,24 @@ function mapApiErrorReason(error) {
52
58
  }
53
59
  return "api_network";
54
60
  }
61
+ /**
62
+ * Resolve the worker attribution stamped onto emitted `merge.*` ledger events for
63
+ * a `gate.met`. Precedence: (1) a non-empty `event.worker_id`, (2) the durable
64
+ * attribution seam, (3) `null`. Attribution is audit-only and is never required
65
+ * for the merge decision (BAPI-525 Change A).
66
+ */
67
+ function resolveAttributionWorkerId(event, identity, resolve) {
68
+ if (typeof event.worker_id === "string" && event.worker_id.trim().length > 0) {
69
+ return event.worker_id.trim();
70
+ }
71
+ if (resolve) {
72
+ const resolved = resolve(event, identity);
73
+ if (typeof resolved === "string" && resolved.trim().length > 0) {
74
+ return resolved.trim();
75
+ }
76
+ }
77
+ return null;
78
+ }
55
79
  /**
56
80
  * Process an eligible `gate.met` event end-to-end: terminal-success short-circuit,
57
81
  * API merge call, ordered ledger recording, and sanitized error handling. Never
@@ -68,6 +92,9 @@ export async function processGateMetMerge(access, event, deps = {}) {
68
92
  return { processed: false, reason: "ineligible" };
69
93
  }
70
94
  const actionKey = identity.action_key;
95
+ // Attribution only — resolved from the event or durable dispatch state, never a
96
+ // merge gate (BAPI-525 Change A). A null value is tolerated end-to-end.
97
+ const attributionWorkerId = resolveAttributionWorkerId(event, identity, deps.resolveWorkerIdForGateEvent);
71
98
  // Terminal-success short-circuit BEFORE any API call (idempotent across restart).
72
99
  if (await checkTerminal(actionKey)) {
73
100
  return { processed: false, reason: "already_succeeded" };
@@ -92,7 +119,7 @@ export async function processGateMetMerge(access, event, deps = {}) {
92
119
  reason,
93
120
  details: baseDetails,
94
121
  run_id: event.run_id ?? null,
95
- worker_id: event.worker_id ?? null,
122
+ worker_id: attributionWorkerId,
96
123
  summary: `merge.failed ${reason}`,
97
124
  }, { emitEvent: deps.emitEvent });
98
125
  return { processed: true, outcome: "api_error", reason };
@@ -119,7 +146,7 @@ export async function processGateMetMerge(access, event, deps = {}) {
119
146
  reason: ledgerEvent.reason ?? null,
120
147
  details: ledgerEvent.details ?? baseDetails,
121
148
  run_id: event.run_id ?? null,
122
- worker_id: event.worker_id ?? null,
149
+ worker_id: attributionWorkerId,
123
150
  }, { emitEvent: deps.emitEvent });
124
151
  emitted.push({ type: ledgerEvent.type, emitted: result.emitted });
125
152
  }
@@ -51,6 +51,14 @@ export const SEMANTIC_EVENT_TYPES = [
51
51
  // post-merge repository re-index, so the (stateless) reconcile loop can fold a
52
52
  // merged ticket to `done` from the ledger instead of an in-memory wait map.
53
53
  "parse.triggered",
54
+ // BAPI-527: durable parse-after-merge TERMINAL markers. Emitted by epic-tick
55
+ // once the durable `/jira/parse-status` surface reports a terminal outcome for a
56
+ // triggered post-merge parse: `parse.succeeded` lets the merge.succeeded signal
57
+ // proceed to `done`; `parse.failed` folds the ticket to `blocked` (and escalates).
58
+ // Payloads are compact (summary/status/reason/details with epic_key + ticket_key
59
+ // + bounded parse error metadata) — never subprocess stdout/stderr/stacks/commands.
60
+ "parse.succeeded",
61
+ "parse.failed",
54
62
  ];
55
63
  /**
56
64
  * Type guard: returns `true` only when `value` is one of the exact taxonomy
@@ -11,9 +11,14 @@
11
11
  import { z } from "zod";
12
12
  import { SEMANTIC_EVENT_TYPES } from "./taxonomy.js";
13
13
  import { ConductorValidationError, toConductorErrorEnvelope } from "./errors.js";
14
- import { emitConductorEvent, pollConductorEvents, waitForConductorEvent, getSupervisorSnapshot, sendWorkerMessage, checkWorkerMessages, } from "./store.js";
14
+ import { emitConductorEvent, pollConductorEvents, waitForConductorEvent, getSupervisorSnapshot, sendWorkerMessage, } from "./store.js";
15
15
  import { normalizePrNumber, normalizeSha } from "./git-ci-types.js";
16
16
  import { waitForDoneGate, resolveDispatchRunIdForBinding } from "./pr-ci-producer.js";
17
+ // BAPI-527: worker-facing ledger operations run through the conductor CLI
18
+ // subprocess (under the captured conductor Node), NOT the in-process SQLite store,
19
+ // so the worker Node never loads the `better-sqlite3` native binary. `store.ts`'s
20
+ // `checkWorkerMessages` is intentionally NOT imported here anymore.
21
+ import { checkWorkerMessagesViaCli, emitConductorEventIfNewViaCli } from "./worker-ledger-cli.js";
17
22
  import { resolveConductorBridgeApiAccess, fetchEpicRunState, ConductorBridgeApiError } from "./bridge-api-client.js";
18
23
  /** Build a Zod enum from the semantic taxonomy so arbitrary types are rejected up front. */
19
24
  export function buildEventTypeZodEnum() {
@@ -37,7 +42,10 @@ function jsonResult(value) {
37
42
  /**
38
43
  * Wrap a tool handler so any thrown value becomes a sanitized structured JSON
39
44
  * error envelope (validation -> 400, store-busy -> 503, otherwise 500). Raw
40
- * input data, stack traces, and secret material are never echoed back.
45
+ * input data, stack traces, and secret material are never echoed back. Any
46
+ * allowlisted structured `details` on the envelope (e.g. the BAPI-526
47
+ * LEDGER_NATIVE_MODULE_LOAD_FAILED node_version/ABI/module fields) is preserved
48
+ * through {@link jsonResult}'s pretty serialization unchanged.
41
49
  */
42
50
  export function withConductorToolErrorHandling(handler) {
43
51
  return async (args) => {
@@ -249,7 +257,17 @@ function registerWaitForDoneGateTool(registerTool) {
249
257
  timeoutMs: args.timeout_ms,
250
258
  pollIntervalMs: args.poll_interval_ms,
251
259
  worktreePath: args.worktree_path,
252
- }, { resolveRunId: resolveDispatchRunIdForBinding });
260
+ }, {
261
+ resolveRunId: resolveDispatchRunIdForBinding,
262
+ // BAPI-527: route the gate's ENTIRE dedup+emit through the conductor CLI
263
+ // subprocess. Injecting `emitIfNew` (not just the write sink) replaces the
264
+ // in-process `emitConductorEventIfNew`, whose `eventAlreadyExists` poll
265
+ // pre-check would load better-sqlite3 in the worker Node. The dedupe key +
266
+ // deterministic id are derived purely and the id is forwarded to the CLI,
267
+ // so dedup happens server-side on the events.id UNIQUE constraint — the
268
+ // worker path performs NO in-process ledger read or write.
269
+ emitIfNew: (input, dimensions) => emitConductorEventIfNewViaCli(input, dimensions),
270
+ });
253
271
  return jsonResult({
254
272
  gate_met: result.gate_met,
255
273
  timed_out: result.timed_out,
@@ -338,9 +356,13 @@ function registerCheckMessagesTool(registerTool) {
338
356
  if (runId.trim().length === 0 || workerId.trim().length === 0) {
339
357
  throw new ConductorValidationError("Conductor worker identity is unavailable: provide run_id + worker_id, or set BAPI_CONDUCTOR_RUN_ID and BAPI_CONDUCTOR_WORKER_ID.");
340
358
  }
341
- const result = await checkWorkerMessages({
342
- run_id: runId,
343
- worker_id: workerId,
359
+ // BAPI-527: read+ack through the conductor CLI subprocess (captured
360
+ // conductor Node), NOT the in-process store, so the worker Node never loads
361
+ // better-sqlite3. A missing/invalid CONDUCTOR_NODE_PATH surfaces as the
362
+ // typed LEDGER_SUBPROCESS_RUNTIME_UNAVAILABLE envelope via the wrapper below.
363
+ const result = await checkWorkerMessagesViaCli({
364
+ runId,
365
+ workerId,
344
366
  limit: args.limit,
345
367
  });
346
368
  return jsonResult(result);