@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.
- package/CHANGELOG.md +15 -0
- package/dist/build-info.json +6 -6
- package/dist/cli/runtime-process.js +1 -1
- package/dist/cli.js +2 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +49 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +1 -1
- package/dist/harness-runtime/lib/dd-codex.mjs +2 -2
- package/dist/harness-runtime/lib/dd-droid.mjs +30 -8
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +7 -1
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +95 -12
- package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +13 -1
- package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +11 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
- package/dist/harness-runtime/lib/native-children.d.mts +2 -0
- package/dist/harness-runtime/lib/native-children.mjs +20 -1
- package/dist/harness-runtime/lib/opencode-plugin.mjs +30 -9
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- package/dist/schemas/flow-run.schema.json +4 -0
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +18 -2
- package/dist/services/controller-execution.js +6 -6
- package/dist/services/external-work-launch.js +2 -4
- package/dist/services/frozen-engine.js +16 -8
- package/dist/services/managed-processes.js +31 -10
- package/dist/services/memory-init-native-capacity.js +65 -10
- package/dist/services/memory-init-preflight.js +21 -3
- package/dist/services/memory-init-project-bootstrap.js +3 -3
- package/dist/services/memory-init-start.js +6 -0
- package/dist/services/memory-init-worktrunk-capability.js +9 -3
- package/dist/services/merge-server.js +2 -4
- package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
- package/dist/services/recovery-snapshot-database.js +1 -1
- package/dist/services/run-controller-adapter.js +1 -1
- package/dist/services/run-controller-recovery.js +2 -4
- package/dist/services/run-controller.js +59 -26
- package/dist/services/run-recovery-guard.js +3 -3
- package/dist/services/run-recovery-runtime.js +3 -3
- package/dist/services/runs.js +102 -13
- package/dist/services/vnext-code-review.js +1 -1
- package/dist/services/vnext-code.js +3 -3
- package/dist/services/vnext-fanout.js +3 -2
- package/dist/services/vnext-plan-review.js +1 -1
- package/dist/services/vnext-plan.js +3 -3
- package/dist/services/vnext-protocolize.js +20 -6
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/package.json +1 -1
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
- 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
|
-
|
|
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,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
|
-
}
|