@abelxiaoxing/cadence 1.3.0 → 1.4.1

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 (76) hide show
  1. package/README.md +172 -13
  2. package/agents/design-explorer.md +17 -11
  3. package/agents/diagnosis-worker.md +9 -1
  4. package/agents/implementation-worker.md +3 -1
  5. package/config/plan-draft.example.json +122 -0
  6. package/config/plan-draft.multiple-tasks.example.json +345 -0
  7. package/package.json +9 -5
  8. package/prompts/abel-design.md +141 -38
  9. package/prompts/abel-diagnose.md +10 -1
  10. package/prompts/abel-implement.md +118 -20
  11. package/prompts/abel-init.md +9 -2
  12. package/src/activity-contracts.ts +82 -0
  13. package/src/apply-transaction.ts +16 -14
  14. package/src/artifact-store.ts +17 -0
  15. package/src/candidate-artifact.ts +143 -0
  16. package/src/candidate-patch.ts +33 -12
  17. package/src/candidate-workspace.ts +74 -0
  18. package/src/canonical.ts +16 -0
  19. package/src/change-contract-schema.ts +191 -0
  20. package/src/change-contract.ts +273 -0
  21. package/src/change-verification.ts +896 -0
  22. package/src/child-model.ts +129 -0
  23. package/src/child-session.ts +296 -328
  24. package/src/contracts.ts +325 -109
  25. package/src/control-contracts.ts +51 -1
  26. package/src/delivery-compiler.ts +241 -141
  27. package/src/delivery-revision.ts +42 -0
  28. package/src/design-control.ts +182 -15
  29. package/src/design-diagnostics.ts +150 -0
  30. package/src/design-journal.ts +216 -72
  31. package/src/durable-contracts.ts +210 -0
  32. package/src/durable-workflow.ts +963 -0
  33. package/src/evidence-draft.ts +122 -0
  34. package/src/implement-graph.ts +14 -16
  35. package/src/implement-plan.ts +70 -0
  36. package/src/index.ts +341 -1405
  37. package/src/isolation-backend.ts +84 -23
  38. package/src/model-source.ts +22 -0
  39. package/src/package-candidate.ts +224 -0
  40. package/src/package-delivery.ts +486 -0
  41. package/src/package-verification.ts +509 -0
  42. package/src/package-workflow.ts +253 -0
  43. package/src/packet-runtime.ts +27 -48
  44. package/src/parent-provider.ts +86 -198
  45. package/src/phase-execution.ts +2248 -0
  46. package/src/pi-adapter.ts +19 -0
  47. package/src/plan-draft-summary.ts +146 -0
  48. package/src/plan-draft.ts +493 -0
  49. package/src/route-policy.ts +1 -11
  50. package/src/run-state.ts +4 -16
  51. package/src/run-store.ts +5 -144
  52. package/src/scoped-grep-worker.mjs +23 -0
  53. package/src/scoped-tools.ts +312 -116
  54. package/src/sqlite-schema.ts +171 -0
  55. package/src/storage-schema.ts +352 -0
  56. package/src/subagent-activity.ts +46 -67
  57. package/src/submit-tool.ts +40 -61
  58. package/src/task-ledger.ts +14 -55
  59. package/src/transport-budget.ts +32 -0
  60. package/src/verification-capability.ts +298 -102
  61. package/src/verification-environment.ts +213 -0
  62. package/src/verification-identity.ts +77 -0
  63. package/src/worker-broker.ts +134 -91
  64. package/src/workflow-engine.ts +28 -8285
  65. package/src/workflow-policy.ts +1388 -0
  66. package/src/workflow-recovery-policy.ts +95 -0
  67. package/src/workflow-scheduling.ts +105 -0
  68. package/src/workflow-state-machine.ts +4086 -0
  69. package/src/workflow-status.ts +384 -0
  70. package/src/workspace-io-entry.mjs +58 -0
  71. package/src/workspace-io-worker.mjs +991 -0
  72. package/src/workspace-io.ts +84 -0
  73. package/src/workspace-store.ts +125 -19
  74. package/skills/abel-workflow/SKILL.md +0 -205
  75. package/src/empty-resource-loader.ts +0 -38
  76. package/src/parent-payload-bridge.ts +0 -523
@@ -12,8 +12,11 @@ import {
12
12
  import type {
13
13
  PacketActivityEvent,
14
14
  PacketActivityObserver,
15
- PacketActivityState,
16
- } from "./packet-runtime.ts";
15
+ } from "./activity-contracts.ts";
16
+ import {
17
+ projectDesignDiagnostic,
18
+ type SafeDesignDiagnostic,
19
+ } from "./design-diagnostics.ts";
17
20
 
18
21
  export const ACTIVITY_DETAILS_KEY = "activityDisplay" as const;
19
22
  export const ACTIVITY_WIDGET_KEY = "abel-subagents" as const;
@@ -22,51 +25,20 @@ export const ACTIVITY_REFRESH_MS = 100;
22
25
  export const ACTIVITY_WIDGET_MAX_LINES = 12;
23
26
  export const ACTIVITY_WIDGET_MIN_WIDTH = 3;
24
27
 
25
- export const WORKFLOW_ACTIVITY_STATES = [
26
- "queued",
27
- "connecting",
28
- "waiting-first-response",
29
- "running",
30
- "validating",
31
- "retrying",
32
- "verifying",
33
- "paused",
34
- "approval-needed",
35
- "applying",
36
- "recovering",
37
- "operation-cancelled",
38
- "discarded",
39
- "rejected",
40
- "completed",
41
- ] as const;
42
-
43
- export type WorkflowActivityState = (typeof WORKFLOW_ACTIVITY_STATES)[number];
44
- export type ActivityState = PacketActivityState | WorkflowActivityState;
45
- export type TerminalActivityState =
46
- | Exclude<
47
- PacketActivityState,
48
- | "queued"
49
- | "connecting"
50
- | "waiting-first-response"
51
- | "running"
52
- | "retrying"
53
- >
54
- | WorkflowActivityState;
55
-
56
- export interface WorkflowActivityUpdate {
57
- state: WorkflowActivityState;
58
- stage?: "abel-design" | "abel-implement";
59
- runId?: string;
60
- change?: string;
61
- taskId?: string;
62
- phase?: string;
63
- objective?: string;
64
- code?: string;
65
- attempt?: number;
66
- maxAttempts?: number;
67
- wait?: string;
68
- legalCommands?: string[];
69
- }
28
+ export type {
29
+ ActivityState,
30
+ TerminalActivityState,
31
+ WorkflowActivityState,
32
+ WorkflowActivityUpdate,
33
+ } from "./activity-contracts.ts";
34
+ export { WORKFLOW_ACTIVITY_STATES } from "./activity-contracts.ts";
35
+
36
+ import type {
37
+ ActivityState,
38
+ TerminalActivityState,
39
+ WorkflowActivityState,
40
+ WorkflowActivityUpdate,
41
+ } from "./activity-contracts.ts";
70
42
 
71
43
  export interface EvidenceActivitySummary {
72
44
  kind: "evidence";
@@ -224,7 +196,7 @@ export function sanitizeFailureReason(value: unknown, maxLength = 240): string {
224
196
  function safeDesignFailure(value: unknown): {
225
197
  operation: string;
226
198
  code: string;
227
- diagnostics: Array<Record<string, string>>;
199
+ diagnostics: SafeDesignDiagnostic[];
228
200
  } | null {
229
201
  const failure = asRecord(value);
230
202
  if (
@@ -237,23 +209,9 @@ function safeDesignFailure(value: unknown): {
237
209
  ) {
238
210
  return null;
239
211
  }
240
- const diagnostics = failure.diagnostics.flatMap((candidate) => {
241
- const record = asRecord(candidate);
242
- if (
243
- !record ||
244
- typeof record.code !== "string" ||
245
- !SAFE_DESIGN_DETAIL_VALUE.test(record.code)
246
- ) {
247
- return [];
248
- }
249
- return [
250
- Object.fromEntries(
251
- Object.entries(record).filter(
252
- ([, entry]) =>
253
- typeof entry === "string" && SAFE_DESIGN_DETAIL_VALUE.test(entry),
254
- ),
255
- ) as Record<string, string>,
256
- ];
212
+ const diagnostics = failure.diagnostics.slice(0, 64).flatMap((candidate) => {
213
+ const diagnostic = projectDesignDiagnostic(candidate);
214
+ return diagnostic ? [diagnostic] : [];
257
215
  });
258
216
  return {
259
217
  operation: failure.operation,
@@ -286,7 +244,10 @@ function renderDesignFailure(
286
244
  const lines = failure.diagnostics.map((diagnostic) => {
287
245
  const details = Object.entries(diagnostic)
288
246
  .filter(([key]) => key !== "code")
289
- .map(([key, value]) => `${key}=${value}`)
247
+ .map(
248
+ ([key, value]) =>
249
+ `${key}=${Array.isArray(value) ? JSON.stringify(value) : value}`,
250
+ )
290
251
  .join(" · ");
291
252
  return ` - ${diagnostic.code}${details ? ` · ${details}` : ""}`;
292
253
  });
@@ -315,6 +276,7 @@ function stateGlyph(state: ActivityState, spinnerFrame = 0): string {
315
276
  switch (state) {
316
277
  case "queued":
317
278
  return "…";
279
+ case "preparing":
318
280
  case "connecting":
319
281
  case "waiting-first-response":
320
282
  case "running":
@@ -532,6 +494,17 @@ function asRecord(value: unknown): Record<string, unknown> | undefined {
532
494
  : undefined;
533
495
  }
534
496
 
497
+ function hasAutomaticContinuation(result: Record<string, unknown>): boolean {
498
+ const continuation = asRecord(result.continuation);
499
+ return (
500
+ ["paused", "approval-needed"].includes(String(result.state)) &&
501
+ asRecord(result.pause)?.code !== "operation-cancelled" &&
502
+ continuation?.owner === "parent" &&
503
+ continuation.automatic === true &&
504
+ (continuation.action === "amend" || continuation.command === "resume")
505
+ );
506
+ }
507
+
535
508
  function workflowStateOf(
536
509
  result: Record<string, unknown>,
537
510
  ): WorkflowActivityState {
@@ -544,6 +517,7 @@ function workflowStateOf(
544
517
  ) {
545
518
  return "operation-cancelled";
546
519
  }
520
+ if (hasAutomaticContinuation(result)) return "recovering";
547
521
  const state = result.state;
548
522
  switch (state) {
549
523
  case "not-started":
@@ -551,6 +525,8 @@ function workflowStateOf(
551
525
  case "ready":
552
526
  case "queued":
553
527
  return "queued";
528
+ case "preparing":
529
+ return "preparing";
554
530
  case "connecting":
555
531
  return "connecting";
556
532
  case "waiting-first-response":
@@ -669,6 +645,7 @@ function workflowWait(
669
645
  ) {
670
646
  return approval.gate;
671
647
  }
648
+ if (state === "preparing") return "worker-setup";
672
649
  if (state === "connecting") return "connection";
673
650
  if (state === "waiting-first-response") return "first-response";
674
651
  if (state === "retrying") return "bounded-policy";
@@ -755,7 +732,8 @@ export function projectWorkflowActivity(
755
732
  ...(workflowWait(state, payload)
756
733
  ? { wait: workflowWait(state, payload) }
757
734
  : {}),
758
- ...(nextWorkflowAction(state, legalCommands)
735
+ ...(!hasAutomaticContinuation(payload) &&
736
+ nextWorkflowAction(state, legalCommands)
759
737
  ? { nextAction: nextWorkflowAction(state, legalCommands) }
760
738
  : {}),
761
739
  };
@@ -854,6 +832,7 @@ function terminalState(
854
832
  } {
855
833
  return ![
856
834
  "queued",
835
+ "preparing",
857
836
  "connecting",
858
837
  "waiting-first-response",
859
838
  "running",
@@ -17,65 +17,16 @@ import {
17
17
  validateDiffResult,
18
18
  validateEvidenceResult,
19
19
  } from "./contracts.ts";
20
+ import {
21
+ completeEvidenceDraft,
22
+ evidenceDraftSchema,
23
+ } from "./evidence-draft.ts";
20
24
  import {
21
25
  type BeginCandidateInput,
22
26
  TASK_LEDGER_LIMITS,
23
27
  type TaskLedger,
24
28
  } from "./task-ledger.ts";
25
29
 
26
- const compactEvidenceSchema = Type.Object({
27
- id: Type.String(),
28
- role: Type.String(),
29
- kind: Type.Literal("evidence"),
30
- conclusions: Type.Array(Type.String()),
31
- citations: Type.Array(
32
- Type.Object({ path: Type.String(), lines: Type.String() }),
33
- ),
34
- constraints: Type.Array(Type.String()),
35
- dependencies: Type.Array(Type.String()),
36
- risks: Type.Array(Type.String()),
37
- blockingQuestions: Type.Array(Type.String()),
38
- hints: Type.Object({
39
- writeSet: Type.Array(Type.String()),
40
- verification: Type.String(),
41
- agentsImpact: Type.String(),
42
- }),
43
- });
44
-
45
- const designEvidenceSchema = Type.Object(
46
- {
47
- id: Type.String(),
48
- role: Type.Literal("design-explorer"),
49
- kind: Type.Literal("evidence"),
50
- packet_id: Type.String(),
51
- module_name: Type.String(),
52
- scope: Type.Array(Type.String()),
53
- files_read: Type.Array(Type.String()),
54
- evidence: Type.Array(
55
- Type.Object(
56
- {
57
- claim: Type.String(),
58
- path: Type.String(),
59
- line_start: Type.Integer({ minimum: 1 }),
60
- line_end: Type.Integer({ minimum: 1 }),
61
- },
62
- { additionalProperties: false },
63
- ),
64
- ),
65
- existing_structures: Type.Array(Type.String()),
66
- existing_conventions: Type.Array(Type.String()),
67
- constraints_discovered: Type.Array(Type.String()),
68
- open_questions: Type.Array(Type.String()),
69
- dependencies: Type.Array(Type.String()),
70
- write_set_hints: Type.Array(Type.String()),
71
- validation_hints: Type.Array(Type.String()),
72
- agents_impact_hints: Type.Array(Type.String()),
73
- risks: Type.Array(Type.String()),
74
- success_criteria_hints: Type.Array(Type.String()),
75
- },
76
- { additionalProperties: false },
77
- );
78
-
79
30
  const diffSchema = Type.Object({
80
31
  id: Type.String(),
81
32
  role: Type.String(),
@@ -225,6 +176,7 @@ export interface CandidateContextBoundary {
225
176
  writePaths: readonly string[];
226
177
  deletePaths?: readonly string[];
227
178
  taskPaths: readonly string[];
179
+ contextReadRoots?: readonly string[];
228
180
  redWritePaths: readonly string[];
229
181
  agents: {
230
182
  impact: "none" | "update-existing" | "create-index" | "remove-index";
@@ -354,6 +306,19 @@ function pathWithinAny(relative: string, approved: readonly string[]): boolean {
354
306
  return approved.some((path) => contextRefWithinBoundary(relative, path));
355
307
  }
356
308
 
309
+ export function permitsContextRead(
310
+ relative: string,
311
+ roots: readonly string[],
312
+ ): boolean {
313
+ // Dynamic discovery never grants hidden credentials or a directory tree.
314
+ return (
315
+ isValidRelativePath(relative) &&
316
+ relative.split("/").every((part) => !part.startsWith(".")) &&
317
+ !/\.(?:pem|key|p12|pfx)$/iu.test(relative) &&
318
+ pathWithinAny(relative, roots)
319
+ );
320
+ }
321
+
357
322
  export function classifyCandidateContextRequest(
358
323
  request: {
359
324
  kind: "context-request";
@@ -377,11 +342,15 @@ export function classifyCandidateContextRequest(
377
342
  const ordinaryRequested = requested.filter((ref) => !isAgentsPath(ref.path));
378
343
  const outsideOrdinary = ordinaryRequested.filter(
379
344
  (ref) =>
345
+ !(
346
+ ref.access === "read" &&
347
+ permitsContextRead(ref.path, boundary.contextReadRoots ?? [])
348
+ ) &&
380
349
  !pathWithinAny(
381
350
  ref.path,
382
351
  ref.access === "write"
383
352
  ? [...boundary.writePaths, ...(boundary.deletePaths ?? [])]
384
- : phasePaths,
353
+ : [...phasePaths, ...boundary.taskPaths],
385
354
  ),
386
355
  );
387
356
  const agentsRequested = requested.filter((ref) => isAgentsPath(ref.path));
@@ -428,7 +397,7 @@ export function classifyCandidateContextRequest(
428
397
  classification = { kind: "paused", code: "agents-context-needed" };
429
398
  } else {
430
399
  classification = {
431
- kind: request.code === "approved-context-needed" ? "retryable" : "paused",
400
+ kind: "retryable",
432
401
  code:
433
402
  request.code === "task-split-needed"
434
403
  ? "task-split-needed"
@@ -863,13 +832,11 @@ export function createSubmitTool(input: {
863
832
  name: "abel_submit_result",
864
833
  label: "Submit Abel Result",
865
834
  description:
866
- "Submit the one final structured Abel evidence or complete unified-diff result.",
835
+ "Submit one accepted final result. Evidence identity and omitted advisory fields are completed by this tool; keep citations, constraints, risks, and open questions explicit. After a rejection, correct once in this same session. Brief accompanying text is harmless.",
867
836
  executionMode: "sequential",
868
837
  parameters:
869
838
  input.output === "evidence"
870
- ? input.role === "design-explorer"
871
- ? designEvidenceSchema
872
- : compactEvidenceSchema
839
+ ? evidenceDraftSchema(input.requestId, input.role)
873
840
  : diffSchema,
874
841
  async execute(_toolCallId, params) {
875
842
  attempts++;
@@ -898,7 +865,11 @@ export function createSubmitTool(input: {
898
865
  phase: true,
899
866
  };
900
867
  failure = undefined;
901
- const value = params as unknown as Record<string, unknown>;
868
+ const value = (
869
+ input.output === "evidence"
870
+ ? completeEvidenceDraft(params, input.requestId, input.role)
871
+ : params
872
+ ) as Record<string, unknown>;
902
873
  if (value === null || typeof value !== "object") {
903
874
  identity.request = false;
904
875
  identity.role = false;
@@ -945,7 +916,15 @@ export function createSubmitTool(input: {
945
916
  stage: "structural-submit",
946
917
  });
947
918
  throw new Error(
948
- validation.reason ?? "submitted result identity does not match",
919
+ JSON.stringify({
920
+ code: "code" in failure ? failure.code : failure.kind,
921
+ reason:
922
+ validation.reason ?? "submitted result identity does not match",
923
+ identityMismatch: Object.entries(identity)
924
+ .filter(([, matches]) => !matches)
925
+ .map(([field]) => field),
926
+ remainingCorrections: Math.max(0, 2 - attempts),
927
+ }),
949
928
  );
950
929
  }
951
930
  submitted = structuredClone(value) as unknown as
@@ -2,14 +2,16 @@ import { createHash } from "node:crypto";
2
2
  import { chmodSync, lstatSync, mkdirSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { DatabaseSync } from "node:sqlite";
5
-
6
5
  import type { ArtifactStore } from "./artifact-store.ts";
6
+ import { compareCanonicalStrings } from "./canonical.ts";
7
7
  import {
8
8
  diffWritePaths,
9
9
  IMPLEMENTATION_PHASES,
10
10
  type ImplementationPhase,
11
11
  isValidRelativePath,
12
12
  } from "./contracts.ts";
13
+ import { configureSqlite, ensureSqliteSchema } from "./sqlite-schema.ts";
14
+ import { TASK_SCHEMA } from "./storage-schema.ts";
13
15
 
14
16
  export const TASK_LEDGER_LIMITS = {
15
17
  maxSegmentBytes: 128 * 1024,
@@ -413,7 +415,9 @@ function normalizeContextRefs(value: unknown): TaskContextReference[] {
413
415
  if (new Set(identities).size !== identities.length) {
414
416
  throw new Error("ledger-context-refs-invalid");
415
417
  }
416
- return refs.sort((left, right) => left.path.localeCompare(right.path));
418
+ return refs.sort((left, right) =>
419
+ compareCanonicalStrings(left.path, right.path),
420
+ );
417
421
  }
418
422
 
419
423
  function safeText(value: unknown, code: string, maximum = 256): string {
@@ -694,57 +698,8 @@ export class TaskLedger {
694
698
  throw new Error("task-ledger-database-invalid");
695
699
  }
696
700
  this.#database = new DatabaseSync(databasePath);
697
- this.#database.exec(
698
- "PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA foreign_keys = ON;",
699
- );
700
- this.#database.exec(`
701
- CREATE TABLE IF NOT EXISTS candidates (
702
- candidate_id TEXT PRIMARY KEY,
703
- identity_json TEXT NOT NULL,
704
- state TEXT NOT NULL,
705
- next_sequence INTEGER NOT NULL,
706
- total_bytes INTEGER NOT NULL,
707
- artifact_hash TEXT,
708
- candidate_hash TEXT,
709
- segment_count INTEGER,
710
- paths_json TEXT,
711
- pause_code TEXT
712
- ) STRICT;
713
- CREATE TABLE IF NOT EXISTS candidate_segments (
714
- candidate_id TEXT NOT NULL REFERENCES candidates(candidate_id) ON DELETE CASCADE,
715
- sequence INTEGER NOT NULL,
716
- artifact_hash TEXT NOT NULL,
717
- size_bytes INTEGER NOT NULL,
718
- segment_hash TEXT NOT NULL,
719
- PRIMARY KEY (candidate_id, sequence)
720
- ) STRICT;
721
- CREATE TABLE IF NOT EXISTS task_ledger (
722
- run_id TEXT NOT NULL,
723
- task_id TEXT NOT NULL,
724
- delivery_revision INTEGER NOT NULL,
725
- boundary_hash TEXT NOT NULL,
726
- objective TEXT NOT NULL,
727
- context_refs_json TEXT NOT NULL,
728
- initial_phase TEXT NOT NULL,
729
- PRIMARY KEY (run_id, task_id)
730
- ) STRICT;
731
- CREATE TABLE IF NOT EXISTS task_events (
732
- run_id TEXT NOT NULL,
733
- task_id TEXT NOT NULL,
734
- ordinal INTEGER NOT NULL,
735
- event_id TEXT NOT NULL,
736
- event_json TEXT NOT NULL,
737
- prior_hash TEXT NOT NULL,
738
- event_hash TEXT NOT NULL,
739
- PRIMARY KEY (run_id, task_id, ordinal),
740
- UNIQUE (run_id, task_id, event_id),
741
- FOREIGN KEY (run_id, task_id) REFERENCES task_ledger(run_id, task_id) ON DELETE CASCADE
742
- ) STRICT;
743
- CREATE TABLE IF NOT EXISTS durable_facts (
744
- fact_key TEXT PRIMARY KEY,
745
- fact_json TEXT NOT NULL
746
- ) STRICT;
747
- `);
701
+ configureSqlite(this.#database);
702
+ ensureSqliteSchema(this.#database, TASK_SCHEMA);
748
703
  chmodSync(databasePath, 0o600);
749
704
  }
750
705
 
@@ -1173,7 +1128,7 @@ export class TaskLedger {
1173
1128
  commitVerifiedEvent(input: VerifiedTaskEvent) {
1174
1129
  this.#assertOpen();
1175
1130
  const normalized = normalizeVerifiedEvent(input);
1176
- this.#task(normalized.runId, normalized.taskId);
1131
+ const task = this.#task(normalized.runId, normalized.taskId);
1177
1132
  const rows = this.#events(normalized.runId, normalized.taskId);
1178
1133
  const existing = rows.find((row) => row.event_id === normalized.eventId);
1179
1134
  const json = JSON.stringify(normalized);
@@ -1186,6 +1141,8 @@ export class TaskLedger {
1186
1141
  throw new Error("ledger-event-capacity-exceeded");
1187
1142
  }
1188
1143
  if (normalized.kind === "phase-verified") {
1144
+ if (task.initial_phase === "green" && normalized.phase === "red")
1145
+ throw new Error("ledger-phase-order-invalid");
1189
1146
  const completed = rows
1190
1147
  .map((row) => JSON.parse(row.event_json) as VerifiedTaskEvent)
1191
1148
  .filter(
@@ -1195,7 +1152,9 @@ export class TaskLedger {
1195
1152
  .map((event) => event.phase);
1196
1153
  const required =
1197
1154
  normalized.phase === "green"
1198
- ? "red"
1155
+ ? task.initial_phase === "green"
1156
+ ? undefined
1157
+ : "red"
1199
1158
  : normalized.phase === "refactor"
1200
1159
  ? "green"
1201
1160
  : undefined;
@@ -0,0 +1,32 @@
1
+ /** Request budgets describe observable model progress, never TCP/TLS events. */
2
+ export const REQUEST_BOUNDS = Object.freeze({
3
+ firstProgressMs: 90_000,
4
+ streamIdleMs: 180_000,
5
+ cancellationDrainMs: 250,
6
+ });
7
+ export type TransportTimeoutCode =
8
+ | "first-progress-timeout"
9
+ | "stream-idle-timeout"
10
+ | "attempt-timeout";
11
+ export class TransportTimeout extends Error {
12
+ readonly scope: "request" | "attempt";
13
+ constructor(readonly code: TransportTimeoutCode) {
14
+ super(code);
15
+ this.name = "TransportTimeout";
16
+ this.scope = code === "attempt-timeout" ? "attempt" : "request";
17
+ }
18
+ }
19
+ export function isTransportTimeoutCode(
20
+ code: string,
21
+ ): code is TransportTimeoutCode {
22
+ return [
23
+ "first-progress-timeout",
24
+ "stream-idle-timeout",
25
+ "attempt-timeout",
26
+ ].includes(code);
27
+ }
28
+ export function transportFailureError(code: string): Error {
29
+ return isTransportTimeoutCode(code)
30
+ ? new TransportTimeout(code)
31
+ : new Error("transport-failure");
32
+ }