@bridge_gpt/mcp-server 0.2.65 → 0.2.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +25 -13
  2. package/build/agent-launchers/claude-executor-adapter.js +12 -18
  3. package/build/agent-launchers/executor-adapter.js +27 -0
  4. package/build/bridge-config.js +104 -0
  5. package/build/codex-bridge-profile.js +10 -1
  6. package/build/commands.generated.js +4 -4
  7. package/build/conductor/bridge-api-client.js +230 -10
  8. package/build/conductor/epic-child-continuation.js +61 -34
  9. package/build/conductor/epic-implementer-cli.js +12 -0
  10. package/build/conductor/guided-repair.js +323 -0
  11. package/build/conductor/readiness-cli.js +30 -9
  12. package/build/conductor/readiness.js +23 -1
  13. package/build/conductor/recovery-cli.js +507 -16
  14. package/build/conductor/recovery-operations.js +325 -2
  15. package/build/conductor/repair-contract.js +61 -3
  16. package/build/credential-store.js +5 -4
  17. package/build/diagnostic-text.js +36 -0
  18. package/build/docs.generated.js +1 -1
  19. package/build/doctor.js +70 -10
  20. package/build/drive-epic.js +395 -78
  21. package/build/epic-implementer-bin.js +24 -22
  22. package/build/epic-launch-diagnostic.js +31 -0
  23. package/build/epic-launch-profile.js +44 -14
  24. package/build/executor/completion-supervision.js +5 -0
  25. package/build/executor/correction-dispatch.js +60 -0
  26. package/build/executor/env.js +6 -0
  27. package/build/executor/http-client.js +15 -0
  28. package/build/executor/integration-job.js +6 -1
  29. package/build/executor/job-errors.js +13 -4
  30. package/build/executor/job-log-registry.js +340 -73
  31. package/build/executor/job-runner.js +260 -33
  32. package/build/executor/job-types.js +8 -13
  33. package/build/executor/preflight.js +91 -2
  34. package/build/executor/prompt-spec.js +113 -9
  35. package/build/executor/runner.js +23 -0
  36. package/build/executor/stale-artifacts.js +5 -4
  37. package/build/executor/types.js +1 -0
  38. package/build/executor/watch-cli.js +35 -8
  39. package/build/executor/worker-command.js +43 -43
  40. package/build/executor/worker-finalization.js +80 -15
  41. package/build/executor/worker-guard-hook.js +46 -10
  42. package/build/executor/worker-log.js +137 -70
  43. package/build/executor/worktree-gc.js +30 -6
  44. package/build/index.js +177 -329
  45. package/build/init.js +107 -4
  46. package/build/install-bridge.js +284 -20
  47. package/build/install-doctor.js +172 -30
  48. package/build/install-reexec.js +69 -7
  49. package/build/macos-terminal-permission-guidance.js +16 -0
  50. package/build/model-thresholds-tool.js +185 -0
  51. package/build/pipelines.generated.js +1 -1
  52. package/build/plane/status.js +4 -0
  53. package/build/plane/supervisor.js +4 -0
  54. package/build/plane/worker-exit-code.js +2 -0
  55. package/build/project-root.js +99 -0
  56. package/build/readme.generated.js +1 -1
  57. package/build/review-tickets.js +156 -13
  58. package/build/setup-epic.js +180 -108
  59. package/build/sfcc/setup-status.js +2 -0
  60. package/build/sfcc/tool-wrapper.js +2 -0
  61. package/build/sfcc/tools-configured.js +96 -0
  62. package/build/start-tickets.js +163 -58
  63. package/build/upgrade-cli.js +109 -24
  64. package/build/version.generated.js +3 -3
  65. package/build/windows-npm-entry.js +103 -0
  66. package/build/worker-guard-hook-bin.js +4 -3
  67. package/build/worktree-core.js +6 -5
  68. package/docs/install/mcp-tool-integrations.md +1 -0
  69. package/docs/install/sfcc-integration.md +12 -7
  70. package/package.json +1 -1
  71. package/pipelines/implement-ticket.json +1 -1
@@ -0,0 +1,31 @@
1
+ /** Keep operator-controlled components out of trusted epic-launch refusal text. */
2
+ export function diagnosticComponent(value) {
3
+ // A short identifier here is a schema-field label, not its stored value.
4
+ if (/^field '[A-Za-z][A-Za-z0-9]{0,39}'$/.test(value))
5
+ return value;
6
+ if (value.length > 512 ||
7
+ /[\x00-\x1f\x7f]/.test(value) ||
8
+ /(?:sk[-_][A-Za-z0-9]+|api[-_]?key[-_:=][A-Za-z0-9]+|secret|token|password|bearer|authorization)/i.test(value) ||
9
+ /%[0-9a-f]{2}/i.test(value) && decodedLooksSensitive(value))
10
+ return "<redacted>";
11
+ return value;
12
+ }
13
+ function decodedLooksSensitive(value) {
14
+ try {
15
+ const decoded = decodeURIComponent(value);
16
+ return decoded !== value && diagnosticComponent(decoded) === "<redacted>";
17
+ }
18
+ catch {
19
+ return true;
20
+ }
21
+ }
22
+ /** URL validation alone permits paths and queries that look like credentials. */
23
+ export function safeEpicLaunchTargetUrl(baseUrl) {
24
+ try {
25
+ const url = new URL(baseUrl);
26
+ return !url.username && !url.password && diagnosticComponent(baseUrl) === baseUrl;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
@@ -26,11 +26,13 @@
26
26
  */
27
27
  import os from "node:os";
28
28
  import path from "node:path";
29
+ import { createHash } from "node:crypto";
29
30
  import { chmod, mkdir, open, readFile, rename, unlink, writeFile } from "node:fs/promises";
30
31
  import { durablyReplaceCredentialStoreJson, getPrimaryCredentialStorePath, withCredentialStoreLock, } from "./credential-store.js";
31
32
  import { validateHttpBaseUrl, describeBaseUrlRejection } from "./base-url.js";
32
33
  import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
33
34
  import { isTransientNpxEntryPath } from "./mcp-server-invocation.js";
35
+ import { diagnosticComponent } from "./epic-launch-diagnostic.js";
34
36
  // ---------------------------------------------------------------------------
35
37
  // Types
36
38
  // ---------------------------------------------------------------------------
@@ -45,7 +47,7 @@ export const EPIC_LAUNCH_EXECUTION_MODES = ["local", "durable"];
45
47
  /** One-time migration guidance for a profile this build cannot read. */
46
48
  export function epicLaunchProfileSetupGuidance(name) {
47
49
  return (`Re-run guided setup to (re)write it: npx -y ${MCP_PACKAGE_NAME} install conductor ` +
48
- `--launch-profile ${EPIC_LAUNCH_PROFILE_NAME_PATTERN.test(name) ? name : "<name>"} --base-url <url> ...`);
50
+ `--launch-profile ${EPIC_LAUNCH_PROFILE_NAME_PATTERN.test(name) ? diagnosticComponent(name) : "<name>"} --base-url <url> ...`);
49
51
  }
50
52
  // ---------------------------------------------------------------------------
51
53
  // Validation (pure)
@@ -73,7 +75,7 @@ function malformed(name, field, why) {
73
75
  return {
74
76
  ok: false,
75
77
  kind: "profile-malformed",
76
- message: `Epic launch profile '${name}' is unusable: ${field} ${why}. ${epicLaunchProfileSetupGuidance(name)}`,
78
+ message: `Epic launch profile '${diagnosticComponent(name)}' is unusable: ${diagnosticComponent(field)} ${why}. ${epicLaunchProfileSetupGuidance(name)}`,
77
79
  };
78
80
  }
79
81
  function validateRuntime(name, raw) {
@@ -118,7 +120,7 @@ export function validateEpicLaunchProfile(raw, selector = "<unnamed>") {
118
120
  return {
119
121
  ok: false,
120
122
  kind: "profile-unsupported-version",
121
- message: `Epic launch profile '${selector}' uses an unsupported schema version; this build reads ` +
123
+ message: `Epic launch profile '${diagnosticComponent(selector)}' uses an unsupported schema version; this build reads ` +
122
124
  `version ${EPIC_LAUNCH_PROFILE_VERSION} only. ${epicLaunchProfileSetupGuidance(selector)}`,
123
125
  };
124
126
  }
@@ -204,23 +206,23 @@ async function readStoreDocument(deps) {
204
206
  if (code === "ENOENT") {
205
207
  return { ok: true, present: false, document: { version: EPIC_LAUNCH_PROFILE_VERSION, profiles: {} } };
206
208
  }
207
- return { ok: false, kind: "store-unreadable", message: `Unable to read ${storePath}.` };
209
+ return { ok: false, kind: "store-unreadable", message: `Unable to read ${diagnosticComponent(storePath)}.` };
208
210
  }
209
211
  let parsed;
210
212
  try {
211
213
  parsed = JSON.parse(raw);
212
214
  }
213
215
  catch {
214
- return { ok: false, kind: "store-unreadable", message: `${storePath} is not valid JSON.` };
216
+ return { ok: false, kind: "store-unreadable", message: `${diagnosticComponent(storePath)} is not valid JSON.` };
215
217
  }
216
218
  if (!isPlainObject(parsed) || !isPlainObject(parsed.profiles)) {
217
- return { ok: false, kind: "store-unreadable", message: `${storePath} has no profiles object.` };
219
+ return { ok: false, kind: "store-unreadable", message: `${diagnosticComponent(storePath)} has no profiles object.` };
218
220
  }
219
221
  if (parsed.version !== EPIC_LAUNCH_PROFILE_VERSION) {
220
222
  return {
221
223
  ok: false,
222
224
  kind: "profile-unsupported-version",
223
- message: `${storePath} uses an unsupported schema version; this build reads version ` +
225
+ message: `${diagnosticComponent(storePath)} uses an unsupported schema version; this build reads version ` +
224
226
  `${EPIC_LAUNCH_PROFILE_VERSION} only. ${epicLaunchProfileSetupGuidance("<name>")}`,
225
227
  };
226
228
  }
@@ -249,7 +251,7 @@ export async function readEpicLaunchProfile(name, deps) {
249
251
  return {
250
252
  ok: false,
251
253
  kind: "profile-missing",
252
- message: `No epic launch profile named '${name}' exists on this host. ${epicLaunchProfileSetupGuidance(name)}`,
254
+ message: `No epic launch profile named '${diagnosticComponent(name)}' exists on this host. ${epicLaunchProfileSetupGuidance(name)}`,
253
255
  };
254
256
  }
255
257
  const validated = validateEpicLaunchProfile(store.document.profiles[name], name);
@@ -259,7 +261,7 @@ export async function readEpicLaunchProfile(name, deps) {
259
261
  return {
260
262
  ok: false,
261
263
  kind: "profile-host-mismatch",
262
- message: `Epic launch profile '${name}' was approved on a different host. Profiles are host-local; ` +
264
+ message: `Epic launch profile '${diagnosticComponent(name)}' was approved on a different host. Profiles are host-local; ` +
263
265
  epicLaunchProfileSetupGuidance(name),
264
266
  };
265
267
  }
@@ -309,6 +311,34 @@ export async function writeEpicLaunchProfile(profile, deps) {
309
311
  return { ok: true, path: storePath, action: existing === undefined ? "created" : "updated" };
310
312
  }, (error) => ({ ok: false, kind: "store-write-failed", message: error }));
311
313
  }
314
+ /** Safe for text and JSON; no policy recipient or webhook value is retained. */
315
+ export function formatEpicLaunchChannelIdentity(identity) {
316
+ return identity;
317
+ }
318
+ const pinnedPolicyContents = new WeakMap();
319
+ /** Store selected content once, without making it serializable with the selection. */
320
+ export function pinEpicLaunchPolicy(selection, policy) {
321
+ pinnedPolicyContents.set(selection, policy);
322
+ return selection;
323
+ }
324
+ export function getPinnedEpicLaunchPolicy(selection) {
325
+ return pinnedPolicyContents.get(selection);
326
+ }
327
+ export function epicLaunchPolicyDigest(raw) {
328
+ return createHash("sha256").update(raw).digest("hex");
329
+ }
330
+ /** Finalize the already selected target after the run's existence is known. */
331
+ export function resolveEpicLaunchSelection(args) {
332
+ return Object.freeze({
333
+ baseUrl: args.baseUrl,
334
+ repoName: args.repoName,
335
+ targetSource: args.targetSource,
336
+ profileName: args.profile?.name ?? null,
337
+ topology: args.topology,
338
+ policy: Object.freeze(args.policy),
339
+ escalationChannel: args.escalationChannel,
340
+ });
341
+ }
312
342
  /**
313
343
  * Normalize every applicable target source and decide ONE target, before any
314
344
  * credential is resolved and before any request is made.
@@ -338,7 +368,7 @@ export function resolveEpicLaunchTarget(inputs) {
338
368
  const flag = normalize(inputs.flagBaseUrl, "--base-url");
339
369
  if (!flag.ok)
340
370
  return { ok: false, kind: "target-invalid", message: flag.message };
341
- const env = normalize(inputs.env.BAPI_BASE_URL, "BAPI_BASE_URL");
371
+ const env = normalize(inputs.profileSource === "profile-env" ? undefined : inputs.env.BAPI_BASE_URL, "BAPI_BASE_URL");
342
372
  if (!env.ok)
343
373
  return { ok: false, kind: "target-invalid", message: env.message };
344
374
  const profile = inputs.profile;
@@ -347,7 +377,7 @@ export function resolveEpicLaunchTarget(inputs) {
347
377
  return {
348
378
  ok: false,
349
379
  kind: "target-conflict",
350
- message: `--base-url names a different endpoint than launch profile '${profile.name}'. An override ` +
380
+ message: `--base-url names a different endpoint than launch profile '${diagnosticComponent(profile.name)}'. An override ` +
351
381
  "does not rebind a profile; re-run guided setup to change its target.",
352
382
  };
353
383
  }
@@ -355,7 +385,7 @@ export function resolveEpicLaunchTarget(inputs) {
355
385
  return {
356
386
  ok: false,
357
387
  kind: "target-conflict",
358
- message: `BAPI_BASE_URL names a different endpoint than launch profile '${profile.name}'. Unset it, ` +
388
+ message: `BAPI_BASE_URL names a different endpoint than launch profile '${diagnosticComponent(profile.name)}'. Unset it, ` +
359
389
  "or re-run guided setup to change the profile's target.",
360
390
  };
361
391
  }
@@ -364,10 +394,10 @@ export function resolveEpicLaunchTarget(inputs) {
364
394
  return {
365
395
  ok: false,
366
396
  kind: "repository-conflict",
367
- message: `--repo names a different repository than launch profile '${profile.name}'.`,
397
+ message: `--repo names a different repository than launch profile '${diagnosticComponent(profile.name)}'.`,
368
398
  };
369
399
  }
370
- return { ok: true, baseUrl: profile.baseUrl, source: "profile", repoName: profile.repoName };
400
+ return { ok: true, baseUrl: profile.baseUrl, source: inputs.profileSource ?? "profile", repoName: profile.repoName };
371
401
  }
372
402
  if (flag.value !== null && env.value !== null && flag.value !== env.value) {
373
403
  return {
@@ -223,6 +223,11 @@ export function finalizationFacts(outcome) {
223
223
  case "WorkerFinalizationMissingRemoteBranchAndPr":
224
224
  case "WorkerFinalizationSavedButUnfinalized":
225
225
  return { verdict: "unmet", pushSatisfied: false, prSatisfied: false };
226
+ // BAPI-1330: the worker's full HEAD, or the PR's head, could not be
227
+ // established. Nothing was observed MISSING, so the verdict is `unknown`
228
+ // and neither push nor PR is inferred satisfied.
229
+ case "WorkerFinalizationPublicationUnverified":
230
+ return { verdict: "unknown", pushSatisfied: false, prSatisfied: false };
226
231
  default:
227
232
  return { verdict: "unknown", pushSatisfied: false, prSatisfied: false };
228
233
  }
@@ -0,0 +1,60 @@
1
+ /** BAPI-1328: persisted remaining-work authority, independent of prompt text. */
2
+ export const CORRECTION_INTENT_CAPABILITY = "correction_intent.v1";
3
+ export const CORRECTION_INTENT_VERSION = 1;
4
+ export const CORRECTION_KINDS = [
5
+ "review_fix", "operator_remediation", "ci_fix", "focused_rebase",
6
+ "overlap_reconciliation", "approved_spec_delta", "continuation",
7
+ "required_checks", "finalization", "integration_correction",
8
+ ];
9
+ function nonblank(value) {
10
+ return typeof value === "string" && value.trim().length > 0 && value.length <= 4096;
11
+ }
12
+ function reference(value) {
13
+ return value !== null && typeof value === "object" && !Array.isArray(value) &&
14
+ Object.keys(value).length > 0 && Object.entries(value).every(([key, item]) => nonblank(key) && ((typeof item === "string" && nonblank(item)) ||
15
+ (typeof item === "number" && Number.isInteger(item))));
16
+ }
17
+ /** A prompt or job type never grants correction authority. */
18
+ export function validateCorrectionIntent(value, row) {
19
+ const refuse = (code, detail) => ({ ok: false, code, detail });
20
+ if (!value || typeof value !== "object" || Array.isArray(value))
21
+ return refuse("correction_context_missing", "correction intent is absent");
22
+ const intent = value;
23
+ if (intent.version !== CORRECTION_INTENT_VERSION)
24
+ return refuse("correction_contract_version_unsupported", "unsupported correction intent version");
25
+ if (!CORRECTION_KINDS.includes(intent.kind))
26
+ return refuse("correction_contract_invalid", "unknown correction kind");
27
+ for (const key of ["repo_name", "epic_run_id", "ticket_key"]) {
28
+ if (!nonblank(row[key]) || intent[key] !== row[key])
29
+ return refuse("correction_contract_invalid", `${key} differs from row`);
30
+ }
31
+ if (!nonblank(intent.branch) || (row.expected_branch && intent.branch !== row.expected_branch))
32
+ return refuse("correction_context_missing", "retained branch is unavailable or changed");
33
+ if (!nonblank(intent.head_sha) || (row.expected_head_sha && intent.head_sha !== row.expected_head_sha))
34
+ return refuse("correction_context_missing", "retained head is unavailable or changed");
35
+ if (!nonblank(intent.obligation) || !reference(intent.context_ref))
36
+ return refuse("correction_context_missing", "remaining obligation or context reference is absent");
37
+ if (intent.resume !== undefined && intent.resume !== null && intent.resume !== false)
38
+ return refuse("correction_contract_invalid", "unsupported resume metadata");
39
+ if (intent.kind === "approved_spec_delta") {
40
+ const delta = intent.approved_delta;
41
+ if (!delta || typeof delta !== "object" || Array.isArray(delta))
42
+ return refuse("renewed_scope_approval_required", "approved same-ticket delta is absent");
43
+ const d = delta;
44
+ if (!["old_identity", "new_identity", "obligation"].every(key => nonblank(d[key])) ||
45
+ !["file_boundary", "dependency_boundary"].every(key => Array.isArray(d[key]) && (key !== "file_boundary" || d[key].length > 0) &&
46
+ d[key].every(nonblank)))
47
+ return refuse("renewed_scope_approval_required", "approved delta identity or boundary is absent");
48
+ }
49
+ return { ok: true, intent };
50
+ }
51
+ export function isRemainingWork(jobType, payload) {
52
+ if (["remediate", "rebase", "ci_fix", "reconcile_overlap", "resume"].includes(jobType))
53
+ return true;
54
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
55
+ return false;
56
+ const p = payload;
57
+ return p.correction_intent !== undefined || p.continuation !== undefined ||
58
+ p.continuation_of_job_id !== undefined || p.finalization !== undefined ||
59
+ p.remaining_checks !== undefined || p.required_checks_remaining !== undefined;
60
+ }
@@ -118,6 +118,7 @@ const SECRET_NAME_FRAGMENTS = ["TOKEN", "SECRET", "PASSWORD", "API_KEY"];
118
118
  * without anything in the executor choosing that.
119
119
  */
120
120
  export const EXPLICIT_DENY_KEYS = [
121
+ "BAPI_CORRECTION_INTENT_VERSION",
121
122
  "BRIDGE_MCP_PROFILE",
122
123
  // BAPI-1301: the per-job ownership tag. The executor injects a fresh,
123
124
  // unpredictable value AFTER this builder and the adapter have run
@@ -287,6 +288,11 @@ export function buildExecutorBaseWorkerEnv(parentEnv, options = {}) {
287
288
  env.POSTGRES_DATABASE = WORKER_SENTINEL_DATABASE_NAME;
288
289
  env.POSTGRES_DB = WORKER_SENTINEL_DATABASE_NAME;
289
290
  env.PGDATABASE = WORKER_SENTINEL_DATABASE_NAME;
291
+ if (options.correctionIntentVersion !== undefined) {
292
+ if (options.correctionIntentVersion !== 1)
293
+ throw new Error("unsupported correction intent version");
294
+ env.BAPI_CORRECTION_INTENT_VERSION = "1";
295
+ }
290
296
  if (typeof options.effectiveBaseBranch === "string" &&
291
297
  options.effectiveBaseBranch.length > 0) {
292
298
  env[PR_BASE_BRANCH_ENV_VAR] = options.effectiveBaseBranch;
@@ -294,6 +294,18 @@ export function createExecutorHttpClient(config) {
294
294
  // 5xx and any other unexpected status are retryable.
295
295
  return { kind: "retryable", error: `claim failed (HTTP ${status}): ${boundedDetail(text)}` };
296
296
  },
297
+ async scopeReadiness(job) {
298
+ const { status, text } = await post(`/${job.id}/scope-readiness`, { repo_name: job.repo_name, claim_token: job.claim_token }, job.repo_name);
299
+ if (status !== 200)
300
+ throw new Error(`scope readiness HTTP ${status}`);
301
+ const parsed = JSON.parse(text);
302
+ if (!parsed || typeof parsed !== "object" ||
303
+ !("decision" in parsed) ||
304
+ !["ready", "waiting", "blocked"].includes(String(parsed.decision))) {
305
+ throw new Error("scope readiness response malformed");
306
+ }
307
+ return parsed;
308
+ },
297
309
  // Heartbeat does NOT go through `mutate()`: it is the one mutation whose
298
310
  // response BODY carries information beyond the outcome (the BAPI-731 stop
299
311
  // request), so it maps the response itself instead of discarding the body.
@@ -371,6 +383,9 @@ export function createExecutorHttpClient(config) {
371
383
  error_kind: failure.error_kind,
372
384
  error_message: failure.error_message,
373
385
  classification: failure.classification,
386
+ // BAPI-1330: the observed process exit, only when the runner has one —
387
+ // a failure without it keeps its exact pre-BAPI-1330 body.
388
+ ...(failure.exit_code !== undefined ? { exit_code: failure.exit_code } : {}),
374
389
  local_commit_count: failure.local_commit_count,
375
390
  last_commit_sha: failure.last_commit_sha,
376
391
  telemetry: failure.telemetry,
@@ -703,13 +703,18 @@ export function buildIntegrationCorrectionJob(action, timeoutSeconds) {
703
703
  job_type: "rebase",
704
704
  ticket_key: action.epic_key,
705
705
  epic_run_id: action.epic_run_id,
706
- payload: { prompt: action.correction_prompt ?? "", base_branch: action.base_branch },
706
+ payload: {
707
+ prompt: action.correction_prompt ?? "", base_branch: action.base_branch,
708
+ correction_intent: action.correction_intent ?? null,
709
+ integration_action_id: action.action_id,
710
+ },
707
711
  expected_branch: action.epic_branch,
708
712
  expected_head_sha: action.expected_epic_head_sha,
709
713
  lease_expires_at: action.lease_expires_at,
710
714
  attempt_count: 1,
711
715
  max_attempts: 1,
712
716
  timeout_seconds: timeoutSeconds,
717
+ required_capabilities: ["correction_intent.v1"],
713
718
  };
714
719
  }
715
720
  /**
@@ -28,10 +28,11 @@
28
28
  * exited cleanly and its exact HEAD is durable on origin, but no usable pull
29
29
  * request exists for that branch: none is visible, its URL is blank, its
30
30
  * description is blank (BAPI-824 folded in), or it does not yet carry the
31
- * pushed head. The work is COMPLETE and PUSHED — only the attachment is
32
- * missing — so this kind is deliberately distinct from every other
33
- * finalization failure, and its guidance names attachment and warns against
34
- * deleting or rebasing the branch; see `worker-finalization.ts`.
31
+ * pushed head. The commits are PUSHED and the attachment is missing; whether
32
+ * the required work is finished or its checks passed is NOT established by
33
+ * this kind (BAPI-1330). It stays distinct from every other finalization
34
+ * failure, and its guidance names attachment and warns against deleting or
35
+ * rebasing the branch; see `worker-finalization.ts`.
35
36
  * - `WorkerFinalizationSavedButUnfinalized` — an implementation-style job
36
37
  * (BAPI-762) exited cleanly, and an origin branch exists at an authoritative
37
38
  * tip that does NOT match the worker's own HEAD — durable work exists on
@@ -132,6 +133,14 @@ export const WorkerFinalizationMissingRemoteBranchAndPr = "WorkerFinalizationMis
132
133
  export const WorkerFinalizationPrBaseMismatch = "WorkerFinalizationPrBaseMismatch";
133
134
  export const WorkerFinalizationPrNotAttached = "WorkerFinalizationPrNotAttached";
134
135
  export const WorkerFinalizationSavedButUnfinalized = "WorkerFinalizationSavedButUnfinalized";
136
+ /**
137
+ * BAPI-1330: a COVERED attempt's publication could not be proven — the worker's
138
+ * full HEAD was unknown, or the pull request reported no head commit. Distinct
139
+ * from `WorkerFinalizationPrNotAttached` because nothing here was observed to be
140
+ * missing: the facts needed to decide were unavailable, so the finalization
141
+ * verdict is `unknown`, never `unmet` and never `ok`.
142
+ */
143
+ export const WorkerFinalizationPublicationUnverified = "WorkerFinalizationPublicationUnverified";
135
144
  export const WorkerConfigIsolation = "ContractError.WorkerConfigIsolation";
136
145
  export const PreSpawnVerification = "ContractError.PreSpawnVerification";
137
146
  export const BridgeCredentialUnavailable = "ContractError.BridgeCredentialUnavailable";