Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/server/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export function resetLifecycleDrainStateForTests(): void {
}
export function tryAdmitTurn(sessionLaneId?: string, clientSignal?: AbortSignal): ActiveTurnLease | null {
if (isDraining()) return null;
if (clientSignal?.aborted) return null;
const opaqueSessionLaneId = sessionLaneId
? createHash("sha256").update(sessionLaneId).digest("hex").slice(0, SESSION_LANE_ID_BYTES)
: undefined;
Expand All @@ -195,6 +196,7 @@ export function tryAdmitTurn(sessionLaneId?: string, clientSignal?: AbortSignal)
const attachedLeases = new Set<AdmissionLease>();
const abortListeners = new Map<AbortSignal, () => void>();
let cancellationReason: unknown;
let cancelled = false;
let active = true;
let transferred = false;
let nativeMainClaimed = false;
Expand All @@ -206,10 +208,12 @@ export function tryAdmitTurn(sessionLaneId?: string, clientSignal?: AbortSignal)
bindAbortSignal(signal) {
if (!active || abortListeners.has(signal)) return () => {};
const onAbort = () => {
if (cancelled) return;
cancelled = true;
cancellationReason = signal.reason;
const boundControllers = [...controllers];
lease.release();
for (const controller of boundControllers) controller.abort(cancellationReason);
for (const controller of [...controllers]) {
if (!controller.signal.aborted) controller.abort(cancellationReason);
}
};
abortListeners.set(signal, onAbort);
signal.addEventListener("abort", onAbort, { once: true });
Expand All @@ -226,20 +230,24 @@ export function tryAdmitTurn(sessionLaneId?: string, clientSignal?: AbortSignal)
ac.abort(cancellationReason ?? new Error("turn already settled"));
return;
}
if (cancelled) {
ac.abort(cancellationReason ?? new Error("turn cancelled"));
return;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
transferred = true;
controllers.add(ac);
activeTurns.set(ac, lease);
lease.bindAbortSignal(ac.signal);
},
beginCodexAccountSelection() {
const mainProfileDraining = nativeMainDrainOwners.size > 0;
let selectionActive = active && !mainProfileDraining;
let selectionActive = active && !cancelled && !mainProfileDraining;
let released = false;
if (selectionActive) nativeMainSelections += 1;
const selection: CodexAccountSelectionAdmission = {
mainProfileDraining,
claimMainProfile() {
if (released || mainProfileDraining || !active) return false;
if (released || mainProfileDraining || !active || cancelled) return false;
if (!nativeMainClaimed) {
nativeMainClaimed = true;
nativeMainTurns.add(lease);
Expand Down
2 changes: 1 addition & 1 deletion structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Native result continuations and function-result injection follow [the mode-speci

Native steering follows [the shared WebSocket contract](transports/streaming-health.md#experimental-native-mid-turn-steering); this surface's defaults remain unchanged.

Responses admission and finalization are composed through the [core module ownership](transports/responses.md#core-module-ownership). Kiro's optional account-load admission is process-local and request-owned; its slot ends with the response body or cancellation. Other providers retain their admission path. Cancellation after a slot grant and before the request resumes releases the slot and returns cancellation without dispatch.
Responses admission and finalization are composed through the [core module ownership](transports/responses.md#core-module-ownership). Kiro's optional account-load admission is process-local and request-owned; its slot ends with the response body or cancellation. Other providers retain their admission path. Cancellation after a slot grant and before the request resumes releases the slot and returns cancellation without dispatch. In `src/server/lifecycle.ts`, cancellation promptly aborts bound controllers but retains active-turn, session-lane and workflow/native ownership until the owner finalizes; late controllers abort immediately without entering the active registry or transferring ownership, cancelled turns cannot select native accounts, and shutdown may release turns while admission is fenced.

Catalog HTTP acquisition follows the [proxy-routing contract](catalog.md#remote-catalog-http-proxy-routing). CLI model lists expose shared catalog estimates as `price`; `models price` retains the saved override in `cost` and adds `effectiveCost`. Explicit zero overrides win; unknown prices remain null, and automatic defaults are never persisted to `modelCosts`. Covered by `tests/cli/cli-models.test.ts` and `tests/cli/cli-models-price.test.ts`. Ordinary model discovery, startup sync, and explicit sync apply the [new-arrival catalog contract](catalog.md#shared-catalog) before publishing rows. The scoped persisted decision also updates the live config merge baseline, so subsequent visibility writes preserve manual re-enablement. A file-loaded inventory that already differs from disk can publish HTTP rows through a detached policy projection without adding unsaved live disables; retained catalog writes still refuse stale inventory, and its load provenance follows the [config contract](config.md).

Expand Down
6 changes: 3 additions & 3 deletions structure/transports/responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ silence. It cannot observe the client's workspace or prove whether a successful
repository state. Consequently, the active-turn and session-lane gates are concurrency admission
limits, the translator budget follows [live retained-byte ownership](byte-accounting.md#stream-buffer-accounting), the response-state caps are cache
retention limits, and the stall watchdog is a silence limit. None is a cumulative continuation or
semantic no-progress budget. The turn lease in `src/server/lifecycle.ts` subscribes to HTTP ingress cancellation, internal decision cancellation and every registered controller. Cancellation releases turn admission, session lanes, attached workflow leases and pending native-main selection ownership without waiting for upstream work. Live/Realtime acquisition binds ingress cancellation until WebSocket upgrade transfers ownership. Normal completion detaches the listeners; late finalizers remain idempotent.
semantic no-progress budget. The turn lease in `src/server/lifecycle.ts` subscribes to HTTP ingress cancellation, internal decision cancellation and every registered controller. Cancellation aborts every bound controller but retains turn admission, session lanes and attached ownership until the corresponding owner finalizes; pending native-main selection cannot claim an account after cancellation. Live/Realtime acquisition binds ingress cancellation until WebSocket upgrade transfers ownership. Normal completion detaches the listeners; late finalizers remain idempotent.

> Decision record: [ADR-0031](../decisions/ADR-0031-responses-http-sse.md)
> Decision record: [ADR-0032](../decisions/ADR-0032-responses-http-sse.md)
Expand Down Expand Up @@ -590,8 +590,8 @@ keeps only allowlisted transport verdict codes and the explicit local `spend_led
deserialize them as required fields.


Active-turn admission owns workflow admission, so both remain held until a streaming body finishes or
is cancelled.
Active-turn admission owns workflow admission, so both remain held until their owner finalizes.
Cancellation signals upstream work immediately; signalling alone does not release admission.

The shared endpoint path also preserves the cyber-policy stop when a malformed UTF-8 5xx body is
replacement-decoded; other malformed-body usage, quota, reset evidence and classification retain the
Expand Down
Loading
Loading