harnery 0.28.0 → 0.30.0

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 (59) hide show
  1. package/dist/commands/work.d.ts +2 -1
  2. package/dist/commands/work.d.ts.map +1 -1
  3. package/dist/commands/work.js +44 -4
  4. package/dist/core/harnesses/types.d.ts +5 -0
  5. package/dist/core/harnesses/types.d.ts.map +1 -1
  6. package/dist/core/supervisor/plan-read.js +7 -1
  7. package/dist/core/supervisor/plan-types.d.ts +10 -0
  8. package/dist/core/supervisor/plan-types.d.ts.map +1 -1
  9. package/dist/core/supervisor/planning.d.ts.map +1 -1
  10. package/dist/core/supervisor/planning.js +18 -0
  11. package/dist/core/supervisor/state.d.ts.map +1 -1
  12. package/dist/core/supervisor/state.js +65 -11
  13. package/dist/core/work/runner.d.ts.map +1 -1
  14. package/dist/core/work/runner.js +11 -2
  15. package/dist/core/work/state.d.ts +17 -0
  16. package/dist/core/work/state.d.ts.map +1 -1
  17. package/dist/core/work/state.js +102 -3
  18. package/dist/core/workflow/engine.js +13 -0
  19. package/dist/core/workflow/index.d.ts +3 -2
  20. package/dist/core/workflow/index.d.ts.map +1 -1
  21. package/dist/core/workflow/index.js +2 -1
  22. package/dist/core/workflow/proof.d.ts +18 -1
  23. package/dist/core/workflow/proof.d.ts.map +1 -1
  24. package/dist/core/workflow/proof.js +34 -0
  25. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  26. package/dist/core/workflow/spawn-claude.js +19 -2
  27. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  28. package/dist/core/workflow/spawn-codex.js +17 -2
  29. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  30. package/dist/core/workflow/spawn-cursor.js +18 -2
  31. package/dist/core/workflow/spawn-failure.d.ts +14 -0
  32. package/dist/core/workflow/spawn-failure.d.ts.map +1 -1
  33. package/dist/core/workflow/spawn-failure.js +34 -0
  34. package/dist/core/workflow/types.d.ts +26 -0
  35. package/dist/core/workflow/types.d.ts.map +1 -1
  36. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  37. package/dist/core/workflow/workspaces/local-git.js +101 -20
  38. package/dist/lib/exec.d.ts +7 -0
  39. package/dist/lib/exec.d.ts.map +1 -1
  40. package/dist/lib/exec.js +6 -3
  41. package/package.json +1 -1
  42. package/src/commands/work.ts +51 -4
  43. package/src/core/harnesses/types.ts +5 -0
  44. package/src/core/supervisor/plan-read.ts +11 -2
  45. package/src/core/supervisor/plan-types.ts +11 -0
  46. package/src/core/supervisor/planning.ts +21 -0
  47. package/src/core/supervisor/state.ts +68 -11
  48. package/src/core/work/runner.ts +11 -2
  49. package/src/core/work/state.ts +125 -3
  50. package/src/core/workflow/engine.ts +11 -0
  51. package/src/core/workflow/index.ts +3 -0
  52. package/src/core/workflow/proof.ts +36 -0
  53. package/src/core/workflow/spawn-claude.ts +21 -2
  54. package/src/core/workflow/spawn-codex.ts +17 -2
  55. package/src/core/workflow/spawn-cursor.ts +18 -2
  56. package/src/core/workflow/spawn-failure.ts +40 -0
  57. package/src/core/workflow/types.ts +27 -0
  58. package/src/core/workflow/workspaces/local-git.ts +112 -22
  59. package/src/lib/exec.ts +13 -3
@@ -25,6 +25,12 @@ const MAX_EVENTS_BYTES = 4 * 1024 * 1024;
25
25
  const MAX_EVENT_BYTES = 16 * 1024;
26
26
  const MAX_EVENTS = 1_000;
27
27
  const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
28
+ /** Default ceiling on consecutive uncharged attempts (ADR 0046). Kept low: each
29
+ * one is a real vendor round-trip, so a handful gives a transient outage room
30
+ * to recover before the item stops and names the outside service. There is no
31
+ * backoff between them beyond the cadence of the supervisor and the vendor's own
32
+ * responses, which is why the bound stays small. */
33
+ const DEFAULT_MAX_UNCHARGED_ATTEMPTS = 3;
28
34
  export function createWorkItem(input) {
29
35
  const coordRoot = resolve(input.coordRoot);
30
36
  const id = input.id ?? newWorkId();
@@ -45,6 +51,12 @@ export function createWorkItem(input) {
45
51
  if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 100) {
46
52
  throw new Error("work maxAttempts must be an integer from 1 to 100");
47
53
  }
54
+ const maxUnchargedAttempts = input.maxUnchargedAttempts ?? DEFAULT_MAX_UNCHARGED_ATTEMPTS;
55
+ if (!Number.isSafeInteger(maxUnchargedAttempts) ||
56
+ maxUnchargedAttempts < 1 ||
57
+ maxUnchargedAttempts > 100) {
58
+ throw new Error("work maxUnchargedAttempts must be an integer from 1 to 100");
59
+ }
48
60
  const acceptance = (input.acceptance ?? []).map((value, index) => boundedString(value, `work acceptance[${index}]`, MAX_ACCEPTANCE_ITEM));
49
61
  if (acceptance.length > MAX_ACCEPTANCE) {
50
62
  throw new Error(`work acceptance exceeds ${MAX_ACCEPTANCE} criteria`);
@@ -59,6 +71,7 @@ export function createWorkItem(input) {
59
71
  dependencies,
60
72
  workflow: { path: workflowPath, sha256: workflowScriptDigest(workflowPath) },
61
73
  max_attempts: maxAttempts,
74
+ max_uncharged_attempts: maxUnchargedAttempts,
62
75
  source,
63
76
  created_at: new Date().toISOString(),
64
77
  };
@@ -178,13 +191,18 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
178
191
  const terminal = latestGovernance?.event === "work.accepted" || latestGovernance?.event === "work.cancelled";
179
192
  const attemptEvents = events.filter((event) => event.event === "attempt.started");
180
193
  const attempts = attemptEvents.map((event, index) => inspectAttempt(coordRoot, event, intent, attemptEvents[index - 1]?.run_id));
194
+ // Charged attempts — not the raw count — are what max_attempts budgets
195
+ // (ADR 0046). An uncharged environment/upstream attempt still increments
196
+ // attempts_used (ordering + next number) but not the budget.
197
+ const chargedAttempts = attempts.filter((attempt) => attempt.uncharged === undefined).length;
181
198
  const base = {
182
199
  id: intent.id,
183
200
  title: intent.title,
184
201
  unresolved_dependencies: [],
185
202
  attempts,
186
203
  attempts_used: attempts.length,
187
- attempts_remaining: Math.max(0, intent.max_attempts - attempts.length),
204
+ charged_attempts: chargedAttempts,
205
+ attempts_remaining: Math.max(0, intent.max_attempts - chargedAttempts),
188
206
  latest_run_id: attempts.at(-1)?.run_id,
189
207
  approval_id: attempts.at(-1)?.approval_id,
190
208
  proof_path: attempts.at(-1)?.proof_path,
@@ -266,7 +284,7 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
266
284
  next_action: "review",
267
285
  };
268
286
  }
269
- const attemptsRemaining = intent.max_attempts - attempts.length;
287
+ const attemptsRemaining = intent.max_attempts - chargedAttempts;
270
288
  if (latest.status === "journal_unreadable") {
271
289
  return {
272
290
  ...base,
@@ -275,6 +293,49 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
275
293
  next_action: attemptsRemaining > 0 ? "retry" : "none",
276
294
  };
277
295
  }
296
+ // Uncharged failures (ADR 0046) are handled before the ordinary work-failure
297
+ // path: they did not touch the work, so they neither spent the budget nor
298
+ // signal that retrying the work would help.
299
+ if (latest.status === "failed" && latest.uncharged === "environment") {
300
+ // A missing precondition. The operator chose to STOP the item immediately
301
+ // rather than retry an unchanged environment (ADR 0046); next_action "none"
302
+ // stops the supervisor. A human who fixes the environment can still force a
303
+ // retry — the attempt was uncharged, so the budget is intact.
304
+ return {
305
+ ...base,
306
+ state: "blocked",
307
+ reason: environmentBlockedReason(latest),
308
+ next_action: "none",
309
+ };
310
+ }
311
+ if (latest.status === "failed" && latest.uncharged === "upstream") {
312
+ // Count trailing consecutive uncharged attempts in the current window. The
313
+ // bound is the only brake on an outage that never ends, so at the limit the
314
+ // item stops and names the outside service — distinct from work-blocked.
315
+ const maxUncharged = intent.max_uncharged_attempts ?? DEFAULT_MAX_UNCHARGED_ATTEMPTS;
316
+ let trailingUncharged = 0;
317
+ for (let index = currentAttempts.length - 1; index >= 0; index--) {
318
+ if (currentAttempts[index].uncharged === undefined)
319
+ break;
320
+ trailingUncharged++;
321
+ }
322
+ if (trailingUncharged >= maxUncharged || attemptsRemaining <= 0) {
323
+ return {
324
+ ...base,
325
+ state: "blocked",
326
+ reason: trailingUncharged >= maxUncharged
327
+ ? `blocked waiting on an outside service after ${trailingUncharged} consecutive uncharged attempt(s): ${upstreamReason(latest)}`
328
+ : `workflow attempt ${latest.number} was uncharged (${upstreamReason(latest)}) but the work attempt budget is exhausted`,
329
+ next_action: "none",
330
+ };
331
+ }
332
+ return {
333
+ ...base,
334
+ state: "blocked",
335
+ reason: `workflow attempt ${latest.number} did not touch the work; an outside service refused: ${upstreamReason(latest)}`,
336
+ next_action: "retry",
337
+ };
338
+ }
278
339
  return {
279
340
  ...base,
280
341
  state: "blocked",
@@ -284,6 +345,31 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
284
345
  next_action: attemptsRemaining > 0 ? "retry" : "none",
285
346
  };
286
347
  }
348
+ /** The failure reason for an uncharged attempt, read from the proof when
349
+ * present. Bounded so a verbose vendor transcript cannot bloat the projection
350
+ * reason (which is persisted verbatim into the reconciliation event). */
351
+ function unchargedProofError(attempt) {
352
+ if (!attempt.proof_path)
353
+ return undefined;
354
+ try {
355
+ const proof = parseObject(readFileSync(attempt.proof_path, "utf8"), "workflow proof");
356
+ const run = proof.run;
357
+ const error = run && typeof run === "object" ? run.error : undefined;
358
+ return typeof error === "string" && error.trim() ? boundedJournalError(error) : undefined;
359
+ }
360
+ catch {
361
+ return undefined;
362
+ }
363
+ }
364
+ function environmentBlockedReason(attempt) {
365
+ const detail = unchargedProofError(attempt);
366
+ return detail
367
+ ? `workflow attempt ${attempt.number} could not start; a required precondition is missing: ${detail}`
368
+ : `workflow attempt ${attempt.number} could not start; a required precondition is missing`;
369
+ }
370
+ function upstreamReason(attempt) {
371
+ return unchargedProofError(attempt) ?? "the vendor was reached and refused";
372
+ }
287
373
  function inspectAttempt(coordRoot, event, intent, priorRunId) {
288
374
  const runId = event.run_id;
289
375
  const attempt = {
@@ -334,6 +420,13 @@ function inspectAttempt(coordRoot, event, intent, priorRunId) {
334
420
  proof.acceptance.summary.unknown === 0
335
421
  ? "succeeded"
336
422
  : "failed";
423
+ // A failed attempt the run classified as uninformative about the work is
424
+ // uncharged (ADR 0046). Absent class ⇒ charged, as before. Read only for a
425
+ // failed attempt: a succeeded run never carries a class.
426
+ if (attempt.status === "failed" &&
427
+ (proof.run.class === "environment" || proof.run.class === "upstream")) {
428
+ attempt.uncharged = proof.run.class;
429
+ }
337
430
  return attempt;
338
431
  }
339
432
  const journalPath = workflowJournalPath(coordRoot, runId);
@@ -596,7 +689,13 @@ function validateWorkIntent(intent, workId) {
596
689
  !/^[a-f0-9]{64}$/.test(intent.workflow.sha256) ||
597
690
  !Number.isSafeInteger(intent.max_attempts) ||
598
691
  intent.max_attempts < 1 ||
599
- intent.max_attempts > 100) {
692
+ intent.max_attempts > 100 ||
693
+ // Optional for back-compat: absent on pre-ADR-0046 intents. When present it
694
+ // must be a valid bound.
695
+ (intent.max_uncharged_attempts !== undefined &&
696
+ (!Number.isSafeInteger(intent.max_uncharged_attempts) ||
697
+ intent.max_uncharged_attempts < 1 ||
698
+ intent.max_uncharged_attempts > 100))) {
600
699
  throw new Error(`work intent ${workId} has an unsupported or mismatched schema`);
601
700
  }
602
701
  boundedString(intent.title, "work title", MAX_TITLE);
@@ -774,6 +774,12 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
774
774
  costUsd += last.costUsd ?? 0;
775
775
  if (!last.ok) {
776
776
  journal("agent.attempt_failed", { id, attempt, error: last.error });
777
+ // ADR 0046: an environment failure (the binary was absent) cannot be
778
+ // helped by retrying an unchanged environment, so stop the in-agent
779
+ // retry too — not just the outer attempt/replan budget. An upstream
780
+ // refusal keeps retrying here: the vendor may recover mid-loop.
781
+ if (last.class === "environment")
782
+ break;
777
783
  continue; // spawn-level failure: retry with the original prompt
778
784
  }
779
785
  if (!agentOpts.schema) {
@@ -833,6 +839,13 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
833
839
  agentCostUsd > 0 || last?.costUsd !== undefined ? agentCostUsd : undefined;
834
840
  agentProof.session_id = last?.sessionId;
835
841
  agentProof.error = reason;
842
+ // Carry the spawn class (environment/upstream) onto the proof only when the
843
+ // final outcome was a spawn failure. A schema failure after a spawn that
844
+ // reached the model is a work failure — left unclassed (charged). An
845
+ // earlier attempt that transiently failed and then succeeded returned
846
+ // above, so this only fires when the agent genuinely failed.
847
+ if (last && !last.ok && last.class)
848
+ agentProof.class = last.class;
836
849
  journal("agent.failed", { id, error: reason });
837
850
  throw new Error(`agent ${proofLabel}: ${reason}`);
838
851
  }
@@ -2,10 +2,11 @@ export type { CreateWorkflowApprovalInput, ResolveWorkflowApprovalInput, Workflo
2
2
  export { assertWorkflowRunId, createWorkflowApproval, listWorkflowApprovals, readWorkflowApproval, renderWorkflowApproval, resolveWorkflowApproval, WORKFLOW_APPROVAL_SCHEMA_VERSION, workflowApprovalId, } from "./approvals.js";
3
3
  export { freezeWorkflowAttemptContext, isCanonicalWorkflowAttemptContext, normalizeWorkflowAttemptContext, } from "./attempt-context.js";
4
4
  export { runWorkflow, WorkflowParkedError, WorkflowRunError } from "./engine.js";
5
- export { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
5
+ export { buildWorkflowProof, createEvidenceRecord, deriveRunFailureClass, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
6
6
  export type { WorkflowRunManifest } from "./run-state.js";
7
7
  export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, readWorkflowRunManifest, WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION, workflowRunManifestPath, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
8
- export type { AcceptanceCriterion, AcceptanceResult, AcceptanceStatus, AcceptanceSummary, AgentOpts, EngineOpts, EvidenceKind, EvidenceSource, EvidenceStatus, HarnessEvidenceCapability, HarnessEvidenceCoverage, HarnessName, ResultDigest, RunReport, Spawner, SpawnRequest, SpawnResult, StageSchema, WorkflowAgentProof, WorkflowApprovalMode, WorkflowAttemptContext, WorkflowAttemptFailureCause, WorkflowAttemptPriorContext, WorkflowAttemptUnresolvedCriterion, WorkflowContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowModule, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowRunStatus, WorkflowSpecialistProfile, WorkflowWorkContext, } from "./types.js";
8
+ export { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
9
+ export type { AcceptanceCriterion, AcceptanceResult, AcceptanceStatus, AcceptanceSummary, AgentOpts, EngineOpts, EvidenceKind, EvidenceSource, EvidenceStatus, HarnessEvidenceCapability, HarnessEvidenceCoverage, HarnessName, ResultDigest, RunReport, Spawner, SpawnFailureClass, SpawnRequest, SpawnResult, StageSchema, WorkflowAgentProof, WorkflowApprovalMode, WorkflowAttemptContext, WorkflowAttemptFailureCause, WorkflowAttemptPriorContext, WorkflowAttemptUnresolvedCriterion, WorkflowContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowModule, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowRunStatus, WorkflowSpecialistProfile, WorkflowWorkContext, } from "./types.js";
9
10
  export { WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION, WORKFLOW_PROOF_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "./types.js";
10
11
  export type { ApplyIntegrationInput, AuthorizedIntegrationPlan, AuthorizedProviderIntegrationInput, CancelWorkspaceInput, CleanupWorkspaceInput, FilesystemIdentity, IntegrationApplyAttempt, IntegrationApplyReceipt, IntegrationAuthorization, IntegrationPlan, IntegrationReviewRecord, IntegrationTarget, LocalGitWorktreeProviderOptions, PrepareIntegrationInput, ProviderIntegrationInput, ProviderIntegrationPreview, ProviderIntegrationResult, ProviderUnknown, ProviderUnsupportedReason, RepositoryBinding, ValidatedFilesystemPath, WorkflowWorkspaceInspection, WorkflowWorkspaceSelection, WorkflowWorkspaceStatus, WorkflowWorkspaceVerification, WorkspaceAllocationRequest, WorkspaceAttestation, WorkspaceBinding, WorkspaceBoundExecutionEvidence, WorkspaceCancellationReceipt, WorkspaceCancellationResult, WorkspaceCleanupAttempt, WorkspaceCleanupIntent, WorkspaceCleanupMode, WorkspaceCleanupReceipt, WorkspaceCleanupResult, WorkspaceCompatibilityExecutionEvidence, WorkspaceExecutionEvidence, WorkspaceIntegrationState, WorkspaceIsolation, WorkspaceLifecycleProjection, WorkspaceLifecycleProjectionInput, WorkspaceLifecycleState, WorkspaceObservation, WorkspaceOwner, WorkspaceProbeInput, WorkspaceProbeResult, WorkspaceProofOutcome, WorkspaceProvider, WorkspaceProviderCapabilities, WorkspaceProviderRef, WorkspaceResourceState, WorkspaceUnsupportedExecutionEvidence, } from "./workspaces/index.js";
11
12
  export { applyIntegration, assertWorkspaceLifecycleTransition, cancelWorkspace, cleanupWorkspace, createLocalGitWorktreeProvider, deriveWorkspaceLifecycle, IntegrationPrepareParkedError, inspectWorkflowWorkspace, isWorkspaceLifecycleState, listWorkflowWorkspaceInspections, prepareIntegration, prepareReclaim, probeLocalGitWorktreeProvider, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, validateProviderEventChain, WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./workspaces/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  export { assertWorkflowRunId, createWorkflowApproval, listWorkflowApprovals, readWorkflowApproval, renderWorkflowApproval, resolveWorkflowApproval, WORKFLOW_APPROVAL_SCHEMA_VERSION, workflowApprovalId, } from "./approvals.js";
2
2
  export { freezeWorkflowAttemptContext, isCanonicalWorkflowAttemptContext, normalizeWorkflowAttemptContext, } from "./attempt-context.js";
3
3
  export { runWorkflow, WorkflowParkedError, WorkflowRunError } from "./engine.js";
4
- export { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
4
+ export { buildWorkflowProof, createEvidenceRecord, deriveRunFailureClass, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
5
5
  export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, readWorkflowRunManifest, WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION, workflowRunManifestPath, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
6
+ export { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
6
7
  export { WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION, WORKFLOW_PROOF_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "./types.js";
7
8
  export { applyIntegration, assertWorkspaceLifecycleTransition, cancelWorkspace, cleanupWorkspace, createLocalGitWorktreeProvider, deriveWorkspaceLifecycle, IntegrationPrepareParkedError, inspectWorkflowWorkspace, isWorkspaceLifecycleState, listWorkflowWorkspaceInspections, prepareIntegration, prepareReclaim, probeLocalGitWorktreeProvider, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, validateProviderEventChain, WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./workspaces/index.js";
@@ -1,6 +1,6 @@
1
1
  import type { RepoSnapshot } from "../context/index.js";
2
2
  import type { NormalizedPolicy, PolicyDecision, PolicyIsolation, PolicyNetworkAccess } from "../policy/index.js";
3
- import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, HarnessAttestationCitation, HarnessEvidenceCapability, ResultDigest, WorkflowAgentProof, WorkflowAttemptContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowProof, WorkflowSandboxProjectionEvidence, WorkflowWorkContext } from "./types.js";
3
+ import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, HarnessAttestationCitation, HarnessEvidenceCapability, ResultDigest, SpawnFailureClass, WorkflowAgentProof, WorkflowAttemptContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowProof, WorkflowSandboxProjectionEvidence, WorkflowWorkContext } from "./types.js";
4
4
  import type { WorkspaceAttestation, WorkspaceBinding, WorkspaceUnsupportedExecutionEvidence } from "./workspaces/index.js";
5
5
  export interface NormalizedWorkflowMeta {
6
6
  name: string;
@@ -54,6 +54,23 @@ export declare function rollupAcceptance(criteria: AcceptanceCriterion[], eviden
54
54
  summary: AcceptanceSummary;
55
55
  };
56
56
  export declare function digestResult(value: unknown, kind?: "text" | "json"): ResultDigest;
57
+ /**
58
+ * The run-level failure class (ADR 0046), derived from the agents rather than a
59
+ * single terminal throw so it survives a script's `parallel()` swallowing the
60
+ * rejection — a swallowed agent's proof is still recorded with its class.
61
+ *
62
+ * Rules, in order, all in service of "default to charging":
63
+ * 1. A succeeded run is never classed (there is nothing uncharged about it).
64
+ * 2. If ANY agent produced a result (succeeded or replayed from cache) the
65
+ * attempt was informative about the work — charge it. This also keeps a
66
+ * resumed run whose earlier segment did real work from being written off by
67
+ * a later environment failure.
68
+ * 3. Otherwise, among the failed agents, environment wins over upstream: a
69
+ * missing binary means nothing ran at all, and it is the operator-chosen
70
+ * hard stop.
71
+ * 4. Anything else is undefined ⇒ a charged work failure, exactly as today.
72
+ */
73
+ export declare function deriveRunFailureClass(status: "succeeded" | "failed", agents: readonly Pick<WorkflowAgentProof, "status" | "class">[]): SpawnFailureClass | undefined;
57
74
  export declare function buildWorkflowProof(input: BuildWorkflowProofInput): WorkflowProof;
58
75
  export declare function writeWorkflowProof(path: string, proof: WorkflowProof): void;
59
76
  export declare function readWorkflowProof(coordRoot: string, runId: string): WorkflowProof;
@@ -1 +1 @@
1
- {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"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"}
@@ -108,6 +108,35 @@ export function digestResult(value, kind) {
108
108
  bytes: Buffer.byteLength(serialized),
109
109
  };
110
110
  }
111
+ /**
112
+ * The run-level failure class (ADR 0046), derived from the agents rather than a
113
+ * single terminal throw so it survives a script's `parallel()` swallowing the
114
+ * rejection — a swallowed agent's proof is still recorded with its class.
115
+ *
116
+ * Rules, in order, all in service of "default to charging":
117
+ * 1. A succeeded run is never classed (there is nothing uncharged about it).
118
+ * 2. If ANY agent produced a result (succeeded or replayed from cache) the
119
+ * attempt was informative about the work — charge it. This also keeps a
120
+ * resumed run whose earlier segment did real work from being written off by
121
+ * a later environment failure.
122
+ * 3. Otherwise, among the failed agents, environment wins over upstream: a
123
+ * missing binary means nothing ran at all, and it is the operator-chosen
124
+ * hard stop.
125
+ * 4. Anything else is undefined ⇒ a charged work failure, exactly as today.
126
+ */
127
+ export function deriveRunFailureClass(status, agents) {
128
+ if (status !== "failed")
129
+ return undefined;
130
+ if (agents.some((agent) => agent.status === "succeeded" || agent.status === "cached")) {
131
+ return undefined;
132
+ }
133
+ const failed = agents.filter((agent) => agent.status === "failed");
134
+ if (failed.some((agent) => agent.class === "environment"))
135
+ return "environment";
136
+ if (failed.some((agent) => agent.class === "upstream"))
137
+ return "upstream";
138
+ return undefined;
139
+ }
111
140
  export function buildWorkflowProof(input) {
112
141
  const acceptance = rollupAcceptance(input.meta.acceptance, input.evidence);
113
142
  const repository = buildRepoEvidence(input.before, input.after);
@@ -121,6 +150,7 @@ export function buildWorkflowProof(input) {
121
150
  }));
122
151
  const harnesses = buildHarnessCoverage(agents, input.harnessEvidence, input.harnessAttestations);
123
152
  const unknowns = buildUnknowns(agents, harnesses, repository);
153
+ const runClass = deriveRunFailureClass(input.status, agents);
124
154
  const journal = readFileSync(input.journalPath);
125
155
  return {
126
156
  schema_version: WORKFLOW_PROOF_SCHEMA_VERSION,
@@ -137,6 +167,7 @@ export function buildWorkflowProof(input) {
137
167
  objective: input.meta.objective,
138
168
  error: clippedOptional(input.error, MAX_SUMMARY_CHARS),
139
169
  result: input.result === undefined ? undefined : digestResult(input.result),
170
+ ...(runClass ? { class: runClass } : {}),
140
171
  },
141
172
  acceptance,
142
173
  agents,
@@ -212,6 +243,9 @@ export function readWorkflowProof(coordRoot, runId) {
212
243
  }
213
244
  if (proof.schema_version !== WORKFLOW_PROOF_SCHEMA_VERSION ||
214
245
  proof.run?.id !== runId ||
246
+ (proof.run.class !== undefined &&
247
+ proof.run.class !== "environment" &&
248
+ proof.run.class !== "upstream") ||
215
249
  (proof.run.work_context !== undefined &&
216
250
  (!proof.run.work_item_id ||
217
251
  proof.run.work_context.id !== proof.run.work_item_id ||
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAsB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAwDxE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAc/B,CAAC"}
1
+ {"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAsB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA2ExE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAc/B,CAAC"}
@@ -22,7 +22,7 @@ import { builtinHarnessProfile, validateHarnessEffort } from "../harnesses/profi
22
22
  import { buildChildEnv } from "./child-env.js";
23
23
  import { notFoundError } from "./harnesses.js";
24
24
  import { resolveSandboxProjection } from "./sandbox-projection.js";
25
- import { vendorFailureText } from "./spawn-failure.js";
25
+ import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
26
26
  export function buildClaudeInvocation(req) {
27
27
  validateHarnessEffort("claude-code", req.effort);
28
28
  if (req.filesystemPolicy) {
@@ -53,7 +53,20 @@ export function normalizeClaudeResult(raw) {
53
53
  error: `claude timed out after ${raw.durationMs}ms and was killed`,
54
54
  };
55
55
  }
56
+ // Structural environment signal: the binary was never there (spawned directly,
57
+ // so a missing binary surfaces as ENOENT). Uncharged and not retried.
58
+ if (raw.spawnErrno === "ENOENT") {
59
+ return {
60
+ ok: false,
61
+ text: "",
62
+ durationMs: raw.durationMs,
63
+ error: notFoundError("claude-code"),
64
+ class: "environment",
65
+ };
66
+ }
56
67
  if (raw.exitCode === 127) {
68
+ // A bare 127 with no errno is a shell/vendor 127, indistinguishable from a
69
+ // legitimate one — charged as work rather than classed environment.
57
70
  return {
58
71
  ok: false,
59
72
  text: "",
@@ -62,11 +75,13 @@ export function normalizeClaudeResult(raw) {
62
75
  };
63
76
  }
64
77
  if (raw.exitCode !== 0) {
78
+ const failureText = vendorFailureText(raw);
65
79
  return {
66
80
  ok: false,
67
81
  text: "",
68
82
  durationMs: raw.durationMs,
69
- error: `claude exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
83
+ error: `claude exited ${raw.exitCode}: ${failureText}`,
84
+ ...(isUpstreamFailureText(failureText) ? { class: "upstream" } : {}),
70
85
  };
71
86
  }
72
87
  let envelope;
@@ -82,6 +97,7 @@ export function normalizeClaudeResult(raw) {
82
97
  };
83
98
  }
84
99
  if (envelope.is_error) {
100
+ const envelopeError = `${envelope.subtype ?? ""} ${(envelope.errors ?? []).join("; ")} ${String(envelope.result ?? "")}`;
85
101
  return {
86
102
  ok: false,
87
103
  text: String(envelope.result ?? ""),
@@ -89,6 +105,7 @@ export function normalizeClaudeResult(raw) {
89
105
  costUsd: envelope.total_cost_usd,
90
106
  durationMs: raw.durationMs,
91
107
  error: `harness error (${envelope.subtype ?? "unknown"}): ${(envelope.errors ?? []).join("; ") || "see envelope"}`,
108
+ ...(isUpstreamFailureText(envelopeError) ? { class: "upstream" } : {}),
92
109
  };
93
110
  }
94
111
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CA8B9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAyBvE;AAED,eAAO,MAAM,YAAY,EAAE,OA2B1B,CAAC"}
1
+ {"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CA8B9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAwCvE;AAED,eAAO,MAAM,YAAY,EAAE,OA2B1B,CAAC"}
@@ -23,7 +23,7 @@ import { builtinHarnessProfile, validateHarnessEffort } from "../harnesses/profi
23
23
  import { buildChildEnv } from "./child-env.js";
24
24
  import { notFoundError } from "./harnesses.js";
25
25
  import { resolveSandboxProjection } from "./sandbox-projection.js";
26
- import { vendorFailureText } from "./spawn-failure.js";
26
+ import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
27
27
  export function buildCodexInvocation(req, resultFile) {
28
28
  validateHarnessEffort("codex", req.effort);
29
29
  if (!resultFile)
@@ -60,15 +60,30 @@ export function normalizeCodexResult(raw) {
60
60
  error: `codex timed out after ${raw.durationMs}ms and was killed`,
61
61
  };
62
62
  }
63
+ // Structural environment signal: the binary was never there (spawned directly,
64
+ // so a missing binary surfaces as ENOENT). Uncharged and not retried.
65
+ if (raw.spawnErrno === "ENOENT") {
66
+ return {
67
+ ok: false,
68
+ text: "",
69
+ durationMs: raw.durationMs,
70
+ error: notFoundError("codex"),
71
+ class: "environment",
72
+ };
73
+ }
63
74
  if (raw.exitCode === 127) {
75
+ // A bare 127 with no errno is a shell/vendor 127, indistinguishable from a
76
+ // legitimate one — charged as work rather than classed environment.
64
77
  return { ok: false, text: "", durationMs: raw.durationMs, error: notFoundError("codex") };
65
78
  }
66
79
  if (raw.exitCode !== 0) {
80
+ const failureText = vendorFailureText(raw);
67
81
  return {
68
82
  ok: false,
69
83
  text: "",
70
84
  durationMs: raw.durationMs,
71
- error: `codex exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
85
+ error: `codex exited ${raw.exitCode}: ${failureText}`,
86
+ ...(isUpstreamFailureText(failureText) ? { class: "upstream" } : {}),
72
87
  };
73
88
  }
74
89
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAkB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAqCxE;AAED,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC"}
1
+ {"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAkB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAqDxE;AAED,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC"}
@@ -23,7 +23,7 @@ import { builtinHarnessProfile, validateHarnessEffort } from "../harnesses/profi
23
23
  import { buildChildEnv } from "./child-env.js";
24
24
  import { notFoundError } from "./harnesses.js";
25
25
  import { resolveSandboxProjection } from "./sandbox-projection.js";
26
- import { vendorFailureText } from "./spawn-failure.js";
26
+ import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.js";
27
27
  /** Exported for unit tests (no live binary to test against). */
28
28
  export function parseCursorOutput(stdout) {
29
29
  try {
@@ -63,15 +63,30 @@ export function normalizeCursorResult(raw) {
63
63
  error: `cursor timed out after ${raw.durationMs}ms and was killed`,
64
64
  };
65
65
  }
66
+ // Structural environment signal: the binary was never there (spawned directly,
67
+ // so a missing binary surfaces as ENOENT). Uncharged and not retried.
68
+ if (raw.spawnErrno === "ENOENT") {
69
+ return {
70
+ ok: false,
71
+ text: "",
72
+ durationMs: raw.durationMs,
73
+ error: notFoundError("cursor"),
74
+ class: "environment",
75
+ };
76
+ }
66
77
  if (raw.exitCode === 127) {
78
+ // A bare 127 with no errno is a shell/vendor 127, indistinguishable from a
79
+ // legitimate one — charged as work rather than classed environment.
67
80
  return { ok: false, text: "", durationMs: raw.durationMs, error: notFoundError("cursor") };
68
81
  }
69
82
  if (raw.exitCode !== 0) {
83
+ const failureText = vendorFailureText(raw);
70
84
  return {
71
85
  ok: false,
72
86
  text: "",
73
87
  durationMs: raw.durationMs,
74
- error: `cursor-agent exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
88
+ error: `cursor-agent exited ${raw.exitCode}: ${failureText}`,
89
+ ...(isUpstreamFailureText(failureText) ? { class: "upstream" } : {}),
75
90
  };
76
91
  }
77
92
  const parsed = parseCursorOutput(raw.stdout);
@@ -82,6 +97,7 @@ export function normalizeCursorResult(raw) {
82
97
  sessionId: parsed.sessionId,
83
98
  durationMs: raw.durationMs,
84
99
  error: `cursor-agent reported is_error: ${parsed.text.slice(0, 300)}`,
100
+ ...(isUpstreamFailureText(parsed.text) ? { class: "upstream" } : {}),
85
101
  };
86
102
  }
87
103
  return {
@@ -15,4 +15,18 @@ export declare function vendorFailureText(raw: {
15
15
  stdout?: string;
16
16
  stderr?: string;
17
17
  }, maxChars?: number): string;
18
+ /**
19
+ * Whether failure text names an UPSTREAM refusal — the vendor was reached and
20
+ * refused (a 5xx/429 status or circuit-open wording), as opposed to a work
21
+ * failure (the model ran and produced a wrong or incomplete result).
22
+ *
23
+ * There is no structural upstream signal the way ENOENT structurally marks a
24
+ * missing binary, so this is the one text match in the classifier. It is kept
25
+ * SHORT and documented on purpose: a per-vendor regex zoo would rot, and a
26
+ * false positive here wrongly withholds a charge, so both the phrase list and
27
+ * the numeric match stay tight — the number must sit in HTTP-status context, not
28
+ * merely fall in the 429/5xx range. Anything it does not match falls through to
29
+ * a charged work failure.
30
+ */
31
+ export declare function isUpstreamFailureText(text: string): boolean;
18
32
  //# sourceMappingURL=spawn-failure.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-failure.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,mEAAmE;AACnE,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACzC,QAAQ,SAAoB,GAC3B,MAAM,CAQR"}
1
+ {"version":3,"file":"spawn-failure.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,mEAAmE;AACnE,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACzC,QAAQ,SAAoB,GAC3B,MAAM,CAQR;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D"}
@@ -22,3 +22,37 @@ export function vendorFailureText(raw, maxChars = DEFAULT_MAX_CHARS) {
22
22
  const combined = parts.join("\n");
23
23
  return combined.length > maxChars ? `…${combined.slice(-maxChars)}` : combined;
24
24
  }
25
+ // A 429 or 5xx status, but only where it sits in HTTP-status CONTEXT — not any
26
+ // digits that merely happen to fall in that range. A bare-number match treats a
27
+ // line number ("at line 500"), an item count ("got 429 items"), or a duration
28
+ // ("after 502 seconds") as an upstream refusal, which wrongly withholds a charge
29
+ // for a failure that was entirely our code. Three recognized shapes:
30
+ // 1. after an "HTTP" label: "HTTP 429", "HTTP/1.1 503 Service Unavailable"
31
+ // 2. after a "status" label: "status 500", "status: 429", "status code 502"
32
+ // 3. a bare status line: "503 Service Unavailable" (code at line start,
33
+ // then a reason phrase) — the classic "<code> <Reason-Phrase>" shape.
34
+ // The trailing `(?![0-9])` and the label/line-start anchors also keep a status
35
+ // embedded in a longer number ("4295"/"5031") out. All 5xx are server-side; 429
36
+ // is the only 4xx that means "reached and refused, retry later".
37
+ const STATUS = "(?:429|5[0-9]{2})";
38
+ const UPSTREAM_STATUS = new RegExp(`(?:\\bHTTP\\b[/0-9. ]*|\\bstatus(?:\\s*code)?\\b[\\s:=]*)${STATUS}(?![0-9])` +
39
+ `|(?:^|\\n)\\s*${STATUS}\\s+[A-Za-z]`, "i");
40
+ // Explicit vendor wording for the same conditions when a numeric status is
41
+ // absent. Deliberately short — see isUpstreamFailureText.
42
+ const UPSTREAM_PHRASES = /circuit[ _-]?open|service unavailable|too many requests|rate[ _-]?limit|overloaded|bad gateway|gateway time-?out|internal server error/i;
43
+ /**
44
+ * Whether failure text names an UPSTREAM refusal — the vendor was reached and
45
+ * refused (a 5xx/429 status or circuit-open wording), as opposed to a work
46
+ * failure (the model ran and produced a wrong or incomplete result).
47
+ *
48
+ * There is no structural upstream signal the way ENOENT structurally marks a
49
+ * missing binary, so this is the one text match in the classifier. It is kept
50
+ * SHORT and documented on purpose: a per-vendor regex zoo would rot, and a
51
+ * false positive here wrongly withholds a charge, so both the phrase list and
52
+ * the numeric match stay tight — the number must sit in HTTP-status context, not
53
+ * merely fall in the 429/5xx range. Anything it does not match falls through to
54
+ * a charged work failure.
55
+ */
56
+ export function isUpstreamFailureText(text) {
57
+ return UPSTREAM_STATUS.test(text) || UPSTREAM_PHRASES.test(text);
58
+ }