fix(k1): harden live handoff and camera recovery

This commit is contained in:
DCCONSTRUCTIONS
2026-08-22 13:09:24 +03:00
parent eaad9deda1
commit 85035fa07b
26 changed files with 1478 additions and 170 deletions
@@ -135,6 +135,23 @@ export class SnapshotRuntimeActionArbiter {
}
}
export function runtimeActionResponseAlreadyAccepted(
responseState: XgridsK1State,
acceptedState: XgridsK1State | null,
): boolean {
const responseRuntimeId = responseState.snapshot_runtime_id?.trim() ?? "";
const acceptedRuntimeId = acceptedState?.snapshot_runtime_id?.trim() ?? "";
const responseRevision = responseState.snapshot_revision;
const acceptedRevision = acceptedState?.snapshot_revision;
return Boolean(
responseRuntimeId
&& acceptedRuntimeId === responseRuntimeId
&& Number.isSafeInteger(responseRevision)
&& Number.isSafeInteger(acceptedRevision)
&& (acceptedRevision as number) >= (responseRevision as number)
);
}
export type AcquisitionPreparationDraft = Omit<
PrepareAcquisitionRequest,
| "operation_id"
@@ -248,6 +265,87 @@ export function connectionActionAuthoritySnapshot(
}
const CONTROL_STATE_READ_INTERVAL_MS = 250;
const NETWORK_PROVISION_SETTLEMENT_FALLBACK_MS = 30_000;
const NETWORK_PROVISION_SETTLEMENT_MAX_MS = 300_000;
const NETWORK_PROVISION_SETTLEMENT_GRACE_MS = 1_000;
function networkProvisionSettlementDeadline(
operation: XgridsOperation,
startedAtMs: number,
): number {
const operationDeadlineMs = operation.deadline_at
? Date.parse(operation.deadline_at)
: Number.NaN;
const requestedDeadlineMs = Number.isFinite(operationDeadlineMs)
&& operationDeadlineMs > startedAtMs
? operationDeadlineMs + NETWORK_PROVISION_SETTLEMENT_GRACE_MS
: startedAtMs + NETWORK_PROVISION_SETTLEMENT_FALLBACK_MS;
return Math.min(
requestedDeadlineMs,
startedAtMs + NETWORK_PROVISION_SETTLEMENT_MAX_MS,
);
}
/**
* Follow one already-admitted Apply after a browser response is interrupted by
* the host Wi-Fi handoff. This loop only reads the local journal; it never
* retries the HTTP mutation, BLE write, CoreWLAN association or control open.
*/
export async function awaitNetworkProvisionSettlementAfterLostResponse(
initialState: XgridsK1State,
idempotencyKey: string,
readState: () => Promise<XgridsK1State>,
acceptState: (state: XgridsK1State) => void,
assertOperatorIntentCurrent: () => void,
options: {
now?: () => number;
wait?: (delayMs: number) => Promise<void>;
} = {},
): Promise<XgridsK1State> {
const now = options.now ?? Date.now;
const wait = options.wait ?? ((delayMs: number) => new Promise<void>((resolve) => {
globalThis.setTimeout(resolve, delayMs);
}));
let state = initialState;
let operation = operationByIdempotencyKey(
state,
"network.provision",
idempotencyKey,
);
if (!operation || !["accepted", "running"].includes(operation.status)) {
return state;
}
const deadlineMs = networkProvisionSettlementDeadline(operation, now());
while (["accepted", "running"].includes(operation.status)) {
assertOperatorIntentCurrent();
const remainingMs = deadlineMs - now();
if (remainingMs <= 0) return state;
await wait(Math.min(CONTROL_STATE_READ_INTERVAL_MS, remainingMs));
assertOperatorIntentCurrent();
try {
const nextState = await readState();
assertOperatorIntentCurrent();
acceptState(nextState);
state = nextState;
} catch (readError) {
// A host Wi-Fi transition can briefly abort even a localhost fetch.
// Preserve the admitted operation and perform only the next bounded
// journal read; the original Apply is never reissued.
if (!(readError instanceof ApiError) || !readError.transportUnavailable) {
throw readError;
}
continue;
}
operation = operationByIdempotencyKey(
state,
"network.provision",
idempotencyKey,
);
if (!operation) return state;
}
return state;
}
const CONTROL_PHASE_WAIT_TIMEOUT_MS = 120_000;
function controlPhase(state: XgridsK1State): XgridsApplicationControlPhase {
@@ -977,7 +1075,10 @@ export function useXgridsK1Runtime(enabled: boolean) {
!operatorIntents.current.isRuntimeCurrent(runtimeToken)
|| !runtimeActionArbiter.current.isCurrent(actionToken)
) return false;
if (!acceptState(nextState)) return false;
if (
!acceptState(nextState)
&& !runtimeActionResponseAlreadyAccepted(nextState, latestState.current)
) return false;
if (!runtimeActionArbiter.current.isCurrent(actionToken)) return false;
return true;
} catch (operationError) {
@@ -1355,6 +1456,13 @@ export function useXgridsK1Runtime(enabled: boolean) {
throw connectError;
}
acceptState(failedState);
failedState = await awaitNetworkProvisionSettlementAfterLostResponse(
failedState,
request.idempotency_key,
() => xgridsK1Api.getState(),
acceptState,
assertOperatorIntentCurrent,
);
const failedOperation = operationByIdempotencyKey(
failedState,
"network.provision",