import type { IncomingMessage, ServerResponse } from "node:http"; import type { QuestionAnswer } from "@sema-agent/core"; import type { CheckpointStoreFull } from "../../plugins/store-backend.js"; import { type ToolApprovalCoordinator } from "../../tool-approval.js"; import { type GovernancePosture } from "../active-run-conflict.js"; import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js"; import { type KeyClosureIssue } from "../../request-key-closure.js"; /** * FRESH 决裁面的**顶层**键闭集裁决:受理集之外的键 ⇒ 返回拒体(调用方 400 `request.body_shape`); * 全部受理 ⇒ `null`。判据边界与孪生门(`taskBodyKeyIssue` / `taskSettingsKeyIssue`)逐字同一条: * **只判键名**(值形校验归各自既有的门,本门零抢话);体本身非对象 ⇒ 本门不判(留给「body must be * { decision … }」那道根形门,免得同一个体被两句话抢答)。 * * 🔴 **重放不额外过门**,也不需要:本腿的幂等回放(`decideIdempotentReplay`)重放的是**同一次请求的同一份 * 体**,它与首决走同一条路由、同一道门;而 checkpoint 的 resume 重建腿根本不读这个体(它读盘上的 body, * 走 `resolveSpec` 的宽容层)⇒ 4xx 砖不到任何存量 parked 任务。这与 tasks 面「RESUME 重放不过门」是同一条律 * 的两种兑现方式:**门只站在人**(或客户端)**现写的那份字节**上。 */ export declare function decideBodyKeyIssue(raw: unknown): KeyClosureIssue | null; /** * FRESH 决裁面的**顶层**键闭集裁决:受理集之外的键 ⇒ 返回拒体(调用方 400 `request.body_shape`); * 全部受理 ⇒ `null`。判据边界与三个孪生门(`taskBodyKeyIssue` / `taskSettingsKeyIssue` / * {@link decideBodyKeyIssue})逐字同一条:**只判键名**(值形校验归各自既有的门,本门零抢话);体本身 * 非对象 ⇒ 本门不判(留给「body must be { decision … }」那道根形门,免得同一个体被两句话抢答)。 * * 🔴 **重放不额外过门**,也不需要 —— 逐字同 {@link decideBodyKeyIssue} 的同名段:本腿的反重放是 * 一次性 nonce(S-280),它守的是**同一份现写的字节**;而续跑重建读的是盘上的体、走 `resolveSpec` 的 * 宽容层 ⇒ 4xx 砖不到任何存量挂起的任务。**门只站在人(或客户端)现写的那份字节上**。 */ export declare function planReviewBodyKeyIssue(raw: unknown): KeyClosureIssue | null; /** * S-342 —— 「空串的理由 = 没有理由 = 这是一次**裸拒**」。这条语义只适用于**本仓自铸**的那一个域。 * * ## 三个域,三个属主(这才是归一:每个域的读法只写一次,而不是三处各挑一种写法) * `body.reason` 在这条路由上会被读三次,而它们的属主**不是同一个**: * · **签名域**(直连门的 HMAC 信封第五位)—— 属主是**跨仓字节契约**(§6.2 / `approvalHmacMessage`, * 签名器在 cli / SDK)。那份契约明写空串与 `null` 是两个不同的被签值。⇒ **逐字线值,永不折叠**; * 在这里折一刀,一份合法签发的空理由证明就永远 401(合并复审 R1 [high] 真复现过)。 * · **上送域**(`resumePlanReview` / `resumeCheckpoint` 的 `reason` 尾参)—— 属主是**引擎的单铸点** * (7.19.0 / [ref] 把「人的结算」收进一个铸点,空文本在那里折成缺席,live 与 durable decide 两条路径同形)。 * ⇒ **原样透传**;跨仓宪法「源头修复禁下游旁路」:在下游再折一次就是同一语义的第二个写者, * 今天两者答案相同,上游哪天改法就变成一条谁都查不出来的分歧。 * · **本仓自铸域**(幂等回放的**身份**)—— 属主就是本仓:它回答「这次重试与首决是不是同一个决议」。 * ⇒ **本函数**。此前那里是一个裸真值判(`body.reason ? …`),与上面两处混在一起看就是三种拼法; * 收进一只具名读法之后,「什么算一个理由」在本仓只有这一个答案,而另外两个域各自点名了自己的属主。 * * 值域前置门(非串 400 / 超长 413)在两条路由里各自先跑,本函数只做「空串 ⇒ 缺席」这一件事。 * 纯空白**仍是**一条理由(上游把这一形逐字钉成 current behaviour,本仓跟着它、不自作主张多折一层)。 */ export declare function decisionReasonOf(body: { reason?: unknown; }): string | undefined; /** {@link checkQuestionAnswer} 的判词。两种拒各配**一个既有的**错误码,不铸第三种。 */ export type QuestionAnswerCheck = { readonly ok: true; readonly value: QuestionAnswer; } /** 形不对(不是对象 / `answers` 空 / 某一位的值形不对)⇒ 400 `request.body_shape`,文案逐字不变。 */ | { readonly ok: false; readonly kind: "shape"; } /** 词表外的键 ⇒ 400 `request.field_invalid`,**点名键路径**(`answer.answers[0].selection`)。 */ | { readonly ok: false; readonly kind: "unknown_key"; readonly keyPaths: readonly string[]; }; /** * Structural check for an operator-supplied QuestionAnswer (durable ask resume, TC-5.4). Strict on the * load-bearing shape — `answers[]` non-empty, each `{header: string, selected: string[], note?: string}` — * so a typo'd payload fails the request instead of resuming the task with an answer the tool can't use. * S-452 起**同时闭键**:词表外的键(拼错 / 未来键 / `__proto__`)拒不丢。 */ export declare function checkQuestionAnswer(v: unknown): QuestionAnswerCheck; /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;` * = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */ export declare function handleApprovalsAssistant(req: IncomingMessage, res: ServerResponse, match: RouteMatch, ctx: RouteCtx): Promise; export declare function streamApprovals(req: IncomingMessage, res: ServerResponse, cs: Pick, /** * 🔴 S-454:**第二条 diff 源** —— `GET /v1/approvals` 的 `livePending` 那**同一只**读函数 * (`Pick` 而不是另立接口:同源是结构事实,不是两处各写一份的约定)。 * * 为什么必须同源:消费方(client-core `hitl/approvalsFeed`)对 stream 上任一非 heartbeat 事件的 * 反应是**重取权威 `list()`** —— 推与拉读两份数据时,一条只在推面存在的行会让 feed 空转, * 一条只在拉面存在的行则永远等不到催人事件。 * * `undefined` = 本部署无 live 审批面(`TOOL_APPROVAL_ENABLED=false` ⇒ 协调器未装配)⇒ 本源整个 * 缺席,流的字节与 S-454 之前逐字相同(能力缺席 = 零变化,不是空数组)。**位置在 `pollMs` 之前** * 与 `governance` 同一条理由:`pollMs` 只有测试传,而本参数是每个生产调用点都必须表态的那一个。 */ live: Pick | undefined, scope: string | undefined, /** [ref]②:出身归因的部署侧一半(缺席 ⇒ 归不出治理出身 ⇒ 键缺席,与 best-effort 同方向)。**位置在 * `pollMs` 之前**是刻意的:`pollMs` 只有测试传,而本参数是每个生产调用点都必须给的那一个。 */ governance?: GovernancePosture, pollMs?: number): Promise; export declare const APPROVALS_ASSISTANT_ROUTES: readonly [{ readonly id: "approvals-list"; readonly path: "/v1/approvals"; readonly methods: readonly ["GET"]; }, { readonly id: "approvals-stream"; readonly path: "/v1/approvals/stream"; readonly methods: readonly ["GET"]; }, { readonly id: "assistant-inbox"; readonly path: "/v1/assistant/inbox"; readonly methods: readonly ["GET"]; }, { readonly id: "assistant-tasks"; readonly path: "/v1/assistant/tasks"; readonly methods: readonly ["GET"]; }, { readonly id: "approval-decide"; readonly pattern: RegExp; readonly label: "/v1/approvals/:id/decide"; readonly methods: readonly ["POST"]; }, { readonly id: "approval-exemptions-list"; readonly pattern: RegExp; readonly label: "/v1/approvals/:id/exemptions"; readonly methods: readonly ["GET"]; }, { readonly id: "approval-exemption-delete"; readonly pattern: RegExp; readonly label: "/v1/approvals/:id/exemptions"; readonly methods: readonly ["DELETE"]; }, { readonly id: "assistant-task-preempt"; readonly pattern: RegExp; readonly label: "/v1/assistant/tasks/:id/preempt"; readonly methods: readonly ["POST"]; }, { readonly id: "assistant-task-resume"; readonly pattern: RegExp; readonly label: "/v1/assistant/tasks/:id/resume"; readonly billable: true; readonly methods: readonly ["POST"]; }, { readonly id: "assistant-task-plan-review"; readonly pattern: RegExp; readonly label: "/v1/assistant/tasks/:id/plan_review"; readonly billable: true; readonly methods: readonly ["POST"]; }, { readonly prefix: "/v1/approvals"; readonly billable: true; }]; /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */ export type ApprovalsAssistantRouteId = RouteIdsOf; //# sourceMappingURL=approvals-assistant.d.ts.map