/** * Agent-utterance producer profile — ADR 0003 step 6. * * This module implements Survey as a producer pointed at agent utterances * instead of web sources. Each factual statement in agent prose is extracted * as a candidate claim and run through the Inquiry pipeline. * * Integration point: surveyAgentUtterance is the clean entry point for * consumers wanting to "spell-check" an agent's output for evidence. Flow-agent * hook wiring (connecting this function to a live agent's output pipeline) is * out of scope for this module and lives in the flow-agents repo. * * Hard constraint (ADR 0003 §4): nothing here silently decides. The * UtteranceClaimExtractor is a pluggable interface; implementations may be * deterministic or model-backed, but they are always extractors — their output * has full provenance (excerpt, span, extractor name, confidence) and is * run through the Inquiry pipeline rather than treated as authoritative. */ import type { DerivationRule, InquiryRecord, TrustBundle, TrustStatus } from "@kontourai/surface"; import type { CanonicalClaimTarget } from "@kontourai/surface"; import type { Candidate, CandidateSet, Extraction, RawSource, SurveyInput } from "./types.js"; import type { InquiryMapping } from "./inquiry-mapping.js"; /** * A single extracted statement from an utterance. */ export interface ExtractedStatement { /** The canonical claim target this statement is about. */ target: CanonicalClaimTarget; /** The value claimed (if parseable). */ value?: unknown; /** The verbatim text segment that contains this claim. */ excerpt: string; /** Character-offset span within the utterance (0-indexed). */ span?: { start: number; end: number; }; /** Extractor confidence (0–1). */ confidence: number; } /** * Pluggable interface for extracting canonical claim statements from * agent-generated text. * * Implementations may be deterministic (like the reference extractor below), * regex-based, NLP-based, or LLM-backed — but they are always extractors: * their output carries full provenance and goes through the Inquiry pipeline * rather than being treated as an authoritative answer. */ export interface UtteranceClaimExtractor { name: string; extract(utterance: string): ExtractedStatement[] | Promise; } /** * Badge values for each extracted statement. * * A badge grades THE STATEMENT, not the target. It is a function of two * things: the status of the bundle's answer for the statement's canonical * target, and how the statement's own asserted value compares to that * answer's value. * * The status half of the vocabulary is Surface's `TrustStatus` verbatim — * Survey does not mint parallel status terms for concepts Surface (and the * Hachure core record shapes it implements) already name. Only two badge * values are Survey-side additions, because they describe the * statement-vs-answer relation rather than a claim's standing: * * - "contradicted": the bundle has an answer that would otherwise read as * support ("verified", "assumed", "stale") and the statement asserts a * DIFFERENT value. Mirrors the Hachure `contradiction` transparency-gap * type (merge.md §7b): a value conflict is surfaced, never silently * resolved in favour of one side. * - "unsupported": the inquiry did not resolve to an answer at all — no * mapping and no registered claim for the target. This value means "there * is nothing here to compare against", and nothing else: a claim that is * registered but merely awaiting review badges "proposed", and one with no * evidence badges "unknown". * * Every other badge is the answer's `TrustStatus` passed through unchanged, * so there is no fall-through path that can report a real status under a * label meaning "no such claim". */ export type StatementBadge = TrustStatus | "contradicted" | "unsupported"; /** * How a statement's asserted value compares to the bundle's answer value. * * - "agrees": both are comparable scalars and equivalent under * `assertionComparisonKey`. * - "contradicts": both are comparable scalars and NOT equivalent. * - "not-compared": no comparison was possible — the inquiry produced no * answer, or the extractor parsed no value out of the statement * (`ExtractedStatement.value` absent), or one side is not a scalar. * Never treated as agreement. */ export type StatementValueComparison = "agrees" | "contradicts" | "not-compared"; /** * How the Extraction's `text-span:` locator was resolved for a statement. * * - "span": the extractor supplied an explicit character span; the locator * points where the extractor said it does. * - "excerpt-match": no span, but the excerpt was found verbatim in the * utterance; the locator points at that occurrence. * - "unanchored-fallback": no span AND the excerpt does not occur in the * utterance. The locator is still well-formed (`text-span:0-`) so * downstream producer discipline holds, but it is a length-shaped * placeholder anchored at offset 0 — it does NOT point at the excerpt, and * the text it spans is unrelated prose. Anything that resolves the locator * against the source MUST check this field first; a hallucinated excerpt * lands here. */ export type LocatorResolution = "span" | "excerpt-match" | "unanchored-fallback"; export interface UtteranceStatement { excerpt: string; span?: { start: number; end: number; }; target: CanonicalClaimTarget; /** * The value the statement asserted, verbatim from the extractor * (`ExtractedStatement.value`) — not normalized, not defaulted. Absent when * the extractor parsed no value, which is exactly when `valueComparison` * is "not-compared". This is the field a reader needs to see WHAT was * compared against the bundle's answer. */ assertedValue?: unknown; /** How `assertedValue` compares to `inquiryRecord.answer?.value`. */ valueComparison: StatementValueComparison; /** Human-readable account of the comparison, naming both sides. */ comparisonRationale: string; /** * The Survey provenance records this statement produced: its Extraction, * its Candidate, and the per-target Candidate Set it belongs to. The * Candidate Set carries the Candidate Conflict verdict and rationale when * two statements in the SAME utterance disagree about one target * (`candidateSet.status === "conflict"`), which is a different signal from * `valueComparison` (statement vs bundle). */ records: UtteranceStatementRecords; /** How this statement's Extraction locator was resolved. */ locatorResolution: LocatorResolution; inquiryRecord: InquiryRecord; badge: StatementBadge; } /** * The result of surveying an agent utterance. * * source: the RawSource representing the utterance (kind: "agent-utterance"). * statements: per-statement verdicts, each with full provenance. * * This is the "spell-check for evidence" projection. Flow-agent hook wiring * is out of scope for this module (lives in flow-agents repo). */ export interface UtteranceTrustReport { source: RawSource; statements: UtteranceStatement[]; } /** * Full set of Survey records generated for a single extracted statement. * These are produced for provenance but not projected to Surface directly — * the report is the consumer-facing artifact. */ export interface UtteranceStatementRecords { extraction: Extraction; candidate: Candidate; candidateSet: CandidateSet; } interface BuildUtteranceRecordsParams { sourceId: string; utterance: string; extracted: ExtractedStatement[]; extractorName: string; observedAt: string; } interface UtteranceRecordsResult { records: UtteranceStatementRecords[]; extractions: Extraction[]; candidateSets: CandidateSet[]; /** * `locatorResolutions[idx]` is how `records[idx]`'s Extraction locator was * resolved — a parallel array rather than a fourth key on * `UtteranceStatementRecords`, whose shape is a pinned contract. The same * value is also on `records[idx].extraction.metadata.agentUtterance`. */ locatorResolutions: LocatorResolution[]; } /** * Build the full set of Survey records for every extracted statement in one * utterance: per-statement Extractions/Candidates plus per-target grouped * Candidate Sets. This is the shared orchestrator both `utteranceToSurveyInput` * and `surveyAgentUtterance` call, so both callers derive these records * identically — Slice 1's "single derivation path" invariant, preserved. * * `records[idx]` corresponds to `extracted[idx]` for every idx — `items` and * `records` are both built via `.map` over the same `extracted[]` array in * the same order; only `candidateSets` is deduped/grouped by target. */ export declare function buildUtteranceRecords(params: BuildUtteranceRecordsParams): UtteranceRecordsResult; /** * Project an agent utterance and its extracted statements into the standard * SurveyInput shape so they can flow into buildSurveyTrustBundle. * * Each extracted statement lands as: * RawSource (agent-utterance) → Extraction (with text-span locator) → * Candidate → CandidateSet (needs-review, no review outcome) → ClaimTarget * * Status discipline (ADR 0003 §4, to-surface.ts producer rules): * - All claims project as "proposed" — unreviewed extractions are proposals, * never authoritative. assertProducerDiscipline forbids verified/assumed * without a review outcome. * - agent-utterance is not a manual-entry source, so extraction.locator is * required. Span-located statements use text-span:start-end; span-less * statements use text-span derived from the excerpt offset in the utterance * (best-effort, 0-based). * * The returned SurveyInput can be passed directly to buildSurveyTrustBundle * to produce a TrustBundle with full provenance in the Trust Bundle metadata. * * @param utterance - The raw agent utterance text. * @param extracted - ExtractedStatements produced by a UtteranceClaimExtractor. * @param context - agentId, extractor name, optional now timestamp. */ export declare function utteranceToSurveyInput(utterance: string, extracted: ExtractedStatement[], context: { agentId: string; extractorName: string; now?: Date; source?: string; }): SurveyInput; /** * Survey an agent utterance, returning a trust report for each extracted claim. * * Steps: * 1. Build a RawSource for the utterance (kind: "agent-utterance"). * 2. Run the extractor → project each statement into Survey records with full * provenance (excerpt, span locator, extractor name, confidence). * 3. Resolve each extracted claim against the bundle via resolveInquiry or * resolveQuestion (if mappings are provided). * 4. Return an UtteranceTrustReport with per-statement badges. * * This function is the integration point for consumers. Flow-agent hook wiring * lives in the flow-agents repo. */ export declare function surveyAgentUtterance(utterance: string, extractor: UtteranceClaimExtractor, context: { bundle: TrustBundle; mappings?: InquiryMapping[]; rules?: DerivationRule[]; now?: Date; agentId: string; }): Promise; /** * Reference UtteranceClaimExtractor for tests. * * REFERENCE IMPLEMENTATION ONLY — not suitable for production extraction. * * Parsing strategy: looks for statements matching the pattern: * " is " * or * " : " * * where subjectId and fieldOrBehavior are single words. This intentionally * simple and transparent pattern lets tests be deterministic. * * The subjectType is always "unknown" since it cannot be inferred from text * alone in this reference implementation. */ export declare const referenceUtteranceExtractor: UtteranceClaimExtractor; export {};