import { createHash } from "node:crypto"; import { existsSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; import type { RepoSnapshot } from "../context/index.ts"; import { DIAGNOSTIC_ADVICE_SCHEMA_VERSION } from "../diagnostics/contract.ts"; import type { NormalizedPolicy, PolicyDecision, PolicyIsolation, PolicyNetworkAccess, } from "../policy/index.ts"; import { policyDigest } from "../policy/index.ts"; import { isCanonicalWorkflowAttemptContext } from "./attempt-context.ts"; import { stableDigest, writeImmutableJson } from "./durable-record.ts"; import { readWorkflowRunManifest } from "./run-state.ts"; import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, AdapterAttestationCitation, AdapterEvidenceCapability, AdapterEvidenceCoverage, ResultDigest, RunFailureClass, SpawnFailureClass, WorkflowAgentProof, WorkflowAttemptContext, WorkflowDiagnosticAdmissionProof, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowSandboxProjectionEvidence, WorkflowWorkContext, } from "./types.ts"; import { EVIDENCE_KINDS, WORKFLOW_DIAGNOSTIC_ADMISSION_SCHEMA_VERSION, WORKFLOW_PROOF_SCHEMA_VERSION, } from "./types.ts"; import { isCanonicalWorkflowWorkContext } from "./work-context.ts"; import type { WorkspaceAttestation, WorkspaceBinding, WorkspaceCompatibilityExecutionEvidence, WorkspaceExecutionEvidence, } from "./workspaces/index.ts"; import { isWorkspaceBoundExecutionEvidence, isWorkspaceCompatibilityExecutionEvidence, isWorkspaceExecutionEvidence, workspaceProofLifecycle, } from "./workspaces/validate.ts"; const MAX_ACCEPTANCE_CRITERIA = 50; const MAX_EVIDENCE_RECORDS = 200; const MAX_PACKET_BYTES = 512 * 1024; const MAX_NAME_CHARS = 200; const MAX_OBJECTIVE_CHARS = 2_000; const MAX_CRITERION_CHARS = 500; const MAX_LABEL_CHARS = 200; const MAX_SUMMARY_CHARS = 2_000; const MAX_REF_CHARS = 1_000; const ACCEPTANCE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/; const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/; export interface NormalizedWorkflowMeta { name: string; description?: string; objective?: string; acceptance: AcceptanceCriterion[]; } export interface BuildWorkflowProofInput { runId: string; workItemId?: string; workContext?: Readonly; attemptContext?: Readonly; meta: NormalizedWorkflowMeta; status: "succeeded" | "failed"; startedAt: string; endedAt: string; durationMs: number; transcriptPath: string; before: RepoSnapshot; after: RepoSnapshot; agents: WorkflowAgentProof[]; evidence: WorkflowEvidenceRecord[]; diagnosticAdmission?: WorkflowDiagnosticAdmissionProof; adapterEvidence?: Readonly>; /** Filesystem policy actually projected into children (ADR 0039). */ sandboxProjection?: WorkflowSandboxProjectionEvidence; /** Live attestations backing each adapter's claims (ADR 0038). Injected by * the caller so proof stays free of filesystem lookups. */ adapterAttestations?: Readonly>; policy?: { config: Readonly; decisions: readonly PolicyDecision[]; isolation: PolicyIsolation; networkAccess: PolicyNetworkAccess; }; /** Set when the script called ctx.blocked(): the run stopped because a human * must rule. Forces run class "decision" regardless of the agents' classes. */ blocked?: { reason: string; decisionId?: string }; workspaceBinding?: WorkspaceBinding; workspaceAttestation?: WorkspaceAttestation; workspaceFallback?: WorkspaceCompatibilityExecutionEvidence; result?: unknown; error?: string; } export function normalizeWorkflowMeta( meta: WorkflowMeta | undefined, fallbackName: string, ): NormalizedWorkflowMeta { const name = boundedRequired(meta?.name ?? fallbackName, "workflow name", MAX_NAME_CHARS); const objective = boundedOptional(meta?.objective, "workflow objective", MAX_OBJECTIVE_CHARS); const description = boundedOptional( meta?.description, "workflow description", MAX_OBJECTIVE_CHARS, ); const acceptance = meta?.acceptance ?? []; if (!Array.isArray(acceptance)) throw new Error("workflow acceptance must be an array"); if (acceptance.length > MAX_ACCEPTANCE_CRITERIA) { throw new Error(`workflow acceptance exceeds ${MAX_ACCEPTANCE_CRITERIA} criteria`); } const seen = new Set(); const normalized = acceptance.map((criterion, index) => { if (!criterion || typeof criterion !== "object") { throw new Error(`workflow acceptance[${index}] must be an object`); } const id = boundedRequired(criterion.id, `workflow acceptance[${index}].id`, 64); if (!ACCEPTANCE_ID.test(id)) { throw new Error( `workflow acceptance id ${JSON.stringify(id)} must match ${ACCEPTANCE_ID.source}`, ); } if (seen.has(id)) throw new Error(`duplicate workflow acceptance id ${JSON.stringify(id)}`); seen.add(id); return { id, statement: boundedRequired( criterion.statement, `workflow acceptance[${index}].statement`, MAX_CRITERION_CHARS, ), }; }); return { name, description, objective, acceptance: normalized }; } export function createEvidenceRecord(input: { value: WorkflowEvidenceInput; sequence: number; acceptanceIds: ReadonlySet; stage?: string; recordedAt?: string; }): WorkflowEvidenceRecord { if (input.sequence > MAX_EVIDENCE_RECORDS) { throw new Error(`workflow evidence exceeds ${MAX_EVIDENCE_RECORDS} records`); } const value = input.value; const acceptanceIds = value.acceptanceIds ?? []; if (!Array.isArray(acceptanceIds)) throw new Error("evidence acceptanceIds must be an array"); const uniqueAcceptanceIds = [...new Set(acceptanceIds)]; for (const id of uniqueAcceptanceIds) { if (typeof id !== "string" || !input.acceptanceIds.has(id)) { throw new Error(`evidence references unknown acceptance id ${JSON.stringify(id)}`); } } return { id: `e${input.sequence}`, source: "workflow", recorded_at: input.recordedAt ?? new Date().toISOString(), kind: enumValue(value.kind, EVIDENCE_KINDS, "evidence kind"), status: enumValue(value.status, ["passed", "failed", "observed", "unknown"], "evidence status"), // Truncate rather than throw. A label is a display string; the substance // lives in `summary` and `ref`. Throwing here discards the whole run at // the point evidence is recorded, which is the END of the work — a real // three-agent review was lost because its label ran 31 characters long. // Same posture the transcript writer already takes: shrink and say so. label: truncatedRequired(value.label, "evidence label", MAX_LABEL_CHARS), summary: boundedOptional(value.summary, "evidence summary", MAX_SUMMARY_CHARS), ref: boundedOptional(value.ref, "evidence ref", MAX_REF_CHARS), stage: boundedOptional(input.stage, "evidence stage", MAX_LABEL_CHARS), acceptance_ids: uniqueAcceptanceIds, }; } export function rollupAcceptance( criteria: AcceptanceCriterion[], evidence: WorkflowEvidenceRecord[], ): { criteria: AcceptanceResult[]; summary: AcceptanceSummary } { const results = criteria.map((criterion): AcceptanceResult => { const attached = evidence.filter((item) => item.acceptance_ids.includes(criterion.id)); const failed = attached.filter((item) => item.status === "failed"); const passed = attached.filter((item) => item.status === "passed"); const decisive = failed.length > 0 ? failed : passed; const status = failed.length > 0 ? "unsatisfied" : passed.length > 0 ? "satisfied" : "unknown"; return { ...criterion, status, evidence_ids: attached.map((item) => item.id), sources: [...new Set(decisive.map((item) => item.source))], }; }); return { criteria: results, summary: { satisfied: results.filter((item) => item.status === "satisfied").length, unsatisfied: results.filter((item) => item.status === "unsatisfied").length, unknown: results.filter((item) => item.status === "unknown").length, total: results.length, }, }; } export function digestResult(value: unknown, kind?: "text" | "json"): ResultDigest { const resolvedKind = kind ?? (typeof value === "string" ? "text" : "json"); const serialized = resolvedKind === "text" ? String(value) : (JSON.stringify(value) ?? "null"); return { kind: resolvedKind, sha256: createHash("sha256").update(serialized).digest("hex"), bytes: Buffer.byteLength(serialized), }; } /** * The run-level failure class (ADR 0046), derived from the agents rather than a * single terminal throw so it survives a script's `parallel()` swallowing the * rejection — a swallowed agent's proof is still recorded with its class. * * Rules, in order, all in service of "default to charging": * 1. A succeeded run is never classed (there is nothing uncharged about it). * 2. If ANY agent produced a result (succeeded or replayed from cache) the * attempt was informative about the work — charge it. This also keeps a * resumed run whose earlier segment did real work from being written off by * a later environment failure. * 3. Otherwise, among the failed agents, environment wins over upstream: a * missing binary means nothing ran at all, and it is the operator-chosen * hard stop. * 4. Anything else is undefined ⇒ a charged work failure, exactly as today. */ export function deriveRunFailureClass( status: "succeeded" | "failed", agents: readonly Pick[], ): SpawnFailureClass | undefined { if (status !== "failed") return undefined; if (agents.some((agent) => agent.status === "succeeded" || agent.status === "cached")) { return undefined; } const failed = agents.filter((agent) => agent.status === "failed"); if (failed.some((agent) => agent.class === "environment")) return "environment"; if (failed.some((agent) => agent.class === "upstream")) return "upstream"; return undefined; } export function buildWorkflowProof(input: BuildWorkflowProofInput): WorkflowProof { const acceptance = rollupAcceptance(input.meta.acceptance, input.evidence); const repository = buildRepoEvidence(input.before, input.after); const agents = input.agents.map((agent) => ({ ...agent, label: clipped(agent.label, MAX_LABEL_CHARS), specialist: clippedOptional(agent.specialist, MAX_LABEL_CHARS), model: clippedOptional(agent.model, MAX_LABEL_CHARS), session_id: clippedOptional(agent.session_id, MAX_REF_CHARS), error: clippedOptional(agent.error, MAX_SUMMARY_CHARS), })); const adapters = buildAdapterCoverage(agents, input.adapterEvidence, input.adapterAttestations); const unknowns = buildUnknowns(agents, adapters, repository); // A declared stop-on-human beats a derived spawn class. If the script said a // human must rule, that is the true reason the run ended, even when some agent // also happened to hit a flaky vendor on the way there. const runClass: RunFailureClass | undefined = input.status === "failed" && input.blocked ? "decision" : deriveRunFailureClass(input.status, agents); const transcript = readFileSync(input.transcriptPath); return { schema_version: WORKFLOW_PROOF_SCHEMA_VERSION, run: { id: input.runId, work_item_id: input.workItemId, name: input.meta.name, status: input.status, started_at: input.startedAt, ended_at: input.endedAt, duration_ms: input.durationMs, work_context: input.workContext, attempt_context: input.attemptContext, objective: input.meta.objective, error: clippedOptional(input.error, MAX_SUMMARY_CHARS), result: input.result === undefined ? undefined : digestResult(input.result), ...(runClass ? { class: runClass } : {}), ...(runClass === "decision" && input.blocked?.decisionId ? { decision_id: clipped(input.blocked.decisionId, MAX_REF_CHARS) } : {}), }, acceptance, agents, evidence: input.evidence, policy: input.policy ? buildPolicyProof(input.policy) : undefined, ...(input.diagnosticAdmission ? { diagnostic_admission: input.diagnosticAdmission } : {}), execution: input.workspaceBinding && input.workspaceAttestation ? buildExecutionEvidence(input.status, input.workspaceBinding, input.workspaceAttestation) : input.workspaceFallback, repository, ...(input.sandboxProjection ? { sandbox_projection: input.sandboxProjection } : {}), adapters, unknowns, integrity: { transcript: { path: "transcript.jsonl", sha256: createHash("sha256").update(transcript).digest("hex"), bytes: transcript.byteLength, }, }, }; } function buildPolicyProof( input: NonNullable, ): WorkflowPolicyProof { const decisions = input.decisions.map((decision) => ({ ...decision, reason: clipped(decision.reason, MAX_SUMMARY_CHARS), request: { ...decision.request, action: clipped(decision.request.action, MAX_REF_CHARS), path: clippedOptional(decision.request.path, MAX_REF_CHARS), target: clippedOptional(decision.request.target, MAX_REF_CHARS), }, })); return { schema_version: input.config.schema_version, name: input.config.name, sha256: policyDigest(input.config), isolation: input.isolation, network_access: input.networkAccess, config: input.config as NormalizedPolicy, decisions, summary: { allowed: decisions.filter((decision) => decision.verdict === "allow").length, denied: decisions.filter((decision) => decision.verdict === "deny").length, asked: decisions.filter((decision) => decision.initial_verdict === "ask").length, total: decisions.length, }, }; } export function writeWorkflowProof(path: string, proof: WorkflowProof): void { const body = `${JSON.stringify(proof, null, 2)}\n`; const bytes = Buffer.byteLength(body); if (bytes > MAX_PACKET_BYTES) { throw new Error(`workflow proof is ${bytes} bytes; limit is ${MAX_PACKET_BYTES}`); } writeImmutableJson(path, proof); } export function readWorkflowProof(coordRoot: string, runId: string): WorkflowProof { if (!RUN_ID.test(runId)) throw new Error(`invalid workflow run id ${JSON.stringify(runId)}`); const path = join(coordRoot, ".harnery", "workflows", runId, "proof.json"); if (!existsSync(path)) throw new Error(`workflow run ${runId} has no proof packet at ${path}`); const size = statSync(path).size; if (size > MAX_PACKET_BYTES) { throw new Error(`workflow proof is ${size} bytes; limit is ${MAX_PACKET_BYTES}`); } let proof: WorkflowProof; try { proof = JSON.parse(readFileSync(path, "utf8")) as WorkflowProof; } catch (error) { throw new Error(`cannot parse workflow proof at ${path}: ${(error as Error).message}`); } if ( proof.schema_version !== WORKFLOW_PROOF_SCHEMA_VERSION || proof.run?.id !== runId || (proof.run.class !== undefined && proof.run.class !== "environment" && proof.run.class !== "upstream" && proof.run.class !== "decision") || (proof.run.work_context !== undefined && (!proof.run.work_item_id || proof.run.work_context.id !== proof.run.work_item_id || !isCanonicalWorkflowWorkContext(proof.run.work_context))) || (proof.run.attempt_context !== undefined && (!proof.run.work_item_id || !proof.run.work_context || !isCanonicalWorkflowAttemptContext(proof.run.attempt_context))) || !validDiagnosticAdmissionProof(proof.diagnostic_admission) || (proof.execution !== undefined && !isWorkspaceExecutionEvidence(proof.execution, runId, proof.run.status)) ) { throw new Error(`workflow proof at ${path} has an unsupported or mismatched schema`); } const manifestPath = join(coordRoot, ".harnery", "workflows", runId, "run.json"); if (proof.execution === undefined && !existsSync(manifestPath)) return proof; const manifest = readWorkflowRunManifest(coordRoot, runId); const manifestBinding = manifest.execution.workspace_binding; const manifestFallback = manifest.execution.workspace_fallback; const proofExecution = proof.execution; const executionMatches = manifestBinding !== undefined ? isWorkspaceBoundExecutionEvidence(proofExecution, runId) && stableDigest(proofExecution.binding) === stableDigest(manifestBinding) : manifestFallback !== undefined ? isWorkspaceCompatibilityExecutionEvidence(proofExecution, runId) && stableDigest(proofExecution) === stableDigest(manifestFallback) : proofExecution === undefined; if (!executionMatches) { throw new Error(`workflow proof at ${path} does not match the frozen execution manifest`); } return proof; } function validDiagnosticAdmissionProof( value: WorkflowDiagnosticAdmissionProof | undefined, ): boolean { if (value === undefined) return true; if ( value.schema_version !== WORKFLOW_DIAGNOSTIC_ADMISSION_SCHEMA_VERSION || value.mode !== "shadow" || value.trigger !== "before-first-dispatch" || value.action !== "none" || (value.state !== "not-needed" && value.state !== "observed") ) { return false; } if (value.state === "not-needed") { return value.reason_code === "no-dispatch" && value.observation === undefined; } const observation = value.observation; return ( value.reason_code === undefined && observation !== undefined && validTimestamp(observation.requested_at) && validTimestamp(observation.observed_at) && Number.isSafeInteger(observation.wait_ms) && observation.wait_ms >= 0 && ["running", "started", "unavailable"].includes(observation.service_state) && ["fresh", "unavailable"].includes(observation.freshness) && (observation.sampled_at === undefined || validTimestamp(observation.sampled_at)) && observation.advice?.schema_version === DIAGNOSTIC_ADVICE_SCHEMA_VERSION && observation.advice.observer_only === true ); } function validTimestamp(value: unknown): value is string { return typeof value === "string" && value.length > 0 && Number.isFinite(Date.parse(value)); } function buildExecutionEvidence( workflowStatus: "succeeded" | "failed", binding: WorkspaceBinding, terminalAttestation: WorkspaceAttestation, ): WorkspaceExecutionEvidence { return { schema_version: 1, binding, terminal_attestation: terminalAttestation, terminal_lifecycle_state: workspaceProofLifecycle(workflowStatus, terminalAttestation), drift: [...terminalAttestation.provider_drift], unsupported: [...terminalAttestation.unsupported], unknowns: [...terminalAttestation.unknowns], receipts: { request: "workspace-request.json", cancellation_outcome: "cancellation/outcome.json", integration_plan: "integration/plan.json", integration_authorization: "integration/authorization.json", integration_apply: "integration/receipt.json", cleanup_intent: "cleanup/intent.json", cleanup_receipt: "cleanup/receipt.json", }, }; } export function renderWorkflowProof(proof: WorkflowProof): string { const lines = [ `run ${proof.run.id} (${proof.run.name}): ${proof.run.status}`, `duration: ${Math.round(proof.run.duration_ms / 1000)}s`, ]; if (proof.run.objective) lines.push(`objective: ${proof.run.objective}`); const summary = proof.acceptance.summary; lines.push( `acceptance: ${summary.satisfied} satisfied, ${summary.unsatisfied} unsatisfied, ${summary.unknown} unknown`, ); for (const criterion of proof.acceptance.criteria) { const mark = criterion.status === "satisfied" ? "PASS" : criterion.status === "unsatisfied" ? "FAIL" : "?"; const refs = criterion.evidence_ids.length > 0 ? ` [${criterion.evidence_ids.join(", ")}]` : ""; lines.push(` ${mark} ${criterion.id}: ${criterion.statement}${refs}`); } lines.push(`evidence: ${proof.evidence.length} record(s); agents: ${proof.agents.length}`); if (proof.policy) { lines.push( `policy: ${proof.policy.name}; ${proof.policy.summary.allowed} allowed, ` + `${proof.policy.summary.denied} denied, ${proof.policy.summary.asked} asked`, ); } if (proof.diagnostic_admission) { const observation = proof.diagnostic_admission.observation; lines.push( observation ? `admission: shadow ${observation.advice.assessment.state}; ` + `${observation.advice.assessment.recommended_action}; action none` : "admission: shadow not needed; no real child dispatch", ); } const repo = proof.repository; const drift = repo.drift; lines.push( `repository: branch ${repo.before.branch ?? "unknown"} -> ${repo.after.branch ?? "unknown"}; ` + `HEAD ${short(repo.before.head)} -> ${short(repo.after.head)}; ` + `${drift.dirty_paths_added.length} dirty added, ${drift.dirty_paths_cleared.length} cleared`, ); if (proof.unknowns.length > 0) { lines.push(`unknowns: ${proof.unknowns.length}`); for (const unknown of proof.unknowns) lines.push(` - ${unknown.message}`); } lines.push(`transcript sha256: ${proof.integrity.transcript.sha256}`); return `${lines.join("\n")}\n`; } function buildRepoEvidence(beforeRaw: RepoSnapshot, afterRaw: RepoSnapshot): WorkflowRepoEvidence { const before = normalizeRepoSnapshot(beforeRaw); const after = normalizeRepoSnapshot(afterRaw); const beforeDirty = new Set(before.dirty_paths); const afterDirty = new Set(after.dirty_paths); const retained = after.dirty_paths.filter((path) => beforeDirty.has(path)); const incomplete = Boolean( before.dirty_paths_truncated || after.dirty_paths_truncated || retained.length > 0, ); return { source: "engine", before, after, drift: { branch_changed: before.branch !== after.branch, head_changed: before.head !== after.head, dirty_paths_added: after.dirty_paths.filter((path) => !beforeDirty.has(path)), dirty_paths_cleared: before.dirty_paths.filter((path) => !afterDirty.has(path)), dirty_paths_retained: retained, incomplete, note: incomplete ? "Snapshots cannot prove whether retained dirty paths changed during the run, and truncated lists may omit paths." : undefined, }, }; } function normalizeRepoSnapshot(snapshot: RepoSnapshot): WorkflowRepoSnapshot { return { cwd: snapshot.cwd, root: snapshot.root, branch: snapshot.branch, head: snapshot.head, dirty_paths: snapshot.dirty_paths, dirty_paths_truncated: snapshot.dirty_paths_truncated, }; } function buildAdapterCoverage( agents: WorkflowAgentProof[], claims: Readonly> | undefined, attestations: Readonly> | undefined, ): AdapterEvidenceCoverage[] { return [...new Set(agents.map((agent) => agent.adapter))].map((adapter) => { const adapterAgents = agents.filter((agent) => agent.adapter === adapter); return { adapter, tool_evidence: claims?.[adapter]?.toolEvidence ?? { support: "unknown", note: "No adapter capability claim was supplied to this workflow run.", }, observed: { final_results: adapterAgents.filter((agent) => agent.result).length, session_ids: adapterAgents.filter((agent) => agent.session_id).length, costs: adapterAgents.filter((agent) => agent.cost_usd !== undefined).length, }, ...(attestations?.[adapter] ? { attestation: attestations[adapter] } : {}), }; }); } function buildUnknowns( agents: WorkflowAgentProof[], adapters: AdapterEvidenceCoverage[], repository: WorkflowRepoEvidence, ): WorkflowProofUnknown[] { const unknowns: WorkflowProofUnknown[] = []; for (const adapter of adapters) { if (adapter.tool_evidence.support === "unknown") { unknowns.push({ code: "adapter_capability_unregistered", adapter: adapter.adapter, message: `${adapter.adapter}: tool-evidence capability was not registered for this run.`, }); } else if (adapter.tool_evidence.support !== "supported") { unknowns.push({ code: "tool_evidence_unavailable", adapter: adapter.adapter, message: `${adapter.adapter}: adapter-native tool evidence is ${adapter.tool_evidence.support}.`, }); } } for (const agent of agents.filter((item) => item.status !== "failed")) { if (agent.cost_usd === undefined) { unknowns.push({ code: "agent_cost_unreported", adapter: agent.adapter, agent_id: agent.id, message: `${agent.id}: ${agent.adapter} did not report per-run cost.`, }); } if (!agent.session_id) { unknowns.push({ code: "agent_session_unreported", adapter: agent.adapter, agent_id: agent.id, message: `${agent.id}: ${agent.adapter} did not report a child session id.`, }); } } if (repository.drift.incomplete) { unknowns.push({ code: "repository_drift_incomplete", message: repository.drift.note ?? "Repository drift is incomplete.", }); } return unknowns; } function boundedRequired(value: unknown, field: string, max: number): string { if (typeof value !== "string" || value.trim() === "") throw new Error(`${field} is required`); const normalized = value.trim(); if (normalized.length > max) throw new Error(`${field} exceeds ${max} characters`); return normalized; } /** * Like `boundedRequired`, but shortens an over-long value instead of throwing. * * For fields where the string is a display label rather than load-bearing * content: losing the tail of a label is trivial, losing the run that produced * it is not. The marker keeps the truncation visible so a reader never mistakes * a shortened label for the whole thing. Still throws when the value is missing * or blank — that is a caller bug, not an overflow. */ function truncatedRequired(value: unknown, field: string, max: number): string { if (typeof value !== "string" || value.trim() === "") throw new Error(`${field} is required`); const normalized = value.trim(); if (normalized.length <= max) return normalized; const marker = "…[truncated]"; return `${normalized.slice(0, Math.max(0, max - marker.length))}${marker}`; } function boundedOptional(value: unknown, field: string, max: number): string | undefined { if (value === undefined) return undefined; if (typeof value !== "string") throw new Error(`${field} must be a string`); const normalized = value.trim(); if (normalized === "") return undefined; if (normalized.length > max) throw new Error(`${field} exceeds ${max} characters`); return normalized; } function enumValue(value: unknown, values: readonly T[], field: string): T { if (typeof value === "string" && values.includes(value as T)) return value as T; throw new Error(`${field} must be one of: ${values.join(", ")}`); } function short(value: string | undefined): string { return value ? value.slice(0, 8) : "unknown"; } function clipped(value: string, max: number): string { return value.length <= max ? value : `${value.slice(0, Math.max(0, max - 1))}…`; } function clippedOptional(value: string | undefined, max: number): string | undefined { return value === undefined ? undefined : clipped(value, max); }