@kontourai/survey 0.4.23 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +86 -872
  2. package/dist/example-data/corrected-document-candidates.d.ts +2 -0
  3. package/dist/{fixtures → example-data}/corrected-document-candidates.js +3 -3
  4. package/dist/{fixtures → example-data}/downstream-public-directory-proposal.d.ts +1 -1
  5. package/dist/{fixtures → example-data}/downstream-public-directory-proposal.js +1 -1
  6. package/dist/{fixtures → example-data}/public-directory-review-resource.d.ts +2 -2
  7. package/dist/{fixtures → example-data}/public-directory-review-resource.js +2 -2
  8. package/dist/example-data/public-field-review.d.ts +2 -0
  9. package/dist/{fixtures → example-data}/public-field-review.js +2 -2
  10. package/dist/{fixtures → example-data}/regulated-document-review-resource.d.ts +1 -1
  11. package/dist/{fixtures → example-data}/regulated-document-review-resource.js +3 -3
  12. package/dist/examples/public-field-observation.js +4 -4
  13. package/dist/examples/review-workbench/downstream-public-directory-adapter.d.ts +1 -1
  14. package/dist/examples/review-workbench/facility-credential-consumer.d.ts +2 -2
  15. package/dist/examples/review-workbench/facility-credential-consumer.js +8 -8
  16. package/dist/examples/review-workbench/server-apply-consumer.d.ts +1 -1
  17. package/dist/examples/review-workbench/server-apply-consumer.js +3 -3
  18. package/dist/src/agent-utterance.d.ts +166 -0
  19. package/dist/src/agent-utterance.js +373 -0
  20. package/dist/src/anthropic.d.ts +104 -0
  21. package/dist/src/anthropic.js +383 -0
  22. package/dist/src/index.d.ts +10 -2
  23. package/dist/src/index.js +5 -1
  24. package/dist/src/inquiry-mapping.d.ts +256 -0
  25. package/dist/src/inquiry-mapping.js +385 -0
  26. package/dist/src/review-workbench/review-queue-session.js +3 -3
  27. package/dist/src/review-workbench/review-surface-preview.js +1 -1
  28. package/dist/src/review-workbench/review-workbench-data.d.ts +4 -4
  29. package/dist/src/review-workbench/review-workbench-data.js +14 -14
  30. package/dist/src/schema-mapping.d.ts +196 -0
  31. package/dist/src/schema-mapping.js +486 -0
  32. package/dist/src/to-flow-artifact.d.ts +41 -0
  33. package/dist/src/to-flow-artifact.js +31 -0
  34. package/dist/src/to-surface.d.ts +3 -3
  35. package/dist/src/to-surface.js +1 -1
  36. package/dist/src/types.d.ts +2 -2
  37. package/package.json +25 -9
  38. package/dist/fixtures/corrected-document-candidates.d.ts +0 -2
  39. package/dist/fixtures/public-field-review.d.ts +0 -2
@@ -0,0 +1,383 @@
1
+ /**
2
+ * Anthropic production adapters for Survey's pluggable interfaces.
3
+ *
4
+ * ADR 0003 §4 compliance: these implementations are PROPOSERS only. Every
5
+ * output is a proposal (MappingProposal / ExtractedStatement) that goes
6
+ * through the existing review/auto-accept machinery before counting.
7
+ * Nothing here bypasses review.
8
+ *
9
+ * Subpath export: import from "@kontourai/survey/anthropic" — this module is
10
+ * NOT re-exported from the main index.ts so consumers without @anthropic-ai/sdk
11
+ * pay nothing.
12
+ *
13
+ * Injected client: both factories accept an optional pre-built client so tests
14
+ * can inject a fake without hitting the network. If no client is provided, one
15
+ * is constructed from opts.apiKey (falling back to process.env.ANTHROPIC_API_KEY).
16
+ */
17
+ const DEFAULT_MODEL = "claude-sonnet-4-6";
18
+ /**
19
+ * Build or return a messages client from options.
20
+ * Dynamic-imports @anthropic-ai/sdk only when no client is injected,
21
+ * keeping the optional peer dep out of the eager module graph.
22
+ */
23
+ async function resolveClient(opts) {
24
+ if (opts.client)
25
+ return opts.client;
26
+ // Dynamically load the SDK — only reachable when no client is injected.
27
+ // Uses a variable module specifier so TypeScript does not try to resolve
28
+ // the optional peer dep at compile time. At runtime the SDK must be installed.
29
+ const sdkModule = "@anthropic-ai/sdk";
30
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
31
+ const sdkImport = await Function("m", "return import(m)")(sdkModule);
32
+ const { default: Anthropic } = sdkImport;
33
+ const apiKey = opts.apiKey ?? process.env["ANTHROPIC_API_KEY"];
34
+ if (!apiKey) {
35
+ throw new Error("AnthropicAdapter: no API key. Provide opts.apiKey, set ANTHROPIC_API_KEY, or inject opts.client.");
36
+ }
37
+ const sdk = new Anthropic({ apiKey });
38
+ return sdk.messages;
39
+ }
40
+ // ---------------------------------------------------------------------------
41
+ // JSON tool schemas
42
+ // ---------------------------------------------------------------------------
43
+ const MAPPING_PROPOSAL_TOOL = {
44
+ name: "submit_mapping_proposals",
45
+ description: "Submit an array of candidate mappings from the natural-language question to registered canonical claim targets or derivation rules. " +
46
+ "You are PROPOSING for human review — every proposal must carry a rationale and confidence score. " +
47
+ "Per ADR 0003 §4, proposals are reviewable records; they do not resolve questions by themselves.",
48
+ input_schema: {
49
+ type: "object",
50
+ properties: {
51
+ proposals: {
52
+ type: "array",
53
+ items: {
54
+ type: "object",
55
+ properties: {
56
+ proposedTargetSubjectType: {
57
+ type: "string",
58
+ description: "subjectType of the canonical claim target (omit if proposing a rule)",
59
+ },
60
+ proposedTargetSubjectId: {
61
+ type: "string",
62
+ description: "subjectId of the canonical claim target (omit if proposing a rule)",
63
+ },
64
+ proposedTargetFieldOrBehavior: {
65
+ type: "string",
66
+ description: "fieldOrBehavior of the canonical claim target (omit if proposing a rule)",
67
+ },
68
+ proposedRuleId: {
69
+ type: "string",
70
+ description: "Id of the derivation rule this question maps to (omit if proposing a target)",
71
+ },
72
+ confidence: {
73
+ type: "number",
74
+ description: "Confidence in this mapping (0.0–1.0)",
75
+ },
76
+ rationale: {
77
+ type: "string",
78
+ description: "Human-readable explanation of why this mapping is proposed",
79
+ },
80
+ excerpt: {
81
+ type: "string",
82
+ description: "Verbatim excerpt from the question that drove the suggestion",
83
+ },
84
+ },
85
+ required: ["confidence", "rationale"],
86
+ },
87
+ },
88
+ },
89
+ required: ["proposals"],
90
+ },
91
+ };
92
+ const UTTERANCE_EXTRACTION_TOOL = {
93
+ name: "submit_extracted_statements",
94
+ description: "Submit an array of factual statements extracted from the agent utterance. " +
95
+ "Each statement maps to a canonical claim target with full provenance (excerpt, span, confidence). " +
96
+ "You are EXTRACTING FOR REVIEW — output is a proposal queue, not authoritative truth. " +
97
+ "Per ADR 0003 §4, every extracted statement requires a rationale and confidence score.",
98
+ input_schema: {
99
+ type: "object",
100
+ properties: {
101
+ statements: {
102
+ type: "array",
103
+ items: {
104
+ type: "object",
105
+ properties: {
106
+ subjectType: {
107
+ type: "string",
108
+ description: "The canonical subjectType (use 'unknown' if uncertain)",
109
+ },
110
+ subjectId: {
111
+ type: "string",
112
+ description: "The entity or resource the statement is about",
113
+ },
114
+ fieldOrBehavior: {
115
+ type: "string",
116
+ description: "The property or behavior being claimed",
117
+ },
118
+ value: {
119
+ description: "The claimed value (string, number, boolean, or null)",
120
+ },
121
+ excerpt: {
122
+ type: "string",
123
+ description: "Verbatim text from the utterance that contains this claim",
124
+ },
125
+ spanStart: {
126
+ type: "number",
127
+ description: "0-indexed character offset where the excerpt starts in the utterance",
128
+ },
129
+ spanEnd: {
130
+ type: "number",
131
+ description: "0-indexed character offset where the excerpt ends in the utterance",
132
+ },
133
+ confidence: {
134
+ type: "number",
135
+ description: "Extraction confidence (0.0–1.0)",
136
+ },
137
+ },
138
+ required: ["subjectId", "fieldOrBehavior", "excerpt", "confidence"],
139
+ },
140
+ },
141
+ },
142
+ required: ["statements"],
143
+ },
144
+ };
145
+ // ---------------------------------------------------------------------------
146
+ // Tool output parsing helpers
147
+ // ---------------------------------------------------------------------------
148
+ /**
149
+ * Extract the first tool_use block with the given name from a message.
150
+ * Returns undefined if not found (malformed output is rejected, never silently accepted).
151
+ */
152
+ function extractToolUseInput(message, toolName) {
153
+ for (const block of message.content) {
154
+ if (block.type === "tool_use" && block.name === toolName) {
155
+ return block.input;
156
+ }
157
+ }
158
+ return undefined;
159
+ }
160
+ function isRecord(value) {
161
+ return typeof value === "object" && value !== null && !Array.isArray(value);
162
+ }
163
+ function isArray(value) {
164
+ return Array.isArray(value);
165
+ }
166
+ function stringOrUndefined(value) {
167
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
168
+ }
169
+ function numberInRange(value, min, max) {
170
+ if (typeof value !== "number" || !isFinite(value))
171
+ return undefined;
172
+ if (value < min || value > max)
173
+ return undefined;
174
+ return value;
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // createAnthropicMappingProposer
178
+ // ---------------------------------------------------------------------------
179
+ /**
180
+ * Create a MappingProposer backed by Anthropic's API using forced tool-use.
181
+ *
182
+ * ADR 0003 §4: returns PROPOSALS only — they flow through the existing
183
+ * review/auto-accept machinery before counting as mappings.
184
+ *
185
+ * Tool output is validated strictly: malformed items (missing required fields,
186
+ * out-of-range confidence, no target and no rule) are filtered out rather than
187
+ * silently accepted.
188
+ */
189
+ export function createAnthropicMappingProposer(opts = {}) {
190
+ const model = opts.model ?? DEFAULT_MODEL;
191
+ return {
192
+ name: `anthropic-mapping-proposer:${model}`,
193
+ async propose(question, context) {
194
+ const client = await resolveClient(opts);
195
+ // Build context summary for the prompt
196
+ const claimsContext = buildClaimsContext(context.bundle);
197
+ const rulesContext = buildRulesContext(context.rules);
198
+ const systemPrompt = [
199
+ "You are a mapping proposer for the Kontour trust ledger.",
200
+ "Your role is to PROPOSE (not decide) how a natural-language question maps to a registered canonical claim or derivation rule.",
201
+ "Every proposal you return will be reviewed by a human or auto-accept policy before it counts.",
202
+ "Do not make up claim targets that are not in the registered list below.",
203
+ "Return only proposals you genuinely believe are plausible mappings — with honest confidence scores.",
204
+ "",
205
+ claimsContext,
206
+ rulesContext,
207
+ ]
208
+ .filter(Boolean)
209
+ .join("\n");
210
+ const userMessage = `Question to map: "${question}"`;
211
+ const message = await client.create({
212
+ model,
213
+ max_tokens: 1024,
214
+ messages: [{ role: "user", content: `${systemPrompt}\n\n${userMessage}` }],
215
+ tools: [MAPPING_PROPOSAL_TOOL],
216
+ tool_choice: { type: "tool", name: "submit_mapping_proposals" },
217
+ });
218
+ const input = extractToolUseInput(message, "submit_mapping_proposals");
219
+ if (!isRecord(input))
220
+ return [];
221
+ const rawProposals = input["proposals"];
222
+ if (!isArray(rawProposals))
223
+ return [];
224
+ const proposedAt = new Date().toISOString();
225
+ const results = [];
226
+ for (const item of rawProposals) {
227
+ const proposal = parseMappingProposalItem(item, question, model, proposedAt);
228
+ if (proposal)
229
+ results.push(proposal);
230
+ }
231
+ return results;
232
+ },
233
+ };
234
+ }
235
+ function parseMappingProposalItem(item, question, proposedBy, proposedAt) {
236
+ if (!isRecord(item))
237
+ return undefined;
238
+ const raw = item;
239
+ const confidence = numberInRange(raw.confidence, 0, 1);
240
+ const rationale = stringOrUndefined(raw.rationale);
241
+ // Both required fields must be present
242
+ if (confidence === undefined || rationale === undefined)
243
+ return undefined;
244
+ const subjectType = stringOrUndefined(raw.proposedTargetSubjectType);
245
+ const subjectId = stringOrUndefined(raw.proposedTargetSubjectId);
246
+ const fieldOrBehavior = stringOrUndefined(raw.proposedTargetFieldOrBehavior);
247
+ const ruleId = stringOrUndefined(raw.proposedRuleId);
248
+ const excerpt = stringOrUndefined(raw.excerpt);
249
+ // Exactly one of (target triple) or ruleId must be present
250
+ const hasTarget = subjectType !== undefined && subjectId !== undefined && fieldOrBehavior !== undefined;
251
+ const hasRule = ruleId !== undefined;
252
+ if (!hasTarget && !hasRule)
253
+ return undefined;
254
+ const proposedTarget = hasTarget
255
+ ? { subjectType: subjectType, subjectId: subjectId, fieldOrBehavior: fieldOrBehavior }
256
+ : undefined;
257
+ const id = `proposal.anthropic.${encodeId(question)}.${Date.now()}`;
258
+ return {
259
+ id,
260
+ question,
261
+ proposedTarget,
262
+ proposedRuleId: hasRule ? ruleId : undefined,
263
+ confidence,
264
+ rationale,
265
+ excerpt,
266
+ proposedBy,
267
+ proposedAt,
268
+ };
269
+ }
270
+ // ---------------------------------------------------------------------------
271
+ // createAnthropicUtteranceExtractor
272
+ // ---------------------------------------------------------------------------
273
+ /**
274
+ * Create a UtteranceClaimExtractor backed by Anthropic's API using forced tool-use.
275
+ *
276
+ * ADR 0003 §4: returns EXTRACTED STATEMENTS only — they carry full provenance
277
+ * (excerpt, span, extractor name, confidence) and flow through the Inquiry
278
+ * pipeline. They are never treated as authoritative.
279
+ *
280
+ * Malformed tool output is rejected/filtered — items missing required fields
281
+ * (subjectId, fieldOrBehavior, excerpt, confidence) are dropped.
282
+ */
283
+ export function createAnthropicUtteranceExtractor(opts = {}) {
284
+ const model = opts.model ?? DEFAULT_MODEL;
285
+ return {
286
+ name: `anthropic-utterance-extractor:${model}`,
287
+ async extract(utterance) {
288
+ const client = await resolveClient(opts);
289
+ const systemPrompt = [
290
+ "You are a factual statement extractor for the Kontour trust ledger.",
291
+ "Your role is to identify every factual claim in the agent utterance and extract it with full provenance.",
292
+ "Each extracted statement will be reviewed for trust coverage — you are NOT deciding truth, only extracting for review.",
293
+ "Extract only statements that assert factual properties of named entities.",
294
+ "Skip opinions, predictions, and procedural descriptions.",
295
+ "Provide honest confidence scores — low confidence for ambiguous phrasing.",
296
+ "Include the exact verbatim excerpt and 0-indexed character span offsets.",
297
+ ].join("\n");
298
+ const userMessage = `Extract factual statements from this agent utterance:\n\n"${utterance}"`;
299
+ const message = await client.create({
300
+ model,
301
+ max_tokens: 2048,
302
+ messages: [{ role: "user", content: `${systemPrompt}\n\n${userMessage}` }],
303
+ tools: [UTTERANCE_EXTRACTION_TOOL],
304
+ tool_choice: { type: "tool", name: "submit_extracted_statements" },
305
+ });
306
+ const input = extractToolUseInput(message, "submit_extracted_statements");
307
+ if (!isRecord(input))
308
+ return [];
309
+ const rawStatements = input["statements"];
310
+ if (!isArray(rawStatements))
311
+ return [];
312
+ const results = [];
313
+ for (const item of rawStatements) {
314
+ const statement = parseExtractedStatementItem(item, utterance);
315
+ if (statement)
316
+ results.push(statement);
317
+ }
318
+ return results;
319
+ },
320
+ };
321
+ }
322
+ function parseExtractedStatementItem(item, utterance) {
323
+ if (!isRecord(item))
324
+ return undefined;
325
+ const raw = item;
326
+ const subjectId = stringOrUndefined(raw.subjectId);
327
+ const fieldOrBehavior = stringOrUndefined(raw.fieldOrBehavior);
328
+ const excerpt = stringOrUndefined(raw.excerpt);
329
+ const confidence = numberInRange(raw.confidence, 0, 1);
330
+ // All required fields must be present
331
+ if (!subjectId || !fieldOrBehavior || !excerpt || confidence === undefined)
332
+ return undefined;
333
+ const subjectType = stringOrUndefined(raw.subjectType) ?? "unknown";
334
+ // Validate span if provided — both start and end must be valid integers
335
+ // within the utterance length
336
+ let span;
337
+ if (typeof raw.spanStart === "number" && typeof raw.spanEnd === "number") {
338
+ const start = Math.trunc(raw.spanStart);
339
+ const end = Math.trunc(raw.spanEnd);
340
+ if (Number.isFinite(start) &&
341
+ Number.isFinite(end) &&
342
+ start >= 0 &&
343
+ end > start &&
344
+ end <= utterance.length) {
345
+ span = { start, end };
346
+ }
347
+ }
348
+ return {
349
+ target: { subjectType, subjectId, fieldOrBehavior },
350
+ value: raw.value ?? undefined,
351
+ excerpt,
352
+ span,
353
+ confidence,
354
+ };
355
+ }
356
+ // ---------------------------------------------------------------------------
357
+ // Prompt context builders
358
+ // ---------------------------------------------------------------------------
359
+ function buildClaimsContext(bundle) {
360
+ if (!bundle || bundle.claims.length === 0)
361
+ return "";
362
+ const lines = [
363
+ "Registered canonical claim targets (use ONLY these as proposedTarget):",
364
+ ...bundle.claims.map((c) => ` - subjectType="${c.subjectType}" subjectId="${c.subjectId}" fieldOrBehavior="${c.fieldOrBehavior}"`),
365
+ ];
366
+ return lines.join("\n");
367
+ }
368
+ function buildRulesContext(rules) {
369
+ if (!rules || rules.length === 0)
370
+ return "";
371
+ const lines = [
372
+ "Registered derivation rules (use rule id as proposedRuleId):",
373
+ ...rules.map((r) => ` - id="${r.id}" name="${r.name}"`),
374
+ ];
375
+ return lines.join("\n");
376
+ }
377
+ function encodeId(value) {
378
+ return value
379
+ .toLowerCase()
380
+ .replace(/\s+/g, "-")
381
+ .replace(/[^a-z0-9\-]/g, "")
382
+ .slice(0, 40);
383
+ }
@@ -7,8 +7,10 @@ export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js"
7
7
  export type { ReviewedCandidateResolutionInput } from "./reviewed-candidate-resolution.js";
8
8
  export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
9
9
  export type { CurrentProposedCandidateRole, ReviewedCurrentProposedResolutionInput, } from "./reviewed-current-proposed-resolution.js";
10
- export { buildSurveyTrustInput } from "./to-surface.js";
11
- export type { BuildSurveyTrustInputOptions } from "./to-surface.js";
10
+ export { flowTrustArtifactFromReviewOutcome } from "./to-flow-artifact.js";
11
+ export type { FlowTrustArtifact, FlowTrustArtifactOptions } from "./to-flow-artifact.js";
12
+ export { buildSurveyTrustBundle } from "./to-surface.js";
13
+ export type { BuildSurveyTrustBundleOptions } from "./to-surface.js";
12
14
  export { buildSurveyLearningProjections } from "./learning-projections.js";
13
15
  export type { LearningProjection, LearningProjectionKind, LearningProjectionSeverity, LearningProjectionSignal, } from "./learning-projections.js";
14
16
  export { buildCanonicalReviewProofPayload, buildReviewProofAnchor, canonicalReviewProofJson, hashCanonicalReviewProofPayload, REVIEW_PROOF_CONTRACT_VERSION, REVIEW_PROOF_PACKAGE_NAME, REVIEW_PROOF_SCHEMA, REVIEW_PROOF_SCHEMA_VERSION, } from "./review-proof.js";
@@ -23,3 +25,9 @@ export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildRev
23
25
  export type { ReviewCandidatePresentation, ReviewCandidatePresentationContext, ReviewItemPresentation, ReviewItemPresentationContext, ReviewPresentationAdapter, ReviewPresentationLink, ReviewResultPresentation, ReviewTracePresentationContext, ReviewTraceRef, ReviewValuePresentationContext, } from "./review-workbench/review-presentation.js";
24
26
  export { apiRecordSource, manualEntrySource, policyStandardSource, uploadedDocumentSource, webPageSource, } from "./raw-source.js";
25
27
  export type { ApiRecordSourceInput, ChecksumInput, ManualEntrySourceInput, PolicyStandardMetadata, PolicyStandardSourceInput, RawSourceInput, UploadedDocumentSourceInput, WebPageSourceInput, } from "./raw-source.js";
28
+ export { applyAutoAcceptPolicy, applyMappingReview, buildMappingReviewItems, lookupMapping, lookupRejectedMapping, normalizeQuestion, proposalsToCandidateSet, referenceMappingProposer, resolveQuestion, } from "./inquiry-mapping.js";
29
+ export type { AutoAcceptPolicy, InquiryMapping, MappingProposal, MappingProposer, } from "./inquiry-mapping.js";
30
+ export { referenceUtteranceExtractor, surveyAgentUtterance, utteranceToSurveyInput, } from "./agent-utterance.js";
31
+ export type { ExtractedStatement, StatementBadge, UtteranceClaimExtractor, UtteranceStatement, UtteranceStatementRecords, UtteranceTrustReport, } from "./agent-utterance.js";
32
+ export { mappingReviewToSurface, referenceSchemaExtractor, surveySchemaMapping, } from "./schema-mapping.js";
33
+ export type { MappingProposalRecord, ReviewedMapping, SchemaMappingExtractor, SchemaMappingOptions, SystemFieldRef, } from "./schema-mapping.js";
package/dist/src/index.js CHANGED
@@ -2,7 +2,8 @@ export { reviewResourceApiVersion } from "./review-resource.js";
2
2
  export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
3
3
  export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js";
4
4
  export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
5
- export { buildSurveyTrustInput } from "./to-surface.js";
5
+ export { flowTrustArtifactFromReviewOutcome } from "./to-flow-artifact.js";
6
+ export { buildSurveyTrustBundle } from "./to-surface.js";
6
7
  export { buildSurveyLearningProjections } from "./learning-projections.js";
7
8
  export { buildCanonicalReviewProofPayload, buildReviewProofAnchor, canonicalReviewProofJson, hashCanonicalReviewProofPayload, REVIEW_PROOF_CONTRACT_VERSION, REVIEW_PROOF_PACKAGE_NAME, REVIEW_PROOF_SCHEMA, REVIEW_PROOF_SCHEMA_VERSION, } from "./review-proof.js";
8
9
  export { fieldObservation } from "./field-observation.js";
@@ -10,3 +11,6 @@ export { repeatedObservation } from "./repeated-observation.js";
10
11
  export { sourceOfAuthorityObservation, sourceOfAuthorityObservationBuilder, SourceOfAuthorityObservationBuilder, } from "./source-of-authority-observation.js";
11
12
  export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-workbench/review-presentation.js";
12
13
  export { apiRecordSource, manualEntrySource, policyStandardSource, uploadedDocumentSource, webPageSource, } from "./raw-source.js";
14
+ export { applyAutoAcceptPolicy, applyMappingReview, buildMappingReviewItems, lookupMapping, lookupRejectedMapping, normalizeQuestion, proposalsToCandidateSet, referenceMappingProposer, resolveQuestion, } from "./inquiry-mapping.js";
15
+ export { referenceUtteranceExtractor, surveyAgentUtterance, utteranceToSurveyInput, } from "./agent-utterance.js";
16
+ export { mappingReviewToSurface, referenceSchemaExtractor, surveySchemaMapping, } from "./schema-mapping.js";
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Inquiry mapping — ADR 0003 step 5.
3
+ *
4
+ * This module implements the "memoize the mapping, never the answer" principle.
5
+ * A MappingProposal is a reviewable record produced by a pluggable MappingProposer.
6
+ * Proposals flow through Survey's existing candidate → review machinery.
7
+ * The durable artifact after review is an InquiryMapping.
8
+ * Answers always recompute live from the TrustBundle.
9
+ *
10
+ * Nothing here silently decides. Exact canonical-form matching is the only thing
11
+ * that resolves without review. A proposer may suggest that a question maps to a
12
+ * registered claim or rule — but every suggestion lands as a MappingProposal with
13
+ * provenance before it counts. (ADR 0003 §4)
14
+ *
15
+ * Integration point: resolveQuestion is the entry point for consumers checking
16
+ * whether a cached mapping already covers a question. Flow-agent hook wiring
17
+ * (connecting this to an agent's output pipeline) is out of scope for this module
18
+ * and lives in the flow-agents repo.
19
+ */
20
+ import type { DerivationRule, InquiryRecord, TrustBundle } from "@kontourai/surface";
21
+ import type { CanonicalClaimTarget } from "@kontourai/surface";
22
+ import type { Candidate, CandidateSet, ReviewOutcome } from "./types.js";
23
+ /**
24
+ * A single machine- or human-generated suggestion that a natural-language
25
+ * question maps to a canonical claim target or a named derivation rule.
26
+ *
27
+ * Exactly one of proposedTarget / proposedRuleId must be set.
28
+ */
29
+ export interface MappingProposal {
30
+ id: string;
31
+ question: string;
32
+ /** The canonical claim target this question is proposed to map to. */
33
+ proposedTarget?: CanonicalClaimTarget;
34
+ /** The derivation rule id this question is proposed to map to. */
35
+ proposedRuleId?: string;
36
+ /** Proposer confidence in the mapping (0–1). */
37
+ confidence: number;
38
+ /** Human-readable rationale for the proposal. */
39
+ rationale: string;
40
+ /** Optional verbatim excerpt from the question that drove the suggestion. */
41
+ excerpt?: string;
42
+ /** Who or what generated this proposal (name of the MappingProposer). */
43
+ proposedBy: string;
44
+ /** ISO 8601 timestamp. */
45
+ proposedAt: string;
46
+ }
47
+ /**
48
+ * The durable reviewed artifact: a natural-language question has been mapped
49
+ * to a canonical claim target or derivation rule and the mapping has been
50
+ * given a status through review (or auto-accept policy).
51
+ *
52
+ * Per ADR 0003 §6: memoize the mapping, never the answer. Answers always
53
+ * recompute from live claim status; this record is never updated to carry
54
+ * a cached answer.
55
+ */
56
+ export interface InquiryMapping {
57
+ id: string;
58
+ /** Deterministic normalized form of the question (see normalizeQuestion). */
59
+ normalizedQuestion: string;
60
+ /** The canonical claim target this mapping resolves to. */
61
+ target?: CanonicalClaimTarget;
62
+ /** The derivation rule id this mapping resolves to. */
63
+ ruleId?: string;
64
+ /** Whether the mapping was accepted by a human reviewer or auto-accept policy. */
65
+ status: "verified" | "assumed" | "rejected";
66
+ /** Actor who performed the review (reviewer id or "auto-accept-policy"). */
67
+ reviewedBy: string;
68
+ /** ISO 8601 timestamp of review. */
69
+ reviewedAt: string;
70
+ /** Optional rationale for the decision. */
71
+ rationale?: string;
72
+ /**
73
+ * Whether the reviewer was within their declared comfort zone.
74
+ * Mirrors Survey's withinComfortZone semantics: false means a different
75
+ * authority should confirm; true (or absent) means the reviewer was
76
+ * comfortable making this decision.
77
+ */
78
+ withinComfortZone?: boolean;
79
+ /** The id of the MappingProposal that was accepted or rejected. */
80
+ proposalId: string;
81
+ }
82
+ /**
83
+ * Pluggable interface for proposing a canonical mapping for a question.
84
+ *
85
+ * Implementations may be deterministic (like the reference proposer below),
86
+ * embedding-based, or LLM-backed — but they are always proposers: their output
87
+ * goes through review before it counts (ADR 0003 §4).
88
+ *
89
+ * The interface accepts both synchronous return values and Promises, matching
90
+ * Survey's async-optional style.
91
+ */
92
+ export interface MappingProposer {
93
+ name: string;
94
+ propose(question: string, context: {
95
+ bundle?: TrustBundle;
96
+ rules?: DerivationRule[];
97
+ }): MappingProposal[] | Promise<MappingProposal[]>;
98
+ }
99
+ /**
100
+ * Deterministic normalization for question strings.
101
+ *
102
+ * Rules:
103
+ * - Lowercase
104
+ * - Collapse internal whitespace runs to a single space
105
+ * - Trim leading/trailing whitespace
106
+ * - Strip terminal punctuation (. ? ! , ;) from the end
107
+ *
108
+ * This is exact normalized-text memoization, not semantic matching.
109
+ * Two questions that differ only in case, whitespace, or trailing punctuation
110
+ * are considered the same question. Questions with different wording but the
111
+ * same intent are NOT matched here; a MappingProposer handles that.
112
+ */
113
+ export declare function normalizeQuestion(question: string): string;
114
+ /**
115
+ * Project an array of proposals for a single question into Survey's existing
116
+ * Candidate / CandidateSet shapes so they flow through the existing review
117
+ * machinery rather than a parallel system.
118
+ *
119
+ * Status rules:
120
+ * - All proposals agree on the same target/ruleId → "needs-review"
121
+ * - Proposals disagree (more than one distinct resolved target/rule) → "conflict"
122
+ * - Empty proposals → "needs-review" with empty candidates
123
+ *
124
+ * The CandidateSet target is the normalized question; each Candidate carries
125
+ * the proposal id as extractionId and the proposal metadata.
126
+ */
127
+ export declare function proposalsToCandidateSet(question: string, proposals: MappingProposal[]): {
128
+ candidateSet: CandidateSet;
129
+ candidates: Candidate[];
130
+ };
131
+ /**
132
+ * Turn a Survey ReviewOutcome on a mapping candidate set into a durable
133
+ * InquiryMapping record.
134
+ *
135
+ * The candidateSet must have been built by proposalsToCandidateSet.
136
+ * The reviewOutcome's candidateId must match one of the candidates.
137
+ */
138
+ export declare function applyMappingReview(candidateSet: CandidateSet, reviewOutcome: ReviewOutcome): InquiryMapping;
139
+ export interface AutoAcceptPolicy {
140
+ minConfidence: number;
141
+ }
142
+ /**
143
+ * Apply an auto-accept policy to a list of proposals, returning InquiryMappings.
144
+ *
145
+ * Proposals at or above minConfidence → status "assumed", withinComfortZone: true
146
+ * Proposals below minConfidence → return a "needs-review" mapping (not yet durable)
147
+ *
148
+ * Only non-conflicting proposals are auto-accepted. If proposals disagree, they
149
+ * need human review regardless of confidence.
150
+ *
151
+ * Returns an array of InquiryMappings (only for accepted proposals).
152
+ */
153
+ export declare function applyAutoAcceptPolicy(proposals: MappingProposal[], policy: AutoAcceptPolicy): InquiryMapping[];
154
+ /**
155
+ * Look up an InquiryMapping for a question by exact normalized-text match.
156
+ *
157
+ * Rejected mappings are remembered but never resolve: this function returns
158
+ * undefined for rejected mappings so callers treat the question as a miss.
159
+ * To check whether a question was previously rejected (and should not be
160
+ * re-proposed), call lookupRejectedMapping.
161
+ */
162
+ export declare function lookupMapping(mappings: InquiryMapping[], question: string): InquiryMapping | undefined;
163
+ /**
164
+ * Check whether a question was previously rejected.
165
+ * Rejected mappings prevent re-proposing: if this returns a mapping, the
166
+ * question should not be sent to a proposer again without human escalation.
167
+ */
168
+ export declare function lookupRejectedMapping(mappings: InquiryMapping[], question: string): InquiryMapping | undefined;
169
+ /**
170
+ * Resolve a natural-language question against a TrustBundle.
171
+ *
172
+ * On mapping hit (verified or assumed): constructs a Surface Inquiry from the
173
+ * mapped target/rule and returns resolveInquiry(...) — answers always recompute
174
+ * live from the current bundle state; the mapping is memoized, not the answer.
175
+ *
176
+ * On miss (no mapping, or rejected mapping): returns an InquiryRecord with
177
+ * outcome "unsupported" so the gap is honest and recordable.
178
+ *
179
+ * This function is the clean integration point for consumers. Flow-agent hook
180
+ * wiring (connecting this to an agent's output pipeline) lives in the
181
+ * flow-agents repo.
182
+ */
183
+ export declare function resolveQuestion(bundle: TrustBundle, question: string, options: {
184
+ mappings: InquiryMapping[];
185
+ rules?: DerivationRule[];
186
+ now?: Date;
187
+ askedBy: string;
188
+ }): InquiryRecord;
189
+ /**
190
+ * Build ReviewItem records for the existing review workbench from a list of
191
+ * mapping candidate sets.
192
+ *
193
+ * Follow the existing ReviewItem contract exactly. This helper produces
194
+ * ReviewItem payloads so mapping proposals can be reviewed through the same
195
+ * workbench as other Survey candidates.
196
+ */
197
+ export declare function buildMappingReviewItems(candidateSets: Array<{
198
+ candidateSet: CandidateSet;
199
+ candidates: Candidate[];
200
+ }>): Array<{
201
+ apiVersion: "survey.kontourai.io/v1alpha1";
202
+ kind: "ReviewItem";
203
+ metadata: {
204
+ name: string;
205
+ labels?: Record<string, string>;
206
+ };
207
+ spec: {
208
+ target: string;
209
+ candidates: Array<{
210
+ id: string;
211
+ role: "proposed";
212
+ value: unknown;
213
+ confidence?: number;
214
+ source: {
215
+ sourceRef: string;
216
+ kind: "inquiry-question";
217
+ observedAt: string;
218
+ locatorScheme: "text";
219
+ };
220
+ extraction: {
221
+ target: string;
222
+ confidence?: number;
223
+ extractor: string;
224
+ extractedAt: string;
225
+ };
226
+ claimTarget: {
227
+ subjectType: string;
228
+ subjectId: string;
229
+ surface: string;
230
+ claimType: string;
231
+ fieldOrBehavior: string;
232
+ impactLevel: "low";
233
+ };
234
+ projection?: {
235
+ candidateSetId: string;
236
+ candidateId: string;
237
+ };
238
+ }>;
239
+ candidateSetStatus: "needs-review" | "conflict";
240
+ rationale?: string;
241
+ };
242
+ status: {
243
+ observedCandidateCount: number;
244
+ };
245
+ }>;
246
+ /**
247
+ * Reference MappingProposer for tests.
248
+ *
249
+ * REFERENCE IMPLEMENTATION ONLY — not suitable for production matching.
250
+ *
251
+ * Matching strategy: a question maps to a claim if it contains both the
252
+ * claim's subjectId and fieldOrBehavior as token substrings (case-insensitive,
253
+ * space-delimited token match). This is intentionally simple and transparent
254
+ * so tests can be deterministic.
255
+ */
256
+ export declare const referenceMappingProposer: MappingProposer;