@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
|
@@ -38,12 +38,14 @@ import { assertNoCorrelatedRejection } from "./continuation-outcome.js";
|
|
|
38
38
|
import { spawnController, recordControllerLauncher, claimControllerProcess, markControllerDispatchReady, assertControllerLeaseAllowed } from "./run-controller-process.js";
|
|
39
39
|
import { controllerAdapter, controllerEnvironment, sessionArguments } from "./run-controller-adapter.js";
|
|
40
40
|
import { retainedChildObserver } from "./controller-child-observer.js";
|
|
41
|
-
import { capacityDelay,
|
|
41
|
+
import { capacityDelay, continueProviderCapacity, eligibleProviderChildOverload, providerChildFailure } from "./provider-capacity.js";
|
|
42
42
|
import { resumeControllerSession, failControllerRecovery } from "./run-controller-recovery.js";
|
|
43
43
|
import { captureControllerBoundary } from "./run-controller-capture.js";
|
|
44
44
|
import { requestRunControl } from "./run-control-worker.js";
|
|
45
45
|
import { storageSessionId } from "./session-identity.js";
|
|
46
|
-
import { CAPACITY_POLICY, nativeItemsSettled,
|
|
46
|
+
import { CAPACITY_POLICY, nativeItemsSettled, overloadExhausted, refusalObservation, terminalProviderOverload } from "../harness-runtime/lib/provider-overload-policy.mjs";
|
|
47
|
+
import { renderNativeChildFollowupInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
48
|
+
import { closeSuccessfulChildCapacity } from "./controller-child-capacity.js";
|
|
47
49
|
import { isObservationLoss } from "../harness-runtime/lib/operation-errors.mjs";
|
|
48
50
|
export { resumeRunController } from "./run-controller-recovery.js";
|
|
49
51
|
const preparedLaunchInputs = new WeakMap();
|
|
@@ -247,7 +249,10 @@ export async function serveRunController(context, input) {
|
|
|
247
249
|
lease.assertNoRenewal();
|
|
248
250
|
if (state.merge_claim)
|
|
249
251
|
mergeLease.assertNoRenewal();
|
|
250
|
-
|
|
252
|
+
// This isolated read already bounds SQLite contention. A wall deadline
|
|
253
|
+
// before opening the reader mistakes scheduler delay for heartbeat loss;
|
|
254
|
+
// the observed row's actual expiry/token still fences productive dispatch.
|
|
255
|
+
assertManagedProcessLease(context, { id: row.process_id, leaseToken: row.process_lease_token, pid: process.pid });
|
|
251
256
|
const current = requireController(context, row.controller_id);
|
|
252
257
|
if (current.owner_pid !== process.pid || current.owner_token !== row.owner_token || !["running", "capturing", "waiting_for_user", "waiting_for_context", "waiting_for_children", "waiting_for_capacity", "waiting_for_merge"].includes(current.status))
|
|
253
258
|
throw new AppError("controller_owner_mismatch", "Controller no longer owns productive dispatch", 1);
|
|
@@ -681,14 +686,12 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
681
686
|
}
|
|
682
687
|
}
|
|
683
688
|
catch (error) {
|
|
684
|
-
if (!["codex-desktop", "opencode-server"].includes(runtimeHarness(session.profile)))
|
|
685
|
-
throw error;
|
|
686
689
|
const resumeCommand = hitl ? text.split("\n\n")[1] : undefined;
|
|
687
690
|
if (hitl && !resumeCommand?.includes(" stage resume "))
|
|
688
691
|
throw error;
|
|
689
692
|
const inspect = (args, id) => runHarnessAdapter({ executable: manifest.adapters[runtimeHarness(session.profile)].executable,
|
|
690
693
|
args, env: controllerEnvironment(context, manifest, session), runtimeOwner: session.owner, operationId: id, timeoutMs: 30_000, signal: cancellation.signal });
|
|
691
|
-
const resumed = await
|
|
694
|
+
const resumed = await continueProviderCapacity({ error, sessionId: session.id, operationId: firstOperationId, signal: cancellation.signal, allowSettledDescendants: true, atomicIntent: true,
|
|
692
695
|
retainedIntents: () => context.db.all("SELECT receipt_json FROM run_controller_operations WHERE controller_id = ? AND generation = ?", [row.controller_id, row.generation])
|
|
693
696
|
.flatMap(item => { const retained = JSON.parse(item.receipt_json || "null"); return retained?.capacity_intent?.root_operation_id === firstOperationId ? [retained.capacity_intent] : []; }).sort((a, b) => a.ordinal - b.ordinal),
|
|
694
697
|
retainedFailures: () => context.db.all("SELECT receipt_json FROM run_controller_operations WHERE controller_id = ? AND generation = ?", [row.controller_id, row.generation])
|
|
@@ -874,7 +877,7 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
874
877
|
loadExternalStageContext({ file: stageContext.file, expectedSha256: stageContext.sha256, stage: next.stage });
|
|
875
878
|
const continuation = JSON.stringify(["start_stage", next.stage, next.attempt ?? null]);
|
|
876
879
|
retainControllerContinuation(state, continuation, { code: "stage_entry_nonprogressing", message: "The coordinator returned without starting the assigned Stage", details: { stage: next.stage, attempt: next.attempt ?? null, last_receipt: state.last_receipt ?? null } });
|
|
877
|
-
const transition = routing.transition(next.stage, state.stage ?? undefined);
|
|
880
|
+
const transition = routing.transition(next.stage, state.stage ?? undefined, run);
|
|
878
881
|
if (routing.kind === "sdlc" && next.stage === "merge" && routing.mergeMode === "server" && !state.merge_claim) {
|
|
879
882
|
const request = context.db.get("SELECT merge_request_id, status FROM merge_requests WHERE project_id = ? AND run_id = ? ORDER BY created_at DESC, merge_request_id DESC LIMIT 1", [row.project_id, row.run_id]);
|
|
880
883
|
if (!request || request.status !== "queued")
|
|
@@ -890,7 +893,22 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
890
893
|
appendEvent(context, row.controller_id, "merge_claimed", { request_id: claim.request.merge_request_id });
|
|
891
894
|
await renewLease();
|
|
892
895
|
}
|
|
893
|
-
const stageCwd = routing.cwd(next.stage, state.merge_claim?.target_workspace);
|
|
896
|
+
const stageCwd = routing.cwd(next.stage, state.merge_claim?.target_workspace, run);
|
|
897
|
+
const routeBinding = (current) => {
|
|
898
|
+
const index = JSON.parse(current.index_json);
|
|
899
|
+
return JSON.stringify([current.project_root, current.workspace_root, index.workspace_route ?? null, state.merge_claim ?? null]);
|
|
900
|
+
};
|
|
901
|
+
const preparedRoute = routeBinding(run);
|
|
902
|
+
const assertRoute = async () => {
|
|
903
|
+
assertOwner();
|
|
904
|
+
if (routeBinding(requireRun(context, row.project_id, row.run_id)) !== preparedRoute) {
|
|
905
|
+
const current = state.current_session === null ? null : state.sessions[state.current_session];
|
|
906
|
+
if (current && !current.stopped)
|
|
907
|
+
await stopSession(current);
|
|
908
|
+
throw new AppError("workspace_route_changed", "RUN workspace changed during native Session preparation", 1, { effect: "no_effect", stage: next.stage });
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
await assertRoute();
|
|
894
912
|
let session = state.current_session === null ? null : state.sessions[state.current_session];
|
|
895
913
|
if (session && routing.kind === "sdlc" && next.stage === "merge" && routing.mergeMode === "same_session" && path.resolve(session.cwd) !== stageCwd) {
|
|
896
914
|
throw new AppError("merge_session_workspace_conflict", "Same-session MERGE cannot change the native Session working directory", 1, { session_cwd: session.cwd, target_cwd: stageCwd });
|
|
@@ -942,6 +960,7 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
942
960
|
}
|
|
943
961
|
const responseFile = path.join(row.state_dir, `${next.stage}-${next.attempt ?? 1}.stage-start-response.json`);
|
|
944
962
|
const command = managedLifecycleCommand(executionContext, `${flowCommand(executionContext)} stage start ${run.id} --stage ${next.stage} --project-root ${JSON.stringify(run.project_root)} --context-file ${JSON.stringify(stageContext.file)} --context-sha256 ${stageContext.sha256} --require-session-binding --response-file ${JSON.stringify(responseFile)} --json --progress-jsonl`);
|
|
963
|
+
await assertRoute();
|
|
945
964
|
appendEvent(context, row.controller_id, "stage_entered", { stage: next.stage, session_id: session.id, context_sha256: stageContext.sha256 });
|
|
946
965
|
await prompt(session, controllerStageEntryPrompt(next.stage, command));
|
|
947
966
|
continue;
|
|
@@ -949,11 +968,19 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
949
968
|
if (state.current_session === null)
|
|
950
969
|
throw new AppError("controller_attach_required", "An unfinished Stage has no managed coordinator", 1);
|
|
951
970
|
const session = state.sessions[state.current_session];
|
|
971
|
+
const childHarness = runtimeHarness(session.profile);
|
|
952
972
|
const baseline = new Set(state.native_baseline ?? []);
|
|
953
973
|
let children = controllerNativeChildren(state.last_receipt ?? {}, session.id).filter(child => !baseline.has(child.session_id));
|
|
974
|
+
childObserver(session)?.observeSettledChildren(children);
|
|
954
975
|
const retainedFailures = childObserver(session)?.failed() ?? [];
|
|
955
976
|
children = controllerNativeChildren({ descendants: [...children, ...retainedFailures] }, session.id);
|
|
956
|
-
|
|
977
|
+
const closedChildEpisodes = closeSuccessfulChildCapacity(state, children, session.id);
|
|
978
|
+
if (closedChildEpisodes.length)
|
|
979
|
+
await prepareMutation(() => {
|
|
980
|
+
saveState(context, row, state);
|
|
981
|
+
appendEvent(context, row.controller_id, "capacity_child_episodes_completed", { child_session_ids: closedChildEpisodes });
|
|
982
|
+
});
|
|
983
|
+
if (children.some(child => child.status === "failed" && renderNativeChildFollowupInstruction(childHarness, child.session_id))) {
|
|
957
984
|
const inspected = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
|
|
958
985
|
state.last_receipt = inspected;
|
|
959
986
|
saveState(context, row, state);
|
|
@@ -961,32 +988,31 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
961
988
|
const failed = children.filter(child => child.status === "failed");
|
|
962
989
|
// Freeze each distinct child refusal on its first exact observation,
|
|
963
990
|
// even while a healthy sibling still owns an active native Turn.
|
|
964
|
-
for (const failedChild of failed.filter(item =>
|
|
965
|
-
const linked = context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id:
|
|
991
|
+
for (const failedChild of failed.filter(item => eligibleProviderChildOverload(item, session.id))) {
|
|
992
|
+
const linked = context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id: childHarness, session_id: failedChild.session_id })]);
|
|
966
993
|
if (linked.length > 1 || linked.some(link => link.status !== "running"))
|
|
967
994
|
continue;
|
|
968
995
|
const retained = state.child_capacity?.[failedChild.session_id];
|
|
969
996
|
const refusals = [...(retained?.refusals ?? [])];
|
|
970
997
|
if (refusals.some(item => item.turn_id === failedChild.terminal_turn_id))
|
|
971
998
|
continue;
|
|
972
|
-
const nativeFailure =
|
|
999
|
+
const nativeFailure = providerChildFailure(failedChild);
|
|
973
1000
|
refusals.push(refusalObservation(nativeFailure, retained?.attempts ?? 0, failedChild.terminal_observed_at ?? context.now()));
|
|
974
1001
|
state.child_capacity ??= {};
|
|
975
1002
|
state.child_capacity[failedChild.session_id] = { ...retained, turn_id: retained?.turn_id ?? failedChild.terminal_turn_id, attempts: retained?.attempts ?? 0, policy: CAPACITY_POLICY, refusals };
|
|
976
1003
|
await prepareMutation(() => { saveState(context, row, state); appendEvent(context, row.controller_id, "capacity_child_refusal", { child_session_id: failedChild.session_id, refusal: refusals.at(-1) }); });
|
|
977
|
-
const
|
|
978
|
-
if (
|
|
979
|
-
throw new AppError("
|
|
1004
|
+
const exhausted = overloadExhausted(refusals);
|
|
1005
|
+
if (exhausted)
|
|
1006
|
+
throw new AppError("provider_unavailable", "Native child exhausted its overload continuation attempts", 1, { ...exhausted, reason: "overload_retries_exhausted", child_session_id: failedChild.session_id, primary_error: nativeFailure });
|
|
980
1007
|
}
|
|
981
1008
|
// Continue one existing child at a time; another failed child does not
|
|
982
1009
|
// create a second fanout wave or share this child's refusal counter.
|
|
983
|
-
const child = children.every(item => item.status === "failed" || item.status === "completed") ? failed.find(item =>
|
|
1010
|
+
const child = children.every(item => item.status === "failed" || item.status === "completed") ? failed.find(item => renderNativeChildFollowupInstruction(childHarness, item.session_id) && eligibleProviderChildOverload(item, session.id)) : undefined;
|
|
984
1011
|
const turnId = child?.terminal_turn_id;
|
|
985
|
-
const native = child && turnId ?
|
|
986
|
-
terminal_status: "failed", provider_error: child.provider_error, observed_at: child.terminal_observed_at } } : null;
|
|
1012
|
+
const native = child && turnId ? providerChildFailure(child) : null;
|
|
987
1013
|
const prior = child && state.child_capacity?.[child.session_id];
|
|
988
|
-
const links = child ? context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id:
|
|
989
|
-
if (child && turnId && native &&
|
|
1014
|
+
const links = child ? context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id: childHarness, session_id: child.session_id })]) : [];
|
|
1015
|
+
if (child && turnId && native && terminalProviderOverload(native, child.session_id, turnId)
|
|
990
1016
|
&& nativeItemsSettled(child.native_turn_items)
|
|
991
1017
|
&& inspected.settled === true && child.parent_session_id === session.id && links.length <= 1
|
|
992
1018
|
&& (!links.length || links[0].status === "running")) {
|
|
@@ -1004,10 +1030,17 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
1004
1030
|
state.child_capacity[child.session_id] = { ...prior, turn_id: prior?.turn_id ?? turnId, attempts: prior?.attempts ?? 0, policy: CAPACITY_POLICY, refusals };
|
|
1005
1031
|
saveState(context, row, state);
|
|
1006
1032
|
}
|
|
1007
|
-
const
|
|
1008
|
-
if (
|
|
1009
|
-
throw new AppError("
|
|
1010
|
-
|
|
1033
|
+
const exhausted = overloadExhausted(refusals);
|
|
1034
|
+
if (exhausted)
|
|
1035
|
+
throw new AppError("provider_unavailable", "Native child exhausted its overload continuation attempts", 1, { ...exhausted, reason: "overload_retries_exhausted", child_session_id: child.session_id, primary_error: native });
|
|
1036
|
+
let wait;
|
|
1037
|
+
try {
|
|
1038
|
+
wait = prepared ? Math.max(0, Date.parse(prepared.not_before) - Date.now()) : capacityDelay(native, attempt);
|
|
1039
|
+
}
|
|
1040
|
+
catch (error) {
|
|
1041
|
+
const failure = error;
|
|
1042
|
+
throw new AppError(failure.code ?? "capacity_retry_metadata_invalid", failure.message ?? "Invalid child retry delay", 1, { ...failure.details, child_session_id: child.session_id, root_operation_id: prior?.root_operation_id ?? null, attempts: refusals.length, primary_error: native });
|
|
1043
|
+
}
|
|
1011
1044
|
if (!Number.isFinite(wait) || Date.now() + wait > 8.64e15)
|
|
1012
1045
|
throw new AppError("capacity_retry_metadata_invalid", "Native child retry time is invalid", 1, { child_session_id: child.session_id, delay_ms: wait });
|
|
1013
1046
|
const rootOperationId = prior?.root_operation_id ?? `${row.controller_id}:child:${digest([child.session_id, turnId]).slice(0, 16)}`;
|
|
@@ -1030,7 +1063,7 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
1030
1063
|
throw new AppError("capacity_child_followup_blocked", "RUN changed during native child capacity wait", 1, { child_session_id: child.session_id });
|
|
1031
1064
|
const fresh = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
|
|
1032
1065
|
const still = controllerNativeChildren(fresh, session.id).find(item => item.session_id === child.session_id);
|
|
1033
|
-
const currentLinks = context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id:
|
|
1066
|
+
const currentLinks = context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id: childHarness, session_id: child.session_id })]);
|
|
1034
1067
|
if (currentLinks.length === 1 && ["completed", "cancelled", "paused"].includes(currentLinks[0].status)) {
|
|
1035
1068
|
state.last_receipt = fresh;
|
|
1036
1069
|
saveState(context, row, state);
|
|
@@ -1042,7 +1075,7 @@ async function executeController(context, row, manifest, state, assertOwnership,
|
|
|
1042
1075
|
throw new AppError("capacity_child_followup_blocked", "Native child or Work changed during capacity wait", 1, { child_session_id: child.session_id, turn_id: turnId });
|
|
1043
1076
|
await renewLease();
|
|
1044
1077
|
assertOwner();
|
|
1045
|
-
const text =
|
|
1078
|
+
const text = renderNativeChildFollowupInstruction(childHarness, child.session_id);
|
|
1046
1079
|
await prompt(session, text, operationId, "child");
|
|
1047
1080
|
state.child_capacity[child.session_id].dispatched = true;
|
|
1048
1081
|
saveState(context, row, state);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AppError } from "../shared/errors.js";
|
|
2
|
-
import { CAPACITY_POLICY,
|
|
2
|
+
import { CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, overloadExhausted } from "../harness-runtime/lib/provider-overload-policy.mjs";
|
|
3
3
|
import { assertRunStartupSettled } from "./run-start-custody.js";
|
|
4
4
|
export function recoveryGuard(context, projectId, runId) { return context.db.get("SELECT recovery_id, project_id, run_id, generation, status, interruption_json, settlement_json, capture_path, created_at, updated_at, sealed_at, resumed_at FROM run_recovery_guards WHERE project_id = ? AND run_id = ? ORDER BY generation DESC LIMIT 1", [projectId, runId]) ?? null; }
|
|
5
5
|
export function assertRunRecoveryAllowed(context, projectId, runId, expectedGeneration) {
|
|
@@ -43,7 +43,7 @@ export function recoveryRuntimeAdmission(context, record, input) {
|
|
|
43
43
|
if (active) {
|
|
44
44
|
const ordinal = dispatch.capacity_intents.length;
|
|
45
45
|
const predecessor = ordinal === 1 ? dispatch.operation_id : dispatch.capacity_intents[ordinal - 2].operation_id;
|
|
46
|
-
if (active.policy !== CAPACITY_POLICY || active.ordinal !== ordinal || active.root_operation_id !== dispatch.operation_id || active.session_id !== binding.provider_session_id
|
|
46
|
+
if (ordinal > MAX_OVERLOAD_RETRIES || active.policy !== CAPACITY_POLICY || active.ordinal !== ordinal || active.root_operation_id !== dispatch.operation_id || active.session_id !== binding.provider_session_id
|
|
47
47
|
|| active.predecessor !== predecessor || active.operation_id !== `${dispatch.operation_id}:capacity:${ordinal}:session.prompt`
|
|
48
48
|
|| active.refusal?.policy !== CAPACITY_POLICY || active.refusal.ordinal !== ordinal - 1 || active.refusal.turn_id !== active.turn_id
|
|
49
49
|
|| active.refusal.session_id !== binding.provider_session_id || active.deadline !== null || !Number.isFinite(Date.parse(active.not_before)))
|
|
@@ -52,7 +52,7 @@ export function recoveryRuntimeAdmission(context, record, input) {
|
|
|
52
52
|
if (!failure || JSON.stringify(failure) !== JSON.stringify(active.refusal))
|
|
53
53
|
return false;
|
|
54
54
|
try {
|
|
55
|
-
if (
|
|
55
|
+
if (overloadExhausted(dispatch.capacity_failures))
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
58
|
catch {
|
|
@@ -8,7 +8,7 @@ import { managedProcessBinding, processIsAlive, resourceHome } from "./managed-p
|
|
|
8
8
|
import { getResourceDatabase } from "../storage/database.js";
|
|
9
9
|
import { requireManagedDaemonBinding } from "./managed-daemon-binding.js";
|
|
10
10
|
import { lifecycleRetryInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
11
|
-
import { CAPACITY_POLICY,
|
|
11
|
+
import { CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, overloadExhausted } from "../harness-runtime/lib/provider-overload-policy.mjs";
|
|
12
12
|
/** Pin retained Session loading and its first acknowledgement Turn, never a general fence bypass. */
|
|
13
13
|
export function prepareRunRecoveryDispatch(context, input) {
|
|
14
14
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(input.operationId) || !path.isAbsolute(input.stateDir))
|
|
@@ -60,7 +60,7 @@ export function prepareRunRecoveryDispatch(context, input) {
|
|
|
60
60
|
/** Publish only an exact successor to a failed, effect-free recovery ACK Turn. */
|
|
61
61
|
export function retainRecoveryCapacityIntent(context, input) {
|
|
62
62
|
const { intent } = input;
|
|
63
|
-
if (!Number.isSafeInteger(intent.ordinal) || intent.ordinal < 1 || !intent.turn_id || !Number.isFinite(Date.parse(intent.not_before)) || intent.policy !== CAPACITY_POLICY
|
|
63
|
+
if (!Number.isSafeInteger(intent.ordinal) || intent.ordinal < 1 || intent.ordinal > MAX_OVERLOAD_RETRIES || !intent.turn_id || !Number.isFinite(Date.parse(intent.not_before)) || intent.policy !== CAPACITY_POLICY
|
|
64
64
|
|| !intent.refusal || intent.refusal.policy !== CAPACITY_POLICY || intent.refusal.ordinal !== intent.ordinal - 1 || intent.refusal.turn_id !== intent.turn_id
|
|
65
65
|
|| !Number.isFinite(Date.parse(intent.refusal.first_observed_at)) || intent.deadline !== null)
|
|
66
66
|
throw new AppError("recovery_capacity_intent_invalid", "Recovery capacity successor is invalid", 1);
|
|
@@ -91,7 +91,7 @@ export function retainRecoveryCapacityIntent(context, input) {
|
|
|
91
91
|
}
|
|
92
92
|
const refusals = dispatch.capacity_failures ?? [];
|
|
93
93
|
const observed = refusals.find(item => item.ordinal === intent.refusal.ordinal);
|
|
94
|
-
if (!observed || JSON.stringify(observed) !== JSON.stringify(intent.refusal) ||
|
|
94
|
+
if (!observed || JSON.stringify(observed) !== JSON.stringify(intent.refusal) || overloadExhausted(refusals))
|
|
95
95
|
throw new AppError("recovery_capacity_intent_conflict", "Recovery successor lacks an immutable non-burst refusal binding", 1);
|
|
96
96
|
const predecessor = intent.ordinal === 1 ? dispatch.operation_id : prior[intent.ordinal - 2]?.operation_id;
|
|
97
97
|
if (prior.length !== intent.ordinal - 1 || predecessor !== intent.predecessor || intent.operation_id !== `${dispatch.operation_id}:capacity:${intent.ordinal}:session.prompt`)
|
package/dist/services/runs.js
CHANGED
|
@@ -26,6 +26,7 @@ import { flowCommand } from "./stage-pause.js";
|
|
|
26
26
|
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
27
27
|
import { admitBundledFlow, bundledHandlerContext } from "./stage-handlers.js";
|
|
28
28
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
29
|
+
import { projectPacketLocators, runContextRelocations } from "./stage-context.js";
|
|
29
30
|
const runSchemaId = "dd-flow/flow-run@3";
|
|
30
31
|
const runtimeSchemaId = "dd-flow/flow-run@3";
|
|
31
32
|
const allowedRunFlowKinds = [
|
|
@@ -284,20 +285,82 @@ export function importFlowRunExecutionProfile(context, input) {
|
|
|
284
285
|
export function rebindFlowRunWorkspace(context, input) {
|
|
285
286
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
286
287
|
const run = resolveRun(context, project.id, input.runId);
|
|
287
|
-
assertRunMutationAllowed(context, project.id, run.id);
|
|
288
|
+
const generation = assertRunMutationAllowed(context, project.id, run.id, input.expectedGeneration);
|
|
288
289
|
const workspaceRoot = resolveWorkspaceRoot(input.workspaceRoot);
|
|
289
290
|
const index = authoritativeIndex(run);
|
|
291
|
+
const receipt = input.routeReceipt ? { ...input.routeReceipt, policy: { ...input.routeReceipt.policy,
|
|
292
|
+
worktree_path: input.routeReceipt.policy.worktree_path ? resolveWorkspaceRoot(input.routeReceipt.policy.worktree_path) : null } } : undefined;
|
|
293
|
+
if (index.execution_bundle && !["vnext_specify", "vnext_protocolize"].includes(run.flow_kind)) {
|
|
294
|
+
if (workspaceRoot === run.workspace_root)
|
|
295
|
+
return { ok: true, changed: false };
|
|
296
|
+
throw new AppError("workspace_route_invalid", "Only SDLC may provision a different RUN workspace", 1, { effect: "no_effect" });
|
|
297
|
+
}
|
|
298
|
+
if (workspaceRoot === run.workspace_root) {
|
|
299
|
+
if (receipt && index.workspace_route && hybridDigest(retainedWorkspaceRoute(context, run).receipt) !== hybridDigest(receipt)) {
|
|
300
|
+
throw new AppError("workspace_route_conflict", "Committed workspace route differs from the repeated request", 1, { effect: "no_effect" });
|
|
301
|
+
}
|
|
302
|
+
publishRunProjections(context, project.id, run.id);
|
|
303
|
+
return { ok: true, changed: false, workspace_root: workspaceRoot };
|
|
304
|
+
}
|
|
305
|
+
if (index.workspace_route)
|
|
306
|
+
throw new AppError("workspace_route_conflict", "RUN already has a committed workspace route", 1, { effect: "no_effect" });
|
|
307
|
+
if (input.expectedRevision !== undefined && (index.runtime_revision ?? 0) !== input.expectedRevision)
|
|
308
|
+
throw new AppError("run_revision_changed", "RUN changed while its workspace was provisioned", 1, { effect: "no_effect", expected_revision: input.expectedRevision, actual_revision: index.runtime_revision ?? 0 });
|
|
309
|
+
if (index.execution_bundle && !receipt)
|
|
310
|
+
throw new AppError("workspace_route_missing", "Bundled SDLC rebind requires its prepared route receipt", 1, { effect: "no_effect" });
|
|
311
|
+
if (receipt && (run.workspace_root !== project.root || receipt.schema_id !== "dd-flow/vnext-workspace-route@1" || receipt.policy.route !== "feature_worktree"
|
|
312
|
+
|| !receipt.policy.provisioned || receipt.policy.worktree_path !== workspaceRoot || !receipt.created || !receipt.bootstrap)) {
|
|
313
|
+
throw new AppError("workspace_route_invalid", "Prepared workspace route does not match this RUN", 1, { effect: "no_effect" });
|
|
314
|
+
}
|
|
290
315
|
const now = context.now();
|
|
291
316
|
const facts = gitFacts(workspaceRoot);
|
|
317
|
+
if (receipt && (facts.branch !== receipt.policy.feature_branch || !receipt.policy.base_ref || !facts.head
|
|
318
|
+
|| spawnSync("git", ["-C", workspaceRoot, "merge-base", "--is-ancestor", receipt.policy.base_ref, facts.head]).status !== 0)) {
|
|
319
|
+
throw new AppError("workspace_route_invalid", "Prepared checkout differs from the feature branch/base receipt", 1, { effect: "no_effect", run_id: run.id });
|
|
320
|
+
}
|
|
292
321
|
index.workspace = { ...index.workspace, workspace_path: workspaceRoot, branch: facts.branch ?? "unknown", base: facts.head ?? "unknown", manager: "official-worktrunk" };
|
|
293
322
|
index.execution = { ...index.execution, project_root: project.root, workspace_root: workspaceRoot, git: facts };
|
|
294
323
|
index.updated_at = now;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
324
|
+
const route = receipt ? { schema_id: "dd-flow/run-workspace-route@1", run_id: run.id, project_id: project.id, project_root: project.root,
|
|
325
|
+
generation, previous_workspace: run.workspace_root, workspace_root: workspaceRoot, revision: (index.runtime_revision ?? 0) + 1,
|
|
326
|
+
git: facts, receipt, receipt_sha256: hybridDigest(receipt) } : undefined;
|
|
327
|
+
if (route)
|
|
328
|
+
index.workspace_route = { revision: route.revision, sha256: hybridDigest(route) };
|
|
329
|
+
context.db.writeTransaction(() => {
|
|
330
|
+
assertRunMutationAllowed(context, project.id, run.id, generation);
|
|
331
|
+
const latest = resolveRun(context, project.id, run.id);
|
|
332
|
+
if (latest.workspace_root !== run.workspace_root)
|
|
333
|
+
throw new AppError("workspace_route_changed", "RUN route changed during preparation", 1, { effect: "no_effect" });
|
|
334
|
+
context.db.run("UPDATE runs SET workspace_root = ? WHERE project_id = ? AND id = ?", [workspaceRoot, project.id, run.id]);
|
|
335
|
+
persistRunState(context, project, { ...run, workspace_root: workspaceRoot }, index);
|
|
336
|
+
appendAudit(context, { projectId: project.id, eventType: "flow_run.workspace_rebound", payload: { run_id: run.id, workspace_root: workspaceRoot, reason: input.reason, ...(route ? { route } : {}) } });
|
|
337
|
+
appendRunTimeline(context, project.id, run.id, { at: now, type: "workspace_rebound", run_id: run.id, workspace_root: workspaceRoot, reason: input.reason, git: facts });
|
|
338
|
+
}, "runs.rebindWorkspace");
|
|
299
339
|
return { ok: true, run_id: run.id, project_root: project.root, workspace_root: workspaceRoot, git: facts };
|
|
300
340
|
}
|
|
341
|
+
function retainedWorkspaceRoute(context, run) {
|
|
342
|
+
const index = authoritativeIndex(run), binding = index.workspace_route;
|
|
343
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.workspace_rebound' AND json_extract(payload_json, '$.run_id') = ? AND json_extract(payload_json, '$.route.revision') = ?", [run.project_id, run.id, binding?.revision ?? -1]);
|
|
344
|
+
const original = rows.length === 1 ? JSON.parse(rows[0].payload_json).route : undefined;
|
|
345
|
+
if (!binding || !original || original.schema_id !== "dd-flow/run-workspace-route@1" || hybridDigest(original) !== binding.sha256 || hybridDigest(original.receipt) !== original.receipt_sha256) {
|
|
346
|
+
throw new AppError("workspace_route_invalid", "RUN committed workspace route evidence changed", 1, { effect: "no_effect", run_id: run.id });
|
|
347
|
+
}
|
|
348
|
+
// Relocation receipts are admitted by snapshot import; never rewrite bundle
|
|
349
|
+
// or source route authority merely because a physical checkout moved.
|
|
350
|
+
const route = JSON.parse(projectPacketLocators(JSON.stringify(original), runContextRelocations(context, run.project_id, run.id)));
|
|
351
|
+
if (route.receipt_sha256 !== original.receipt_sha256
|
|
352
|
+
|| route.run_id !== run.id || route.project_id !== run.project_id || route.project_root !== run.project_root || route.workspace_root !== run.workspace_root
|
|
353
|
+
|| index.workspace.workspace_path !== run.workspace_root || index.execution?.workspace_root !== run.workspace_root || index.execution?.project_root !== run.project_root
|
|
354
|
+
|| index.workspace.manager !== "official-worktrunk" || index.workspace.branch !== route.receipt.policy.feature_branch
|
|
355
|
+
// execution.git is the latest observation (stage completion/refresh updates
|
|
356
|
+
// it). The immutable review base is workspace.base, not that moving HEAD.
|
|
357
|
+
|| index.workspace.base !== route.git.head
|
|
358
|
+
|| !Number.isInteger(route.generation) || route.generation < 0
|
|
359
|
+
|| route.receipt.policy.worktree_path !== run.workspace_root) {
|
|
360
|
+
throw new AppError("workspace_route_invalid", "RUN has no exact coherent committed workspace route", 1, { effect: "no_effect", run_id: run.id });
|
|
361
|
+
}
|
|
362
|
+
return route;
|
|
363
|
+
}
|
|
301
364
|
function loadUpgradeFlowContract(context, projectRoot) {
|
|
302
365
|
const canon = resolveCanonRoot(context, context.env.DD_MEMORYBANK ? { explicitRoot: context.env.DD_MEMORYBANK } : {});
|
|
303
366
|
if (!canon.ok || !canon.canon) {
|
|
@@ -1608,6 +1671,17 @@ export function publishRunProjections(context, projectId, runId) {
|
|
|
1608
1671
|
if (!latest)
|
|
1609
1672
|
throw new AppError("not_found", "RUN is not registered", 1, { run_id: runId });
|
|
1610
1673
|
const index = authoritativeIndex(latest);
|
|
1674
|
+
if (index.workspace_route) {
|
|
1675
|
+
const route = retainedWorkspaceRoute(context, latest);
|
|
1676
|
+
const file = path.join(runArtifactRoot(latest), "02-protocolize", "workspace-route.json");
|
|
1677
|
+
ensureDir(path.dirname(file));
|
|
1678
|
+
try {
|
|
1679
|
+
writeJsonFile(file, route.receipt);
|
|
1680
|
+
}
|
|
1681
|
+
catch (error) {
|
|
1682
|
+
throw new AppError("workspace_route_publication_failed", "Committed route projection could not be published", 1, { effect: "committed", run_id: runId, route_sha256: index.workspace_route.sha256, cause: String(error) });
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1611
1685
|
const mutations = context.db.get("SELECT 1 FROM sqlite_schema WHERE name = 'flow_run_flag_mutations'") ? context.db.all("SELECT resulting_revision, result_json, created_at, idempotency_key FROM flow_run_flag_mutations WHERE project_id = ? AND run_id = ? ORDER BY resulting_revision DESC LIMIT 32", [projectId, runId]) : [];
|
|
1612
1686
|
const history = mutations.reverse().map((row) => ({
|
|
1613
1687
|
revision: row.resulting_revision,
|
|
@@ -1661,11 +1735,26 @@ function authoritativeIndex(run) {
|
|
|
1661
1735
|
throw new AppError("runtime_state_invalid", "RUN mechanical state in SQLite is missing or invalid", 1, { run_id: run.id, cause: String(error) });
|
|
1662
1736
|
}
|
|
1663
1737
|
}
|
|
1664
|
-
function
|
|
1665
|
-
const scope = bundle
|
|
1738
|
+
function assertBundleWorkspaceIdentity(context, run, bundle) {
|
|
1739
|
+
const scope = currentBundleScope(context, run, bundle);
|
|
1740
|
+
const route = authoritativeIndex(run).workspace_route ? retainedWorkspaceRoute(context, run) : undefined;
|
|
1741
|
+
if (scope.workspace === run.workspace_root)
|
|
1742
|
+
return true;
|
|
1743
|
+
if (bundle.descriptor.descriptor.id !== "sdlc" || !["vnext_specify", "vnext_protocolize"].includes(run.flow_kind) || scope.workspace !== run.project_root)
|
|
1744
|
+
return false;
|
|
1745
|
+
if (!route)
|
|
1746
|
+
return false;
|
|
1747
|
+
return route.previous_workspace === scope.workspace && route.receipt.policy.route === "feature_worktree"
|
|
1748
|
+
&& route.receipt.policy.base_ref !== null && route.receipt.policy.feature_branch !== null;
|
|
1749
|
+
}
|
|
1750
|
+
function currentBundleScope(context, run, bundle) {
|
|
1751
|
+
return JSON.parse(projectPacketLocators(JSON.stringify(bundle.execution.snapshot().scope), runContextRelocations(context, run.project_id, run.id)));
|
|
1752
|
+
}
|
|
1753
|
+
function assertBundleRunIdentity(context, run, bundle) {
|
|
1754
|
+
const scope = currentBundleScope(context, run, bundle);
|
|
1666
1755
|
const flowId = bundle.descriptor.descriptor.id;
|
|
1667
1756
|
const matchesFlow = flowId === "sdlc" ? ["vnext_specify", "vnext_protocolize"].includes(run.flow_kind) : flowId === run.flow_kind;
|
|
1668
|
-
if (bundle.manifest.run_id !== run.id || scope.projectRoot !== run.project_root ||
|
|
1757
|
+
if (bundle.manifest.run_id !== run.id || scope.projectRoot !== run.project_root || !assertBundleWorkspaceIdentity(context, run, bundle) || !matchesFlow) {
|
|
1669
1758
|
throw new AppError("run_bundle_identity_invalid", "Execution bundle belongs to another RUN or workspace", 1, { phase: "prepare", effect: "no_effect", run_id: run.id });
|
|
1670
1759
|
}
|
|
1671
1760
|
const blobs = new Map(bundle.manifest.inventory.map(ref => [ref.path, bundle.blob(ref.path)]));
|
|
@@ -1697,7 +1786,7 @@ export function runBundleAuthorityMetadata(bundle) {
|
|
|
1697
1786
|
*/
|
|
1698
1787
|
export function assertPreparedRunBundleAuthority(context, scope, bundle) {
|
|
1699
1788
|
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
1700
|
-
assertBundleRunIdentity(run, bundle);
|
|
1789
|
+
assertBundleRunIdentity(context, run, bundle);
|
|
1701
1790
|
}
|
|
1702
1791
|
/** SQL-only fence after the caller has admitted compiled handlers outside the
|
|
1703
1792
|
* writer. Descriptor schema loading is deliberately excluded from this fence.
|
|
@@ -1713,9 +1802,9 @@ export function assertPreparedRunBundleSqlAuthority(context, scope, bundle) {
|
|
|
1713
1802
|
|| anchor.descriptor_sha256 !== bundle.manifest.descriptor.sha256 || anchor.flow_id !== bundle.manifest.flow_id
|
|
1714
1803
|
|| anchor.admission_sha256 !== hybridDigest(bundleAuthority(bundle.admission())))
|
|
1715
1804
|
throw new AppError("run_bundle_anchor_invalid", "Prepared bundle differs from authoritative RUN", 1);
|
|
1716
|
-
const execution = bundle
|
|
1805
|
+
const execution = currentBundleScope(context, run, bundle);
|
|
1717
1806
|
const flow = bundle.descriptor.descriptor.id;
|
|
1718
|
-
if (bundle.manifest.run_id !== run.id || execution.projectRoot !== run.project_root ||
|
|
1807
|
+
if (bundle.manifest.run_id !== run.id || execution.projectRoot !== run.project_root || !assertBundleWorkspaceIdentity(context, run, bundle)
|
|
1719
1808
|
|| !(flow === "sdlc" ? ["vnext_specify", "vnext_protocolize"].includes(run.flow_kind) : flow === run.flow_kind)) {
|
|
1720
1809
|
throw new AppError("run_bundle_identity_invalid", "Execution bundle belongs to another RUN or workspace", 1, { effect: "no_effect", run_id: run.id });
|
|
1721
1810
|
}
|
|
@@ -1755,7 +1844,7 @@ export function readFlowRunExecutionBundle(context, input) {
|
|
|
1755
1844
|
}
|
|
1756
1845
|
const admission = { ...retained, receipts: new Map(retained.receipts), descriptorContext: bundledHandlerContext(new Map()) };
|
|
1757
1846
|
const bundle = RetainedExecutionBundle.read(path.join(runArtifactRoot(run), "execution-bundle"), anchor.manifest_sha256, admission);
|
|
1758
|
-
assertBundleRunIdentity(run, bundle);
|
|
1847
|
+
assertBundleRunIdentity(context, run, bundle);
|
|
1759
1848
|
if (bundle.manifest.descriptor.sha256 !== anchor.descriptor_sha256 || bundle.manifest.flow_id !== anchor.flow_id) {
|
|
1760
1849
|
throw new AppError("run_bundle_anchor_invalid", "RUN bundle descriptor identity changed", 1);
|
|
1761
1850
|
}
|
|
@@ -1766,7 +1855,7 @@ export function bindFlowRunExecutionBundle(context, input) {
|
|
|
1766
1855
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
1767
1856
|
const run = resolveRun(context, project.id, input.runId), index = authoritativeIndex(run);
|
|
1768
1857
|
const generation = assertRunMutationAllowed(context, project.id, run.id);
|
|
1769
|
-
assertBundleRunIdentity(run, input.bundle);
|
|
1858
|
+
assertBundleRunIdentity(context, run, input.bundle);
|
|
1770
1859
|
if (input.bundle.manifest.flow_id !== "sdlc") {
|
|
1771
1860
|
throw new AppError("run_bundle_start_required", "Memory RUN and its bundle authority must be allocated together through the request-bound start", 1, { phase: "prepare", effect: "no_effect", run_id: run.id });
|
|
1772
1861
|
}
|
|
@@ -42,7 +42,7 @@ export function prepareVnextCodeReviewStart(context, input) {
|
|
|
42
42
|
const run = requireRun(context, project.id, input.runId);
|
|
43
43
|
const home = requireHome(run);
|
|
44
44
|
assertRunMutationAllowed(context, project.id, run.id);
|
|
45
|
-
requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage });
|
|
45
|
+
requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage });
|
|
46
46
|
if (!fs.existsSync(path.join(home, "05-code", "stage-report.json")))
|
|
47
47
|
throw new AppError("runtime_artifact_missing", "CODE-REVIEW requires an accepted CODE report", 1);
|
|
48
48
|
const root = path.join(home, stageDir);
|
|
@@ -42,7 +42,7 @@ export async function prepareVnextCodeWorkspace(context, input) {
|
|
|
42
42
|
const project = requireProjectByRoot(context, projectRoot);
|
|
43
43
|
const run = requireRun(context, project.id, input.runId);
|
|
44
44
|
assertRunMutationAllowed(context, project.id, run.id);
|
|
45
|
-
requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: requireHome(run), workspaceRoot: run.workspace_root, stage: input.stage ?? stage });
|
|
45
|
+
requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: requireHome(run), workspaceRoot: run.workspace_root, stage: input.stage ?? stage });
|
|
46
46
|
const root = path.join(requireHome(run), stageDir);
|
|
47
47
|
fs.mkdirSync(root, { recursive: true });
|
|
48
48
|
return ensureCodeWorkspaceReady(context, project.id, run, root, [], input.progress, { force: input.stage !== "code-review" });
|
|
@@ -53,7 +53,7 @@ export function prepareVnextCodeStart(context, input) {
|
|
|
53
53
|
const run = requireRun(context, project.id, input.runId);
|
|
54
54
|
assertRunMutationAllowed(context, project.id, run.id);
|
|
55
55
|
const home = requireHome(run);
|
|
56
|
-
requireVnextWorkspaceRoute({ projectRoot, runId: input.runId, runHome: home, workspaceRoot: run.workspace_root, stage });
|
|
56
|
+
requireVnextWorkspaceRoute(context, { projectRoot, runId: input.runId, runHome: home, workspaceRoot: run.workspace_root, stage });
|
|
57
57
|
const existingRoot = path.join(home, stageDir);
|
|
58
58
|
const existingPrompt = path.join(existingRoot, "stage-prompt.md");
|
|
59
59
|
const existingStage = stageStatus(run, stage);
|
|
@@ -158,7 +158,7 @@ export async function finishVnextCode(context, input) {
|
|
|
158
158
|
const home = requireHome(run);
|
|
159
159
|
const root = path.join(home, stageDir);
|
|
160
160
|
const generation = assertRunMutationAllowed(context, project.id, run.id);
|
|
161
|
-
requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage });
|
|
161
|
+
requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage });
|
|
162
162
|
if (stageStatus(run, stage) === "done" && fs.existsSync(path.join(root, "stage-report.json"))) {
|
|
163
163
|
resumeAcceptedCodeStage(context, run, projectRoot, stage, JSON.parse(fs.readFileSync(path.join(root, "stage-report.json"), "utf8")), nextAction(run, changedPathsFromReport(path.join(root, "stage-report.json"))));
|
|
164
164
|
return { ok: true, resumed: true, run_id: run.id, stage, outcome: "accepted", report_path: path.join(root, "stage-report.json"), next_action: nextAction(run, changedPathsFromReport(path.join(root, "stage-report.json"))) };
|
|
@@ -10,7 +10,8 @@ import { storageSessionId } from "./session-identity.js";
|
|
|
10
10
|
import { reconcileUnfinishedChecks } from "./code-checks.js";
|
|
11
11
|
import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
12
12
|
import { stageWorkIds, stageGraphScope } from "./stage-work-graph.js";
|
|
13
|
-
import {
|
|
13
|
+
import { eligibleProviderChildOverload } from "./provider-capacity.js";
|
|
14
|
+
import { renderNativeChildFollowupInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
14
15
|
import { readDescriptorRootWork } from "./descriptor-root-work.js";
|
|
15
16
|
import { selectMemoryCompositionRegistration } from "./memory-composition-registration.js";
|
|
16
17
|
import { safeNativeFailure } from "../harness-runtime/lib/native-children.mjs";
|
|
@@ -262,7 +263,7 @@ export async function reconcileVnextFanout(context, input) {
|
|
|
262
263
|
continue;
|
|
263
264
|
}
|
|
264
265
|
const link = active[0];
|
|
265
|
-
if (value.harness_id
|
|
266
|
+
if (renderNativeChildFollowupInstruction(String(value.harness_id), String(child.session_id)) && eligibleProviderChildOverload(child, value.parent_session_id)) {
|
|
266
267
|
issues.push({ code: "child_capacity_pending", work_id: link.work_id, session_id: child.session_id });
|
|
267
268
|
continue;
|
|
268
269
|
}
|
|
@@ -50,7 +50,7 @@ export function prepareVnextPlanReviewStart(context, input) {
|
|
|
50
50
|
return { ...common, state: "terminal", report, outcome };
|
|
51
51
|
throw new AppError("invalid_work_state", "PLAN-REVIEW is already terminal; corrections are applied in the same review Work and are not re-reviewed automatically", 2, { run_id: run.id, outcome });
|
|
52
52
|
}
|
|
53
|
-
const workspaceRoute = requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan-review" });
|
|
53
|
+
const workspaceRoute = requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan-review" });
|
|
54
54
|
if (!fs.existsSync(path.join(planRoot, "stage-report.json")) || !fs.existsSync(batch))
|
|
55
55
|
throw new AppError("runtime_artifact_missing", "PLAN-REVIEW requires the accepted PLAN report and proposed CODE batch", 1);
|
|
56
56
|
const requested = requestedMode(run);
|
|
@@ -33,7 +33,7 @@ export function prepareVnextPlanStart(context, input) {
|
|
|
33
33
|
const project = requireProjectByRoot(context, projectRoot);
|
|
34
34
|
const run = requireRun(context, projectRoot, input.runId);
|
|
35
35
|
const home = requireHome(run);
|
|
36
|
-
const workspaceRoute = requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
|
|
36
|
+
const workspaceRoute = requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
|
|
37
37
|
const protocols = protocolIds(home);
|
|
38
38
|
if (!protocols.length)
|
|
39
39
|
throw new AppError("runtime_artifact_missing", "PLAN requires accepted PROTOCOLIZE protocols", 1);
|
|
@@ -136,7 +136,7 @@ export function startVnextPlan(context, input, prepared = prepareVnextPlanStart(
|
|
|
136
136
|
export function prepareVnextPlanFinish(context, input) {
|
|
137
137
|
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
138
138
|
const run = requireRun(context, projectRoot, input.runId), home = requireHome(run);
|
|
139
|
-
requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
|
|
139
|
+
requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
|
|
140
140
|
const protocols = protocolIds(home);
|
|
141
141
|
assertProtocolWorkspace(run.workspace_root, protocols);
|
|
142
142
|
const prepared = prepareVnextPlanArtifacts(context, { projectRoot, workspaceRoot: run.workspace_root, runId: run.id, home, protocols });
|
|
@@ -153,7 +153,7 @@ function finishVnextPlanOwned(context, input) {
|
|
|
153
153
|
const run = requireRun(context, projectRoot, input.runId);
|
|
154
154
|
const home = requireHome(run);
|
|
155
155
|
const root = path.join(home, "03-plan");
|
|
156
|
-
requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
|
|
156
|
+
requireVnextWorkspaceRoute(context, { projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage: "plan" });
|
|
157
157
|
const protocols = protocolIds(home);
|
|
158
158
|
if (!protocols.length)
|
|
159
159
|
throw new AppError("runtime_artifact_missing", "PLAN requires accepted PROTOCOLIZE protocols", 1);
|