/** * [ref] 件二+件三 —— **装配自证**域:启动期静态自检(拒启判据)与 operator 诊断读面 * (`GET /v1/diagnostics/wiring`)。 * * 🔴 两件为什么同住一个模块:它们回答的是同一个问题(「这台 worker 到底接了什么线」),只是一个在 boot * 用它拒绝启动、一个在运行期把它读给 operator。分成两处 = 拒启用的判据与诊断报的读数会各自演化,而 * 「拒启说没矛盾、诊断页显示矛盾」正是本设计要消灭的那类装配谎言。故 boot 半场是本文件导出的**纯函数** * (可单测、无 I/O),路由半场只是它的读口。 * * 分层:本模块不值 import `server.ts`(那条边会闭合运行时装载环),对它只有 `import type`。 */ import { type PostureKnobReading, type PostureSource } from "../../posture-source.js"; import type { IncomingMessage, ServerResponse } from "node:http"; import type { WiringManifest } from "@sema-agent/core"; import { type StreamApprovalGate, type StreamApprovalGateInput } from "../../tool-approval.js"; import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js"; /** 流内审批门的读数形:上场即 `"active"`,否则是那**四**个合取项里**第一个**不满足的原因词 * (与 `resolveStreamApprovalGate` 同一闭集,词表**取型**自它 ⇒ 增删 reason 在此处自动跟随/编译红; * S-384 删掉第 5 项「账必须持久」时本行数字漏改过一次,**数字是手抄的那一半**)。 */ export type StreamApprovalGateReading = "active" | Extract["reason"]; /** server 自己的装配谓词读数(core 的静态半场之外的那一半:本仓在这台 worker 上做出的判断)。 * * 🔴 S-178:三个 **posture 家族**旋钮改报 `{value, source, note}`(与 S-167 的 `readFace` 同一张四词表、 * 同一只 note 函数)。裸布尔答不出裁定的补偿项要求的那句话 —— 单机形升级后 `durableApproval` 缺席即 * ON、审批窗缺席即 24h,运维必须在一个读面上看得见「这是 posture 派生的」以及「怎么钉回」。 * `streamApproval` 那一格是**门读数**(另一根轴:这条腿现在通不通),不是旋钮,故形不变。 */ export interface ServerWiringGates { streamApproval: StreamApprovalGateReading; durableApproval: PostureKnobReading; streamAskWindowMs: PostureKnobReading; sessionAutoTitle: PostureKnobReading; checkpointStore: boolean; } /** `buildServerWiringGates` 的入参 = 三个谓词各自的**真输入**(不接受已算好的布尔:那样这里读到的就 * 不再是装配现场的事实,而是别处算完可能已漂的复述)。 */ export interface ServerWiringGateInput { toolApprovalEnabled: boolean; streamApprovalEnabled: boolean; /** 谓词自己的入参类型(结构形,不 import store 依赖树)—— 取型而非重述,两处不可能漂。 */ backend: StreamApprovalGateInput["backend"]; /** 在场性即事实;本面不取实例,故 `unknown` 足够(取窄型会把整棵 store 类型树拖进诊断域)。 */ checkpointStore: unknown; durableApproval: boolean; /** S-178:三个 posture 旋钮的**来源**,取自 config 上与值同处写的那一格(不在这里重判)。 */ durableApprovalSource: PostureSource; streamAskWindowMs: number; streamAskWindowMsSource: PostureSource; sessionAutoTitle: boolean; sessionAutoTitleSource: PostureSource; } /** 纯数据产物(`build*`):三个门的当前读数。`streamApproval` 走**单一谓词** * `resolveStreamApprovalGate` —— 与 `/v1/capabilities` 的 `streamApproval` 位、与回决口的 501 门同一个 * 符号,所以诊断页读到的与消费端撞到的行为不可能不同。 */ export declare function buildServerWiringGates(input: ServerWiringGateInput): ServerWiringGates; /** 启动自检的判决:`refusals` 非空 ⇒ 拒启(装配谎言);`warnings` ⇒ 留痕但放行。 */ export interface StaticWiringAudit { refusals: readonly string[]; warnings: readonly string[]; } /** 自检输入 = core 的静态半场 + 我方装配现场的两件事实。 */ export interface StaticWiringFacts { /** core `describeStaticWiring(deps, specTemplate)` 的产物。 */ manifest: WiringManifest; /** 我方喂给 `resolveStreamApprovalGate` 的 `parkFacility` 实参(= checkpoint 店在场 ∧ DURABLE_APPROVAL)。 */ parkFacility: boolean; /** store backend 的种类;`undefined` = env-only worker(无 backend)。 */ backendKind: "mysql" | "pg" | "local" | undefined; } /** * 三项对表(设计稿件二)。判据全是**两个独立谓词对同一件事的答案**,矛盾即装配谎言 —— 装配谎言不是 * 「配置不理想」,是「服务端对自己接了什么线的两套说法互斥」,继续启动只会把矛盾带进运行期的每一条腿。 * * ① park 设施:core 从 `spec.checkpointStore ?? deps.checkpointStore` 判 `parkLane.capable`,我方从 * `backend.checkpoint ∧ DURABLE_APPROVAL` 判 `parkFacility` 并据此决定流内审批协议上不上场。两者 * 不等 ⇒ 协议按「窗到期能 park」上场而引擎侧根本不 park(结局是 core fail-closed deny),或反之 * 白白关掉一个可用的协议。`capable` 真而 `effective` 不真同样致命:能力在、这条部署形上却不会真的 * park,窗到期依旧无降级目的地。 * ② 会话店持久性:manifest 声明 `declared_durable` 而后端是 local / 根本没有后端 ⇒ 「重启存活」的声明 * 没有介质兑现。这条与 memory+durable 两条既有拒启同族(声明与介质必须对得上)。 * ③ interaction posture 声明 `interactive` 却没有任何 question 投递面 ⇒ 只 **warn**:core 在 prepare 时 * 会按腿拒,boot 先一步把话说成人话。不拒启,因为部署形本身可能只是暂时没接活体面,而 core 侧已有 * 兜底门 —— 拒启会把一个可运行的部署挡在门外。 * ④ park 车道的持久性读数不可信时 ⇒ **warn**(理由与这条判据为什么不是拒启,见其行内注)。 * * 判决分两级是有判据的:**拒启**留给「两个谓词对同一件事互斥」(其中一个必错,继续跑就是带着谎言服务); * **警告**留给「读数不可信/声明不完整」(信号本身还不足以断定谁错,据此拒启会挡住正常部署)。 */ export declare function buildStaticWiringAudit(facts: StaticWiringFacts): StaticWiringAudit; /** * boot 消费口:`refusals` 非空即抛(拒启,与 memory+durable 两条既有拒启同族的 fail-loud 形), * `warnings` 逐条 warn。返回判决本体,调用方可以继续把读数打进启动 info 行。 */ export declare function assertStaticWiringConsistent(facts: StaticWiringFacts, logger: { warn: (msg: string, meta?: Record) => void; } | undefined): StaticWiringAudit; /** * [ref]③ clay 裁 (b)(2026-08-12,core [ref]④ + cli [ref]① 双背书):**零凭证 turnkey 形**的 * loopback 来源门判据(单一属主,端点与测试同吃)。 * * 只在「operator 名单空 ∧ 非多租户 ∧ 全局 service-credential 门也不在场(没配 SERVICE_AUTH_TOKEN / * AUTH_TOKENS)」的部署形上判——那正是全局门整个不设、诊断端点此前无认证可读的形。诊断面泄的是装配 * 拓扑(store 接线/能力位/治理姿态),对误暴露公网端口的 turnkey 机器构成侦察面。(b) 对本机自用零摩擦 * (壳缺省钉 BIND_HOST=127.0.0.1,local curl 照常),对远程侦察 fail-closed;配了 token 的部署远程读 * 走认证(全局门),不进本臂。 * * 判源=TCP 对端地址(`req.socket.remoteAddress`),**非任何头**——XFF 类头在无反代的 turnkey 形上本就 * 是自造面;有反代的部署必然配 token(反代后 remoteAddress 恒 loopback,门自然放行,语义仍对:该形的 * 边界由反代+token 承担)。`remoteAddress` 缺席(socket 已断等边缘)⇒ 判拒(fail-closed,安全轴方向)。 */ export declare function zeroCredentialLoopbackDenied(remoteAddress: string | undefined, config: { authToken?: string; authTokens?: Record; }): boolean; export declare function handleDiagnostics(req: IncomingMessage, res: ServerResponse, match: RouteMatch, ctx: RouteCtx): Promise; export declare const DIAGNOSTICS_ROUTES: readonly [{ readonly id: "diagnostics-wiring"; readonly path: "/v1/diagnostics/wiring"; readonly methods: readonly ["GET"]; }]; /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */ export type DiagnosticsRouteId = RouteIdsOf; //# sourceMappingURL=diagnostics.d.ts.map