From 1729cb22c4f81021d15e7786d3d0b96069a6ae36 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 12 Jul 2026 10:53:20 +0200 Subject: [PATCH] fix: expose durable runner admission identity --- .../src/agentrun-session-send-render.test.ts | 268 ++++++++++++++++++ scripts/src/agentrun/session-send-render.ts | 137 ++++++--- 2 files changed, 372 insertions(+), 33 deletions(-) create mode 100644 scripts/src/agentrun-session-send-render.test.ts diff --git a/scripts/src/agentrun-session-send-render.test.ts b/scripts/src/agentrun-session-send-render.test.ts new file mode 100644 index 00000000..bba2e39f --- /dev/null +++ b/scripts/src/agentrun-session-send-render.test.ts @@ -0,0 +1,268 @@ +import { describe, expect, test } from "bun:test"; +import { renderSessionSendError, renderSessionSendResult } from "./agentrun/session-send-render"; +import type { AgentRunOutputMode, AgentRunResourceOptions } from "./agentrun/utils"; + +const command = "agentrun send session/sess_admission_fixture --prompt-stdin"; +const sessionId = "sess_admission_fixture"; +const runId = "run_admission_fixture"; +const commandId = "cmd_admission_fixture"; +const dispatchIntentId = "rdi_admission_fixture"; +const plannedRunnerJobId = "rjob_planned_fixture"; + +const durableAdmissionFixture: Record = { + ok: true, + command, + data: { + action: "session-send", + dryRun: false, + mutation: true, + partialWrite: false, + sessionId, + decision: "turn", + internalCommandType: "turn", + run: { id: runId, status: "queued", sessionRef: { sessionId } }, + command: { + id: commandId, + state: "pending", + type: "turn", + dispatchIntent: { + id: dispatchIntentId, + state: "pending", + runnerJobId: plannedRunnerJobId, + actualRunnerJobId: null, + attemptCount: 0, + durable: true, + }, + }, + runnerJob: { + action: "runner-dispatch-admitted", + state: "pending", + runId, + commandId, + runnerJobId: plannedRunnerJobId, + dispatchIntentId, + durable: true, + }, + runnerAdmission: { + mode: "durable-dispatch-intent", + state: "pending", + reason: "session-runner-admission-recovered", + disposition: "recovered", + failureKind: null, + sessionId, + runId, + commandId, + dispatchIntentId, + plannedRunnerJobId, + actualRunnerJobId: null, + attemptCount: 0, + durable: true, + mutation: true, + partialWrite: false, + recoveredPriorPartialWrite: true, + retryable: true, + retryAuthority: "dispatcher", + recoveryActions: [ + { operation: "describe", resourceKind: "session", resourceName: sessionId }, + { operation: "events", resourceKind: "run", resourceName: runId, runId, afterSeq: 0, limit: 100 }, + ], + valuesPrinted: false, + }, + pollActions: [ + { operation: "logs", resourceKind: "session", resourceName: sessionId, sessionId, limit: 100 }, + { operation: "cancel", resourceKind: "session", resourceName: sessionId }, + ], + valuesPrinted: false, + }, +}; + +const legacySuccessFixture: Record = { + ok: true, + command, + data: { + action: "session-send", + dryRun: false, + mutation: true, + partialWrite: false, + sessionId, + decision: "turn", + internalCommandType: "turn", + run: { id: runId, status: "queued", sessionRef: { sessionId } }, + command: { id: commandId, state: "pending", type: "turn" }, + runnerJob: { id: "rjob_legacy_fixture", state: "queued", runId, commandId }, + pollActions: [ + { operation: "describe", resourceKind: "session", resourceName: sessionId }, + { operation: "events", resourceKind: "run", resourceName: runId, runId, afterSeq: 0, limit: 100 }, + ], + valuesPrinted: false, + }, +}; + +const typedFailureFixture: Record = { + ok: false, + command, + failureKind: "validation-failed", + message: "durable runner dispatch is not enabled for session turn admission", + httpStatus: 503, + agentrun: { + failureKind: "infra-failed", + error: { + name: "AgentRunError", + failureKind: "infra-failed", + details: { + code: "runner-admission-failed", + reason: "session-runner-dispatcher-disabled", + phase: "runner-admission", + sessionId, + mutation: false, + partialWrite: false, + retryable: false, + retryAuthority: "none", + recoveryAction: "restore-runner-dispatcher", + recoveryActions: [ + { operation: "describe", resourceKind: "session", resourceName: sessionId }, + ], + valuesPrinted: false, + }, + }, + }, +}; + +describe("AgentRun session send runner admission rendering", () => { + test("default output exposes durable admission without claiming a Kubernetes Job exists", () => { + const result = renderSessionSendResult(command, durableAdmissionFixture, options("human")); + expect(result.ok).toBe(true); + expect(result.renderedText).toContain("RunnerAdmission:"); + expect(result.renderedText).toContain("mode=durable-dispatch-intent state=pending disposition=recovered"); + expect(result.renderedText).toContain(`runId=${runId} commandId=${commandId} dispatchIntentId=${dispatchIntentId}`); + expect(result.renderedText).toContain(`plannedRunnerJobId=${plannedRunnerJobId} actualRunnerJobId=-`); + expect(result.renderedText).toContain("mutation=true partialWrite=false recoveredPriorPartialWrite=true"); + expect(result.renderedText).toContain("retryable=true retryAuthority=dispatcher"); + expect(result.renderedText).toContain(`planned-runnerjob/${plannedRunnerJobId}`); + expect(result.renderedText).toContain("它本身不是 Kubernetes Job 已创建的证据"); + expect(result.renderedText).toContain("durable dispatcher 负责自动重试;这不是重复执行 session send 的授权"); + expect(result.renderedText).not.toContain(`describe runnerjob/${plannedRunnerJobId}`); + expect(result.renderedText).not.toContain("agentrun send session/"); + expect(Buffer.byteLength(result.renderedText, "utf8")).toBeLessThan(7000); + }); + + test("JSON and YAML expose the same bounded runner admission contract and read-only Next", () => { + const jsonText = renderSessionSendResult(command, durableAdmissionFixture, options("json")).renderedText; + const yamlText = renderSessionSendResult(command, durableAdmissionFixture, options("yaml")).renderedText; + const json = JSON.parse(jsonText) as Record; + const yaml = Bun.YAML.parse(yamlText) as Record; + expect(yaml).toEqual(json); + expect(json.runnerAdmission).toMatchObject({ + mode: "durable-dispatch-intent", + state: "pending", + reason: "session-runner-admission-recovered", + disposition: "recovered", + runId, + commandId, + dispatchIntentId, + plannedRunnerJobId, + actualRunnerJobId: null, + mutation: true, + partialWrite: false, + recoveredPriorPartialWrite: true, + retryable: true, + retryAuthority: "dispatcher", + }); + expect(json.resources.runnerJob).toBeNull(); + expect(json.resources.plannedRunnerJob).toEqual({ + kind: "planned-runnerjob", + id: plannedRunnerJobId, + identityRole: "durable-planned", + kubernetesJobCreated: null, + kubernetesJobCreationEvidence: "not-asserted", + state: "pending", + durable: true, + }); + expect(json.next.actions.length).toBeGreaterThan(0); + for (const action of json.next.actions) { + expect(action.mutation).toBe(false); + expect(action.command).toMatch(/^bun scripts\/cli\.ts agentrun (?:describe|events|logs|result)\b/u); + expect(action.command).not.toContain("agentrun send"); + expect(action.command).not.toContain(`runnerjob/${plannedRunnerJobId}`); + } + for (const output of [jsonText, yamlText]) expect(Buffer.byteLength(output, "utf8")).toBeLessThan(7000); + }); + + test("legacy manager success output keeps runnerJob behavior when runnerAdmission is absent", () => { + const human = renderSessionSendResult(command, legacySuccessFixture, options("human")).renderedText; + const json = JSON.parse(renderSessionSendResult(command, legacySuccessFixture, options("json")).renderedText) as Record; + expect(human).not.toContain("RunnerAdmission:"); + expect(human).toContain("runnerjob/rjob_legacy_fixture state=queued"); + expect(human).toContain("只使用 manager 返回的资源 identity 做只读下钻"); + expect(json.runnerAdmission).toBeNull(); + expect(json.resources.runnerJob).toEqual({ kind: "runnerjob", id: "rjob_legacy_fixture", state: "queued" }); + expect(json.resources).not.toHaveProperty("plannedRunnerJob"); + expect(json.next.actions.some((action: Record) => String(action.command).includes("describe runnerjob/rjob_legacy_fixture"))).toBe(true); + }); + + test("typed failure remains fail-closed, bounded, and does not suggest a repeated send", () => { + const human = renderSessionSendError(command, typedFailureFixture, options("human")); + const jsonText = renderSessionSendError(command, typedFailureFixture, options("json")).renderedText; + const yamlText = renderSessionSendError(command, typedFailureFixture, options("yaml")).renderedText; + const json = JSON.parse(jsonText) as Record; + const yaml = Bun.YAML.parse(yamlText) as Record; + expect(human.ok).toBe(false); + expect(human.renderedText).toContain("TypedFailure: infra-failed"); + expect(human.renderedText).toContain("Reason: session-runner-dispatcher-disabled"); + expect(human.renderedText).toContain("Mutation: false"); + expect(human.renderedText).toContain("PartialWrite: false"); + expect(human.renderedText).toContain("Retryable: false"); + expect(human.renderedText).toContain("RetryAuthority: none"); + expect(human.renderedText).toContain("RecoveryAction: restore-runner-dispatcher"); + expect(human.renderedText).not.toContain("agentrun send session/"); + expect(yaml).toEqual(json); + expect(json).toMatchObject({ + kind: "SessionSendError", + mutation: false, + partialWrite: false, + error: { + failureKind: "validation-failed", + typedFailureKind: "infra-failed", + code: "runner-admission-failed", + reason: "session-runner-dispatcher-disabled", + retryable: false, + retryAuthority: "none", + recoveryAction: "restore-runner-dispatcher", + }, + }); + for (const output of [human.renderedText, jsonText, yamlText]) expect(Buffer.byteLength(output, "utf8")).toBeLessThan(7000); + }); +}); + +function options(output: AgentRunOutputMode): AgentRunResourceOptions { + return { + output, + full: false, + raw: false, + debug: false, + taskInput: false, + limit: 20, + cursor: null, + queue: null, + state: null, + unread: false, + readerId: "cli", + taskId: null, + runId: null, + commandId: null, + sessionId: null, + afterSeq: null, + eventDetailSeq: null, + tail: null, + fullText: false, + reason: null, + dryRun: false, + file: null, + aipod: null, + idempotencyKey: null, + promptStdin: false, + node: null, + lane: null, + passthroughArgs: [], + }; +} diff --git a/scripts/src/agentrun/session-send-render.ts b/scripts/src/agentrun/session-send-render.ts index fb324ad4..bbb89988 100644 --- a/scripts/src/agentrun/session-send-render.ts +++ b/scripts/src/agentrun/session-send-render.ts @@ -48,28 +48,37 @@ export function sessionSendSuccessProjection(command: string, raw: Record 0; - const createRunner = resolveCreateRunner({ decision, reuseRun, request: record(data.request), runnerJob, runnerAdmission: record(data.runnerAdmission) }); + const createRunner = resolveCreateRunner({ decision, reuseRun, request: record(data.request), runnerJob, runnerAdmission: rawRunnerAdmission }); const sessionId = firstString(data.sessionId, record(run.sessionRef).sessionId, activeBefore.sessionId, reusedIdleRun.sessionId); - const runId = firstString(run.id, data.runId, activeBefore.runId, reusedIdleRun.runId, record(data.runnerAdmission).runId); - const commandId = firstString(commandResource.id, data.commandId, activeBefore.commandId, reusedIdleRun.commandId, record(data.runnerAdmission).commandId); - const runnerJobId = firstString(runnerJob.id, runnerJob.runnerJobId, data.runnerJobId, record(data.runnerAdmission).plannedRunnerJobId, dispatchIntent.runnerJobId); - const dispatchIntentId = firstString(dispatchIntent.id, record(data.runnerAdmission).dispatchIntentId); - const mutation = booleanOrNull(data.mutation) ?? booleanOrNull(raw.mutation); - const partialWrite = booleanOrNull(record(data.runnerAdmission).partialWrite) ?? false; + const runId = firstString(run.id, data.runId, activeBefore.runId, reusedIdleRun.runId, rawRunnerAdmission.runId); + const commandId = firstString(commandResource.id, data.commandId, activeBefore.commandId, reusedIdleRun.commandId, rawRunnerAdmission.commandId); + const dispatchIntentId = firstString(dispatchIntent.id, rawRunnerAdmission.dispatchIntentId); + const plannedRunnerJobId = firstString(rawRunnerAdmission.plannedRunnerJobId, dispatchIntent.runnerJobId, isPlannedRunnerJob(runnerJob) ? runnerJob.runnerJobId : null); + const actualRunnerJobId = firstString( + rawRunnerAdmission.actualRunnerJobId, + dispatchIntent.actualRunnerJobId, + isPlannedRunnerJob(runnerJob) ? null : runnerJob.id, + isPlannedRunnerJob(runnerJob) ? null : runnerJob.runnerJobId, + Object.keys(rawRunnerAdmission).length === 0 ? data.runnerJobId : null, + ); + const mutation = booleanOrNull(data.mutation) ?? booleanOrNull(raw.mutation) ?? booleanOrNull(rawRunnerAdmission.mutation); + const partialWrite = booleanOrNull(rawRunnerAdmission.partialWrite) ?? false; + const recoveryActions = mergeActionSources(rawRunnerAdmission.recoveryActions, data.pollActions); const nextActions = dryRun ? [{ command: sessionSendConfirmCommand(command, sessionId), mutation: true, reason: "复用同一 prompt stdin,仅在确认计划后提交一次。", }] - : readOnlyNextActions({ sessionId, runId, commandId, runnerJobId }, data.pollActions); + : readOnlyNextActions({ sessionId, runId, commandId, runnerJobId: actualRunnerJobId }, recoveryActions); return { kind: dryRun ? "SessionSendPlan" : "SessionSendResult", ok: raw.ok !== false, @@ -85,7 +94,8 @@ export function sessionSendSuccessProjection(command: string, raw: Record): Sessio const commandResource = firstRecord(record(resources.command), record(resourceIdentities.command), record(details.command)); const runnerJob = firstRecord(record(resources.runnerJob), record(resourceIdentities.runnerJob), record(details.runnerJob)); const dispatchIntent = firstRecord(record(resources.dispatchIntent), record(resourceIdentities.dispatchIntent), record(commandResource.dispatchIntent), record(details.dispatchIntent)); - const runnerAdmission = compactRunnerAdmission(firstRecord(record(details.runnerAdmission), record(agentrun.runnerAdmission))); + const rawRunnerAdmission = firstRecord(record(details.runnerAdmission), record(agentrun.runnerAdmission)); + const runnerAdmission = compactRunnerAdmission(rawRunnerAdmission, dispatchIntent); const sessionId = firstString(details.sessionId, session.id, session.sessionId, record(runnerAdmission).sessionId); const runId = firstString(details.runId, run.id, run.runId, record(runnerAdmission).runId); const commandId = firstString(details.commandId, commandResource.id, commandResource.commandId, record(runnerAdmission).commandId); - const runnerJobId = firstString(details.runnerJobId, runnerJob.id, runnerJob.runnerJobId, record(runnerAdmission).plannedRunnerJobId, dispatchIntent.runnerJobId); - const dispatchIntentId = firstString(details.dispatchIntentId, dispatchIntent.id, record(runnerAdmission).dispatchIntentId); + const plannedRunnerJobId = firstString(rawRunnerAdmission.plannedRunnerJobId, dispatchIntent.runnerJobId, isPlannedRunnerJob(runnerJob) ? runnerJob.runnerJobId : null); + const actualRunnerJobId = firstString( + rawRunnerAdmission.actualRunnerJobId, + dispatchIntent.actualRunnerJobId, + isPlannedRunnerJob(runnerJob) ? null : details.runnerJobId, + isPlannedRunnerJob(runnerJob) ? null : runnerJob.id, + isPlannedRunnerJob(runnerJob) ? null : runnerJob.runnerJobId, + ); + const dispatchIntentId = firstString(details.dispatchIntentId, dispatchIntent.id, rawRunnerAdmission.dispatchIntentId); const typedFailureKind = firstString(nestedError.failureKind, details.failureKind, agentrun.failureKind); const code = firstString(details.code, nestedError.code, agentrun.code); const reason = firstString(details.reason, details.degradedReason, details.changeReason, record(runnerAdmission).reason); @@ -138,8 +158,26 @@ export function sessionSendErrorProjection(raw: Record): Sessio : Array.isArray(agentrun.recoveryActions) ? agentrun.recoveryActions : []; - const nextActions = readOnlyNextActions({ sessionId, runId, commandId, runnerJobId }, recoveryActions); + const nextActions = readOnlyNextActions({ sessionId, runId, commandId, runnerJobId: actualRunnerJobId }, mergeActionSources(rawRunnerAdmission.recoveryActions, recoveryActions)); const message = truncateOneLine(firstString(raw.message, nestedError.message, agentrun.message) ?? "AgentRun session send failed", 400); + const errorRetryable = firstBoolean(details.retryable, nestedError.retryable, agentrun.retryable, record(runnerAdmission).retryable); + const errorRetryAuthority = firstString(details.retryAuthority, nestedError.retryAuthority, agentrun.retryAuthority, record(runnerAdmission).retryAuthority); + const errorRecoveryAction = firstString(details.recoveryAction, nestedError.recoveryAction, agentrun.recoveryAction, record(runnerAdmission).recoveryAction); + const errorProjection: Record = { + failureKind: stringOrNull(raw.failureKind), + typedFailureKind, + code, + reason, + message, + httpStatus: numberOrNull(raw.httpStatus), + traceId: firstString(details.traceId, agentrun.traceId), + phase: firstString(details.phase, details.stage), + durable: firstBoolean(details.durable, record(runnerAdmission).durable), + valuesPrinted: false, + }; + if (errorRetryable !== null) errorProjection.retryable = errorRetryable; + if (errorRetryAuthority !== null) errorProjection.retryAuthority = errorRetryAuthority; + if (errorRecoveryAction !== null) errorProjection.recoveryAction = errorRecoveryAction; return { kind: "SessionSendError", ok: false, @@ -155,28 +193,20 @@ export function sessionSendErrorProjection(raw: Record): Sessio session: resourceIdentity("session", sessionId, session), run: resourceIdentity("run", runId, run), command: resourceIdentity("command", commandId, commandResource), - runnerJob: resourceIdentity("runnerjob", runnerJobId, runnerJob), + runnerJob: resourceIdentity("runnerjob", actualRunnerJobId, isPlannedRunnerJob(runnerJob) ? {} : runnerJob), + plannedRunnerJob: plannedRunnerIdentity(plannedRunnerJobId, rawRunnerAdmission), dispatchIntent: resourceIdentity("dispatch-intent", dispatchIntentId, dispatchIntent), }), runnerAdmission, request: null, - error: { - failureKind: stringOrNull(raw.failureKind), - typedFailureKind, - code, - reason, - message, - httpStatus: numberOrNull(raw.httpStatus), - traceId: firstString(details.traceId, agentrun.traceId), - phase: firstString(details.phase, details.stage), - durable: firstBoolean(details.durable, record(runnerAdmission).durable), - valuesPrinted: false, - }, + error: errorProjection, next: { actions: nextActions, note: partialWrite === true ? "manager 已报告 partial write;先按资源 identity 只读核对 durable state,禁止盲目重发、客户端清理或另建 run。" - : "先按资源 identity 核对 manager durable state;只有 manager 明确 fail-closed 或给出可重试合同后才能重发。", + : runnerAdmission === null + ? "先按资源 identity 核对 manager durable state;只有 manager 明确 fail-closed 或给出可重试合同后才能重发。" + : "plannedRunnerJobId 不是 Kubernetes Job 创建证据;先按只读入口核对 durable state,仅在 manager 明确给出可重试合同后重发。", }, valuesPrinted: false, }; @@ -193,6 +223,9 @@ function renderSessionSendHuman(command: string, projection: SessionSendProjecti if (error.reason !== null) lines.push(`Reason: ${display(error.reason)}`); lines.push(`Message: ${display(error.message)}`); if (error.httpStatus !== null) lines.push(`HTTP: ${display(error.httpStatus)}`); + if (error.retryable !== null && error.retryable !== undefined) lines.push(`Retryable: ${display(error.retryable)}`); + if (error.retryAuthority !== null && error.retryAuthority !== undefined) lines.push(`RetryAuthority: ${display(error.retryAuthority)}`); + if (error.recoveryAction !== null && error.recoveryAction !== undefined) lines.push(`RecoveryAction: ${display(error.recoveryAction)}`); } else { lines.push(projection.kind === "SessionSendPlan" ? "Session send plan" : "Session send submitted"); lines.push(`OK: ${String(projection.ok)}`); @@ -217,7 +250,17 @@ function renderSessionSendHuman(command: string, projection: SessionSendProjecti if (projection.runnerAdmission !== null) { const admission = projection.runnerAdmission; lines.push("", "RunnerAdmission:"); - lines.push(` mode=${display(admission.mode)} state=${display(admission.state)} reason=${display(admission.reason)} durable=${display(admission.durable)}`); + lines.push(` mode=${display(admission.mode)} state=${display(admission.state)} disposition=${display(admission.disposition)} reason=${display(admission.reason)} durable=${display(admission.durable)}`); + lines.push(` runId=${display(admission.runId)} commandId=${display(admission.commandId)} dispatchIntentId=${display(admission.dispatchIntentId)}`); + lines.push(` plannedRunnerJobId=${display(admission.plannedRunnerJobId)} actualRunnerJobId=${display(admission.actualRunnerJobId)}`); + lines.push(` mutation=${display(admission.mutation)} partialWrite=${display(admission.partialWrite)} recoveredPriorPartialWrite=${display(admission.recoveredPriorPartialWrite)}`); + lines.push(` retryable=${display(admission.retryable)} retryAuthority=${display(admission.retryAuthority)} willRetry=${display(admission.willRetry)} recoveryAction=${display(admission.recoveryAction)}`); + if (admission.plannedRunnerJobId !== null && admission.plannedRunnerJobId !== undefined) { + lines.push(" Semantics: plannedRunnerJobId 是 durable planned identity;它本身不是 Kubernetes Job 已创建的证据。"); + } + if (admission.retryable === true && admission.retryAuthority === "dispatcher") { + lines.push(" Retry: durable dispatcher 负责自动重试;这不是重复执行 session send 的授权。"); + } } if (projection.next.actions.length > 0 || projection.next.note !== null) { lines.push("", "Next:"); @@ -228,13 +271,15 @@ function renderSessionSendHuman(command: string, projection: SessionSendProjecti } function compactResources(resources: Record | null>): Record { - return { + const result: Record = { session: resources.session, run: resources.run, command: resources.command, runnerJob: resources.runnerJob, dispatchIntent: resources.dispatchIntent, }; + if (resources.plannedRunnerJob !== null && resources.plannedRunnerJob !== undefined) result.plannedRunnerJob = resources.plannedRunnerJob; + return result; } function resourceIdentity(kind: string, id: string | null, value: Record): Record | null { @@ -253,17 +298,43 @@ function resourceIdentity(kind: string, id: string | null, value: Record): Record | null { +function compactRunnerAdmission(value: Record, dispatchIntent: Record = {}): Record | null { if (Object.keys(value).length === 0) return null; const result: Record = {}; - for (const key of ["mode", "state", "reason", "failureKind", "sessionId", "runId", "commandId", "dispatchIntentId", "plannedRunnerJobId", "mutation", "partialWrite", "durable"] as const) { + for (const key of ["mode", "state", "reason", "disposition", "failureKind", "sessionId", "runId", "commandId", "dispatchIntentId", "plannedRunnerJobId", "actualRunnerJobId", "attemptCount", "mutation", "partialWrite", "recoveredPriorPartialWrite", "durable", "retryable", "retryAuthority", "willRetry", "retryAfterMs", "recoveryAction"] as const) { const field = value[key]; if (typeof field === "string" || typeof field === "number" || typeof field === "boolean" || field === null) result[key] = typeof field === "string" ? truncateOneLine(field, 240) : field; } + const actualRunnerJobId = firstString(value.actualRunnerJobId, dispatchIntent.actualRunnerJobId); + if (actualRunnerJobId !== null) result.actualRunnerJobId = actualRunnerJobId; result.valuesPrinted = false; return result; } +function plannedRunnerIdentity(id: string | null, admission: Record): Record | null { + if (id === null) return null; + const result: Record = { + kind: "planned-runnerjob", + id, + identityRole: "durable-planned", + kubernetesJobCreated: null, + kubernetesJobCreationEvidence: "not-asserted", + }; + const state = admission.state; + if (typeof state === "string" || typeof state === "number" || typeof state === "boolean") result.state = state; + const durable = admission.durable; + if (typeof durable === "boolean") result.durable = durable; + return result; +} + +function isPlannedRunnerJob(value: Record): boolean { + return value.action === "runner-dispatch-admitted"; +} + +function mergeActionSources(...values: unknown[]): unknown[] { + return values.flatMap((value) => Array.isArray(value) ? value.slice(0, 12) : []).slice(0, 12); +} + function compactRequest(value: Record): Record | null { if (Object.keys(value).length === 0) return null; const result: Record = {};