/** * Package-owned shared infrastructure-failure declaration for every primary * packaged-role output tool (#541). * * One module owns what the judge mandates be shared (not reimplemented per * seat): the typed `infrastructureFailure.diagnostic` declaration composed into * each output tool's schema, and the single early host `failInfrastructure` * call each output `execute` makes before any role business validation / gate / * audit / ledger / Git work. The accepted status sets are intentionally NOT * extended: an infra declaration fails BEFORE accepted validation, never * becomes an accepted receipt. The diagnostic is carried verbatim on the thrown * Error so settlement keeps the original cause (kind=failure, exit 1). */ import { Type, type TSchema } from "typebox"; import type { CorrectableSubmissionError } from "../submission-correctable-error.ts"; import { isRecord } from "../unknown-value.ts"; export const INFRASTRUCTURE_FAILURE_DECLARATION_KEY = "infrastructureFailure" as const; export const INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY = "diagnostic" as const; /** * Shared declaration fragment for model guidance (#541 / #676 C / ADR 0057). * Nested field declarations + descriptions only — host must not pure-shape-reject * the envelope (仓内 CLAUDE.md 开篇). Runtime `failOnInfrastructureFailureDeclaration` still * recognizes a real non-empty diagnostic string as the failure declaration. * No required/minLength/type host gates on the declaration fragment. */ const infrastructureFailureNested = Type.Object( { [INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]: Type.Unknown({ description: "基础设施失败诊断字符串。", }), }, { additionalProperties: true, description: "仅基础设施真实失败时出现。", }, ); // Open nested required so host cannot pure-shape-reject the declaration fragment. (infrastructureFailureNested as unknown as { required: string[] }).required = []; const infrastructureFailureDeclarationSchema = Type.Object( { [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureNested, }, { additionalProperties: true }, ); /** * Compose declarations shared by terminating output tools: infrastructure failure * and the optional role-asserted ticket identity. Returns an open object (additionalProperties: true) * with the base schema's properties plus the shared declaration. Incoming required * keys that still exist are kept; every other key stays optional. * Static typing is preserved on the base (`as S`), so existing * `Static` derived parameter types are unchanged. */ export function withTerminatingOutputDeclarations< S extends TSchema & { properties?: Record }, >(schema: S): S { const baseProperties = (schema as { properties?: Record }) .properties; const properties: Record = { ...(baseProperties ?? {}), ...( baseProperties?.ticketNumber === undefined ? { ticketNumber: Type.Unknown({ description: "可选本票号。尚未绑定时由角色在既有回执中申报(正整数、数字串或前导 #N;归位只读本字段)。", }), } : {} ), [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureDeclarationSchema.properties[ INFRASTRUCTURE_FAILURE_DECLARATION_KEY ], }; const object = Type.Object(properties, { additionalProperties: true }); const incoming = (schema as { required?: unknown }).required; const preserved = Array.isArray(incoming) ? incoming.filter((key): key is string => typeof key === "string" && Object.hasOwn(properties, key)) : []; (object as unknown as { required: string[] }).required = preserved; return object as unknown as S; } /** Structural host seam subset shared by every terminating execute path. */ type TerminatingInfrastructureHostActions = { failInfrastructure(error: unknown, ctx: C, toolCallId?: string): never; }; /** Safe recognition of the typed declaration; non-shapes / hostile input fail closed. */ function isInfrastructureFailureDeclaration( parameters: unknown, ): boolean { if (!isRecord(parameters)) return false; if (!Object.hasOwn(parameters, INFRASTRUCTURE_FAILURE_DECLARATION_KEY)) return false; const declaration = parameters[INFRASTRUCTURE_FAILURE_DECLARATION_KEY]; if (!isRecord(declaration)) return false; const diagnostic = declaration[INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]; return typeof diagnostic === "string" && diagnostic.trim().length > 0; } /** Non-empty trimmed diagnostic from the declaration, else undefined. */ export function infrastructureFailureDiagnostic( parameters: unknown, ): string | undefined { if (!isInfrastructureFailureDeclaration(parameters)) return undefined; const declaration = (parameters as Record)[ INFRASTRUCTURE_FAILURE_DECLARATION_KEY ] as Record; const diagnostic = declaration[INFRASTRUCTURE_FAILURE_DIAGNOSTIC_KEY]; return typeof diagnostic === "string" ? diagnostic.trim() : undefined; } /** diagnostic → Error, name stamped so the host error identity is observable. */ function infrastructureFailureError(diagnostic: string): Error { const error = new Error(diagnostic); error.name = "InfrastructureFailure"; return error; } /** * The one early call for every terminating output `execute`: if the parameters * carry the infra declaration, hand the diagnostic error to the shared host * `failInfrastructure` seam (which aborts the run). No-op otherwise. * * #641 chain②: an optional seat-owned bounce hook may intercept the * declaration first — when the seat can machine-verify a lawful normal * completion, it returns a correctable error and the declaration is treated as * model misuse (交件契约封驳) instead of a host failure. Returning undefined * keeps the shared failure path. */ export function failOnInfrastructureFailureDeclaration( parameters: unknown, hostActions: TerminatingInfrastructureHostActions, ctx: C, toolCallId: string, bounceInfrastructureDeclaration?: (params: unknown, toolCallId: string, ctx: C) => CorrectableSubmissionError | undefined, ): void { const diagnostic = infrastructureFailureDiagnostic(parameters); if (diagnostic === undefined) return; if (bounceInfrastructureDeclaration !== undefined) { const bounce = bounceInfrastructureDeclaration(parameters, toolCallId, ctx); if (bounce !== undefined) throw bounce; } hostActions.failInfrastructure( infrastructureFailureError(diagnostic), ctx, toolCallId, ); }