@deksden-com/dd-flow-cli 0.9.0-beta.138 → 0.9.0-beta.139

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 (53) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/build-info.json +6 -6
  3. package/dist/cli/runtime-process.js +1 -1
  4. package/dist/cli.js +2 -0
  5. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +49 -20
  6. package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
  7. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +1 -1
  8. package/dist/harness-runtime/lib/dd-codex.mjs +2 -2
  9. package/dist/harness-runtime/lib/dd-droid.mjs +30 -8
  10. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +7 -1
  11. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +95 -12
  12. package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
  13. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +13 -1
  14. package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
  15. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
  16. package/dist/harness-runtime/lib/delegation-instructions.mjs +11 -0
  17. package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
  18. package/dist/harness-runtime/lib/native-children.d.mts +2 -0
  19. package/dist/harness-runtime/lib/native-children.mjs +20 -1
  20. package/dist/harness-runtime/lib/opencode-plugin.mjs +30 -9
  21. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
  22. package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
  23. package/dist/schemas/flow-run.schema.json +4 -0
  24. package/dist/services/controller-child-capacity.js +13 -0
  25. package/dist/services/controller-child-observer.js +18 -2
  26. package/dist/services/controller-execution.js +6 -6
  27. package/dist/services/external-work-launch.js +2 -4
  28. package/dist/services/frozen-engine.js +16 -8
  29. package/dist/services/managed-processes.js +31 -10
  30. package/dist/services/memory-init-native-capacity.js +65 -10
  31. package/dist/services/memory-init-preflight.js +21 -3
  32. package/dist/services/memory-init-project-bootstrap.js +3 -3
  33. package/dist/services/memory-init-start.js +6 -0
  34. package/dist/services/memory-init-worktrunk-capability.js +9 -3
  35. package/dist/services/merge-server.js +2 -4
  36. package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
  37. package/dist/services/recovery-snapshot-database.js +1 -1
  38. package/dist/services/run-controller-adapter.js +1 -1
  39. package/dist/services/run-controller-recovery.js +2 -4
  40. package/dist/services/run-controller.js +59 -26
  41. package/dist/services/run-recovery-guard.js +3 -3
  42. package/dist/services/run-recovery-runtime.js +3 -3
  43. package/dist/services/runs.js +102 -13
  44. package/dist/services/vnext-code-review.js +1 -1
  45. package/dist/services/vnext-code.js +3 -3
  46. package/dist/services/vnext-fanout.js +3 -2
  47. package/dist/services/vnext-plan-review.js +1 -1
  48. package/dist/services/vnext-plan.js +3 -3
  49. package/dist/services/vnext-protocolize.js +20 -6
  50. package/dist/services/vnext-workspace-policy.js +7 -2
  51. package/package.json +1 -1
  52. package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
  53. package/dist/harness-runtime/lib/codex-capacity-policy.mjs +0 -102
@@ -10,7 +10,7 @@ import { projectFeatureWorktreePath, resolveProjectRoot } from "../storage/paths
10
10
  import { previewNextEntityId } from "./ids.js";
11
11
  import { registerProtocol } from "./protocols.js";
12
12
  import { requireProjectByRoot } from "./projects.js";
13
- import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRunStage, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion, withStageSettlement, gitFacts, rebindFlowRunWorkspace } from "./runs.js";
13
+ import { advanceFlowRun, appendFlowRunTimelineEvent, attachFlowRunStage, completeFlowRunStage, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion, withStageSettlement, gitFacts, rebindFlowRunWorkspace, publishRunProjections } from "./runs.js";
14
14
  import { resolveStageTransition, stageExecutionCwd } from "./execution-policy.js";
15
15
  import { validateSchema } from "./schema-validation.js";
16
16
  import { assertStageStartHookEvent, hookSessionIdentity } from "./hooks.js";
@@ -21,6 +21,8 @@ import { loadVnextWorkspacePolicy, requireVnextWorkspaceRoute, vnextRunBranch }
21
21
  import { readVnextSpecifyResult } from "./vnext-specify.js";
22
22
  import { flowCommand, stagePauseCommand, stagePauseCommandTemplate } from "./stage-pause.js";
23
23
  import { acceptStageReport, prepareStageReport, publishStageReport } from "./stage-report-renderer.js";
24
+ import { hybridDigest } from "./hybrid-contracts.js";
25
+ import { assertRunMutationAllowed } from "./run-recovery.js";
24
26
  import { currentStagePrompt, applyExternalStageContext, prepareExternalStageContext, renderRunOperationalContext } from "./stage-context.js";
25
27
  import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.js";
26
28
  import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
@@ -202,7 +204,7 @@ export async function finishVnextProtocolize(context, input) {
202
204
  throw new AppError("protocolize_intent_conflict", "Completed PROTOCOLIZE result differs from accepted input", 1, { run_id: run.id });
203
205
  const currentStage = acceptedIndex.current_stage ?? [...acceptedIndex.stage_runs].reverse().find(item => item.status === "running" || item.status === "done")?.stage;
204
206
  const activeReplay = currentStage === stage && acceptedIndex.status === "running";
205
- const resolvedPolicy = activeReplay ? requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" }) : accepted.mechanical?.workspace_route;
207
+ const resolvedPolicy = activeReplay ? requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" }) : accepted.mechanical?.workspace_route;
206
208
  if (activeReplay) {
207
209
  if (acceptedIndex.verdict !== "protocolized")
208
210
  advanceFlowRun(context, { settlementStage: "protocolize", projectRoot, runId: run.id, status: "running", verdict: "protocolized", nextAction: "start_plan" });
@@ -212,7 +214,7 @@ export async function finishVnextProtocolize(context, input) {
212
214
  return { ok: true, outcome: "protocolized", run_id: run.id, protocol_ids: accepted.semantic.acceptance, workspace_root: run.workspace_root, git_policy: resolvedPolicy, next_action: activeReplay ? "await_plan" : acceptedIndex.next_action, ...("command" in next && next.command ? { next_command: next.command } : {}), next,
213
215
  artifacts: { result: resultFile, report_json: file, report_markdown: path.join(root, "stage-report.md"), report_html: path.join(root, "stage-report.html"), protocols: accepted.semantic.acceptance.map(id => path.join(run.workspace_root, ".memory-bank/protocol", id, "summary.md")) } };
214
216
  }
215
- const resolvedPolicy = requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" });
217
+ const resolvedPolicy = requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: requireRunHome(run), workspaceRoot: run.workspace_root, stage: "protocolize" });
216
218
  const acceptedObligations = acceptedSpecifyObligations(context, projectRoot, run.id, path.join(requireRunHome(run), "01-specify", "specify.json"));
217
219
  let obligations;
218
220
  try {
@@ -699,6 +701,11 @@ function renderPrompt(input) {
699
701
  return ["<work_context>", `- RUN: ${input.runId}`, `- Work: ${input.workId}`, `- Accepted SPECIFY JSON: ${input.specifyPath}`, `- Handoff: ${input.handoff.stage_handoff.effective}`, "</work_context>", "", "<accepted_obligations>", obligationList, "</accepted_obligations>", "", "<frozen_git_policy>", `- route: ${input.policy.route}`, `- integration branch: ${input.policy.integration_branch ?? "not applicable"}`, `- base ref: ${input.policy.base_ref ?? "not applicable"}`, `- feature branch: ${input.policy.feature_branch ?? "not applicable"}`, `- service worktree: ${input.policy.worktree_path ?? "not applicable"}`, `- bootstrap: ${input.policy.bootstrap_status}`, `- decision: ${input.policy.reason}`, "The CLI already created and bootstrapped this workspace at PROTOCOLIZE start. Do not create branches, worktrees, protocol files or Git commits yourself. This transition Work may run from the stable session; edit only the supplied RUN result file.", "</frozen_git_policy>", "", "<catalog>", input.catalog.length ? input.catalog.map((x) => `- ${x}`).join("\n") : "- inactive", "</catalog>", "", "<stage_instructions>", input.template.trim(), "</stage_instructions>", "", "<output_contract>", `Edit only ${input.resultPath}. Keep this exact JSON shape; CLI allocates ids and writes PRT/PSET/feature documents in the already provisioned workspace:\n\n\`\`\`json\n${JSON.stringify(resultTemplate(input.obligations), null, 2)}\n\`\`\``, "obligation_id values and their one-per-obligation entries are prefilled by the CLI. Preserve those IDs exactly; change only member_keys when semantic ownership requires it. The CLI rejects unknown, duplicate or missing obligations, and every member must own at least one AC-*.", "If PROTOCOLIZE itself exposes a material user decision with no reasonable default, do not finish and do not return to SPECIFY. Write the question packet to the named text file, then run the separate standalone lifecycle command below. Do not combine file creation and dd-flow in one shell command:", "```sh", input.pauseCommandTemplate, "```", "Ask the returned user_message and stop. Resume this same PROTOCOLIZE Work using the exact command returned by pause.", `When the answer is incorporated and the result is protocolized, finish:\n\n${input.finishCommand}`, stageLifecycleInstruction, "</output_contract>", ""].join("\n");
700
702
  }
701
703
  function prepareWorkspaceForProtocolize(context, projectRoot, projectId, run, stageRoot) {
704
+ // A committed rebind may have survived a projection crash. Repair only from
705
+ // its SQL authority before considering physical creation again.
706
+ const stored = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, run.id]);
707
+ if (stored && JSON.parse(stored.index_json).workspace_route)
708
+ publishRunProjections(context, projectId, run.id);
702
709
  const receiptPath = path.join(stageRoot, "workspace-route.json");
703
710
  if (fs.existsSync(receiptPath))
704
711
  return { kind: "retained", policy: readWorkspaceRouteReceipt(stageRoot).policy };
@@ -727,7 +734,9 @@ function prepareWorkspaceForProtocolize(context, projectRoot, projectId, run, st
727
734
  throw error;
728
735
  }
729
736
  prepareManagedWorktreeBootstrapSources(projectRoot);
730
- return { kind: "feature", receiptPath, config, stableFacts, sessionMode, branch, create };
737
+ return { kind: "feature", receiptPath, config, stableFacts, sessionMode, branch, create,
738
+ expectedRevision: stored ? JSON.parse(stored.index_json).runtime_revision ?? 0 : 0,
739
+ expectedGeneration: assertRunMutationAllowed(context, projectId, run.id) };
731
740
  }
732
741
  async function provisionWorkspaceForProtocolize(context, projectRoot, run, prepared) {
733
742
  if (prepared.kind === "retained")
@@ -740,9 +749,14 @@ async function provisionWorkspaceForProtocolize(context, projectRoot, run, prepa
740
749
  const { config, stableFacts, sessionMode, branch, create } = prepared;
741
750
  const created = await createManagedWorktree(context, create, create);
742
751
  const bootstrap = await bootstrapManagedWorktree(context, { projectRoot, worktreePath: created.worktreePath });
743
- rebindFlowRunWorkspace(context, { projectRoot, runId: run.id, workspaceRoot: created.worktreePath, reason: "PROTOCOLIZE start provisioned the configured feature-worktree route" });
744
752
  const policy = { route: "feature_worktree", integration_branch: config.workspace.integration_branch, base_ref: stableFacts.head, feature_branch: branch, worktree_path: created.worktreePath, reason: "Project workspace configuration requires a managed feature worktree before durable protocol documents exist.", provision_stage: "protocolize_start", stage_session_mode: sessionMode, provisioned: true, bootstrap_status: bootstrap.status };
745
- writeJson(receiptPath, { schema_id: "dd-flow/vnext-workspace-route@1", policy, created, bootstrap });
753
+ const receipt = { schema_id: "dd-flow/vnext-workspace-route@1", policy,
754
+ created: { worktreePath: created.worktreePath, worktrunk: created.worktrunk, command: [], result: { exit_code: 0, stdout: "", stderr: "" }, response: { workspace_path: created.worktreePath } },
755
+ bootstrap: { status: bootstrap.status, commands: [] },
756
+ proof: { creation_sha256: hybridDigest(created), bootstrap_sha256: hybridDigest(bootstrap) } };
757
+ rebindFlowRunWorkspace(context, { projectRoot, runId: run.id, workspaceRoot: created.worktreePath, routeReceipt: receipt,
758
+ expectedRevision: prepared.expectedRevision, expectedGeneration: prepared.expectedGeneration,
759
+ reason: "PROTOCOLIZE start provisioned the configured feature-worktree route" });
746
760
  return policy;
747
761
  }
748
762
  function readWorkspaceRouteReceipt(root) {
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { AppError } from "../shared/errors.js";
5
- import { gitFacts } from "./runs.js";
5
+ import { gitFacts, publishRunProjections } from "./runs.js";
6
6
  const configRelativePath = path.join(".memory-bank", "dd-flow", "project-workspace.json");
7
7
  /** The runtime reads explicit configuration; it never attempts to infer route from policy prose. */
8
8
  export function loadVnextWorkspacePolicy(projectRoot) {
@@ -53,8 +53,13 @@ export function vnextRunBranch(policy, run) {
53
53
  * The route receipt is the single source of truth after PROTOCOLIZE provisions
54
54
  * the workspace. Every later mutating vNext stage calls this before it opens.
55
55
  */
56
- export function requireVnextWorkspaceRoute(input) {
56
+ export function requireVnextWorkspaceRoute(context, input) {
57
57
  const file = path.join(input.runHome, "02-protocolize", "workspace-route.json");
58
+ const stored = context.db.get("SELECT project_id,index_json FROM runs WHERE project_root = ? AND id = ?", [input.projectRoot, input.runId]);
59
+ // Productive lifecycle readback repairs only SQL-committed projections. The
60
+ // status command never calls this path and cannot mutate historical files.
61
+ if (stored && JSON.parse(stored.index_json).workspace_route)
62
+ publishRunProjections(context, stored.project_id, input.runId);
58
63
  let receipt;
59
64
  try {
60
65
  receipt = JSON.parse(fs.readFileSync(file, "utf8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.138",
3
+ "version": "0.9.0-beta.139",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.23.0",
@@ -1,10 +0,0 @@
1
- export const CAPACITY_POLICY: "codex-overload-burst@1";
2
- export const OVERLOAD_WINDOW_MS: number;
3
- export type CapacityRefusal = { policy: string; ordinal: number; session_id: string; turn_id: string; first_observed_at: string; native_terminal_at?: string; native_terminal_basis?: string };
4
- export function parseCapacityOperation(id: string): { root: string; ordinal: number };
5
- export function normalizeCodexFailure(error: unknown, sessionId?: string, turnId?: string): null | { code: string; session_id: string; turn_id: string; native_error: Record<string, unknown>; observed_at: unknown; terminal_at: unknown; native_turn_items: unknown; primary_error: { details?: Record<string, unknown> } };
6
- export function terminalCodexOverload(error: unknown, sessionId?: string, turnId?: string): boolean;
7
- export function nativeItemsSettled(items: unknown): boolean;
8
- export function capacityBackoff(error: unknown, ordinal: number, now?: number): number;
9
- export function refusalObservation(error: unknown, ordinal: number, firstObservedAt: string): CapacityRefusal;
10
- export function overloadBurst(refusals: CapacityRefusal[]): null | { policy: string; basis: string; elapsed_ms: number; refusals: CapacityRefusal[] };
@@ -1,102 +0,0 @@
1
- /** Pure policy shared by the controller and pinned-engine EVAL consumers. */
2
- export const CAPACITY_POLICY = "codex-overload-burst@1";
3
- export const OVERLOAD_WINDOW_MS = 120_000;
4
-
5
- function policyError(code, message, details = {}) { return Object.assign(new Error(message), { code, details }); }
6
-
7
- export function parseCapacityOperation(id) {
8
- if (typeof id !== "string" || !id) throw policyError("capacity_continuation_conflict", "Missing capacity operation identity");
9
- const match = /^(.*):capacity:(\d+):session\.prompt$/.exec(id);
10
- if (!match) return { root: id, ordinal: 0 };
11
- const ordinal = Number(match[2]);
12
- if (!Number.isSafeInteger(ordinal) || ordinal < 1 || String(ordinal) !== match[2]) throw policyError("capacity_continuation_conflict", "Capacity ordinal is not a canonical positive safe integer", { operation_id: id });
13
- return { root: match[1], ordinal };
14
- }
15
-
16
- export function normalizeCodexFailure(error, expectedSessionId, expectedTurnId) {
17
- let current = error;
18
- for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
19
- // The same continuation authority applies to a proven v2 execution
20
- // refusal. Never classify transport loss or an active native retry here.
21
- if (current.code === "provider_capacity_transient") {
22
- const details = current.details ?? {};
23
- if (details.native_source !== "opencode.v2.session.execution" || details.terminal_status !== "failed"
24
- || typeof details.provider_session_id !== "string" || !details.provider_session_id
25
- || typeof details.turn_id !== "string" || !details.turn_id || details.native_turn_id !== details.turn_id
26
- || expectedSessionId !== undefined && details.provider_session_id !== expectedSessionId
27
- || expectedTurnId !== undefined && details.turn_id !== expectedTurnId) return null;
28
- return { code: "serverOverloaded", session_id: details.provider_session_id, turn_id: details.turn_id,
29
- native_error: { type: details.native_error_type, status: details.http_status }, observed_at: details.observed_at,
30
- terminal_at: null, terminal_basis: null, native_turn_items: details.native_turn_items ?? null, primary_error: current };
31
- }
32
- if (current.code === "turn_interrupted") {
33
- const details = current.details ?? {}, native = details.provider_error;
34
- const code = typeof native?.codexErrorInfo === "string" ? native.codexErrorInfo : native?.codexErrorInfo?.code;
35
- if (details.terminal_status !== "failed" || typeof details.provider_session_id !== "string" || !details.provider_session_id
36
- || expectedSessionId !== undefined && details.provider_session_id !== expectedSessionId
37
- || typeof details.turn_id !== "string" || !details.turn_id || details.native_turn_id !== details.turn_id
38
- || expectedTurnId !== undefined && details.turn_id !== expectedTurnId || typeof code !== "string") return null;
39
- return { code, session_id: details.provider_session_id, turn_id: details.turn_id, native_error: native,
40
- observed_at: details.observed_at ?? null, terminal_at: details.terminal_at ?? null, terminal_basis: details.terminal_basis ?? null,
41
- native_turn_items: details.native_turn_items ?? null, primary_error: current };
42
- }
43
- // No arbitrary recursive cause search: an ownership/hook/lifecycle error wins.
44
- if (current.code !== "harness_adapter_failed") return null;
45
- current = current.details?.cause ?? current.cause;
46
- }
47
- return null;
48
- }
49
-
50
- export function terminalCodexOverload(error, expectedSessionId, expectedTurnId) {
51
- return normalizeCodexFailure(error, expectedSessionId, expectedTurnId)?.code === "serverOverloaded";
52
- }
53
-
54
- export function nativeItemsSettled(items) {
55
- return items?.observed === true && typeof items.possible_effects === "boolean" && items.pending !== true
56
- && (items.possible_effects === false || items.pending === false);
57
- }
58
-
59
- export function capacityBackoff(error, ordinal, now = Date.now()) {
60
- if (!Number.isSafeInteger(ordinal) || ordinal < 1 || !Number.isFinite(now)) throw policyError("capacity_continuation_conflict", "Invalid capacity timing identity");
61
- const native = normalizeCodexFailure(error)?.native_error ?? error?.details?.provider_error ?? error;
62
- const header = Object.entries(native?.headers ?? {}).find(([key]) => key.toLowerCase() === "retry-after")?.[1];
63
- const value = header ?? native?.retryAfter ?? native?.retry_after;
64
- let retry = 0;
65
- if (value !== undefined && value !== null) {
66
- retry = typeof value === "number" || typeof value === "string" && /^\d+(\.\d+)?$/.test(value.trim()) ? Number(value) * 1000 : typeof value === "string" ? Math.max(0, Date.parse(value) - now) : NaN;
67
- if (!Number.isFinite(retry) || retry < 0 || !Number.isFinite(now + retry) || now + retry > 8.64e15)
68
- throw policyError("capacity_retry_metadata_invalid", "Retry-After cannot be represented safely", { retry_after: value });
69
- }
70
- return Math.max(ordinal === 1 ? 5_000 : 15_000, retry);
71
- }
72
-
73
- export function refusalObservation(error, ordinal, firstObservedAt) {
74
- const failure = normalizeCodexFailure(error);
75
- const observed = Date.parse(firstObservedAt);
76
- if (!failure || !Number.isSafeInteger(ordinal) || ordinal < 0 || !Number.isFinite(observed))
77
- throw policyError("capacity_continuation_conflict", "Refusal lacks an immutable native identity/time");
78
- if (failure.observed_at !== null && (typeof failure.observed_at !== "string" || !Number.isFinite(Date.parse(failure.observed_at)) || Date.parse(failure.observed_at) !== observed))
79
- throw policyError("capacity_continuation_conflict", "Native refusal observation time is invalid or changed");
80
- const native = failure.terminal_at && Date.parse(failure.terminal_at);
81
- return { policy: CAPACITY_POLICY, ordinal, session_id: failure.session_id, turn_id: failure.turn_id,
82
- first_observed_at: firstObservedAt, ...(Number.isFinite(native) && typeof failure.terminal_basis === "string" && failure.terminal_basis ? { native_terminal_at: failure.terminal_at, native_terminal_basis: failure.terminal_basis } : {}) };
83
- }
84
-
85
- export function overloadBurst(refusals) {
86
- for (const [index, refusal] of refusals.entries()) {
87
- const previous = refusals[index - 1];
88
- if (typeof refusal.first_observed_at !== "string" || !Number.isFinite(Date.parse(refusal.first_observed_at))
89
- || previous && Date.parse(refusal.first_observed_at) < Date.parse(previous.first_observed_at))
90
- throw policyError("capacity_continuation_conflict", "Refusal observation clock reversed or is invalid");
91
- }
92
- const continuations = refusals.filter(item => item.ordinal > 0);
93
- const pair = continuations.slice(-2);
94
- if (pair.length !== 2) return null;
95
- const [previous, current] = pair;
96
- if (previous.session_id !== current.session_id || previous.turn_id === current.turn_id || current.ordinal !== previous.ordinal + 1)
97
- throw policyError("capacity_continuation_conflict", "Refusal chain identity is inconsistent");
98
- const basis = previous.native_terminal_at && current.native_terminal_at && previous.native_terminal_basis && previous.native_terminal_basis === current.native_terminal_basis ? "native_terminal_at" : "first_observed_at";
99
- const elapsed = Date.parse(current[basis]) - Date.parse(previous[basis]);
100
- if (!Number.isFinite(elapsed) || elapsed < 0) throw policyError("capacity_continuation_conflict", "Refusal clock reversed or is invalid");
101
- return elapsed <= OVERLOAD_WINDOW_MS ? { policy: CAPACITY_POLICY, basis, elapsed_ms: elapsed, refusals: pair } : null;
102
- }