@kontourai/survey 0.4.24 → 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.
- package/README.md +10 -10
- package/dist/example-data/corrected-document-candidates.d.ts +2 -0
- package/dist/{fixtures → example-data}/corrected-document-candidates.js +3 -3
- package/dist/{fixtures → example-data}/downstream-public-directory-proposal.d.ts +1 -1
- package/dist/{fixtures → example-data}/downstream-public-directory-proposal.js +1 -1
- package/dist/{fixtures → example-data}/public-directory-review-resource.d.ts +2 -2
- package/dist/{fixtures → example-data}/public-directory-review-resource.js +2 -2
- package/dist/example-data/public-field-review.d.ts +2 -0
- package/dist/{fixtures → example-data}/public-field-review.js +2 -2
- package/dist/{fixtures → example-data}/regulated-document-review-resource.d.ts +1 -1
- package/dist/{fixtures → example-data}/regulated-document-review-resource.js +3 -3
- package/dist/examples/public-field-observation.js +4 -4
- package/dist/examples/review-workbench/downstream-public-directory-adapter.d.ts +1 -1
- package/dist/examples/review-workbench/facility-credential-consumer.d.ts +2 -2
- package/dist/examples/review-workbench/facility-credential-consumer.js +8 -8
- package/dist/examples/review-workbench/server-apply-consumer.d.ts +1 -1
- package/dist/examples/review-workbench/server-apply-consumer.js +3 -3
- package/dist/src/agent-utterance.d.ts +166 -0
- package/dist/src/agent-utterance.js +373 -0
- package/dist/src/anthropic.d.ts +104 -0
- package/dist/src/anthropic.js +383 -0
- package/dist/src/index.d.ts +8 -2
- package/dist/src/index.js +4 -1
- package/dist/src/inquiry-mapping.d.ts +256 -0
- package/dist/src/inquiry-mapping.js +385 -0
- package/dist/src/review-workbench/review-queue-session.js +3 -3
- package/dist/src/review-workbench/review-surface-preview.js +1 -1
- package/dist/src/review-workbench/review-workbench-data.d.ts +4 -4
- package/dist/src/review-workbench/review-workbench-data.js +14 -14
- package/dist/src/schema-mapping.d.ts +196 -0
- package/dist/src/schema-mapping.js +486 -0
- package/dist/src/to-surface.d.ts +3 -3
- package/dist/src/to-surface.js +1 -1
- package/dist/src/types.d.ts +2 -2
- package/package.json +20 -7
- package/dist/fixtures/corrected-document-candidates.d.ts +0 -2
- 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
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-r
|
|
|
9
9
|
export type { CurrentProposedCandidateRole, ReviewedCurrentProposedResolutionInput, } from "./reviewed-current-proposed-resolution.js";
|
|
10
10
|
export { flowTrustArtifactFromReviewOutcome } from "./to-flow-artifact.js";
|
|
11
11
|
export type { FlowTrustArtifact, FlowTrustArtifactOptions } from "./to-flow-artifact.js";
|
|
12
|
-
export {
|
|
13
|
-
export type {
|
|
12
|
+
export { buildSurveyTrustBundle } from "./to-surface.js";
|
|
13
|
+
export type { BuildSurveyTrustBundleOptions } from "./to-surface.js";
|
|
14
14
|
export { buildSurveyLearningProjections } from "./learning-projections.js";
|
|
15
15
|
export type { LearningProjection, LearningProjectionKind, LearningProjectionSeverity, LearningProjectionSignal, } from "./learning-projections.js";
|
|
16
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";
|
|
@@ -25,3 +25,9 @@ export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildRev
|
|
|
25
25
|
export type { ReviewCandidatePresentation, ReviewCandidatePresentationContext, ReviewItemPresentation, ReviewItemPresentationContext, ReviewPresentationAdapter, ReviewPresentationLink, ReviewResultPresentation, ReviewTracePresentationContext, ReviewTraceRef, ReviewValuePresentationContext, } from "./review-workbench/review-presentation.js";
|
|
26
26
|
export { apiRecordSource, manualEntrySource, policyStandardSource, uploadedDocumentSource, webPageSource, } from "./raw-source.js";
|
|
27
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
|
@@ -3,7 +3,7 @@ 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
5
|
export { flowTrustArtifactFromReviewOutcome } from "./to-flow-artifact.js";
|
|
6
|
-
export {
|
|
6
|
+
export { buildSurveyTrustBundle } from "./to-surface.js";
|
|
7
7
|
export { buildSurveyLearningProjections } from "./learning-projections.js";
|
|
8
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";
|
|
9
9
|
export { fieldObservation } from "./field-observation.js";
|
|
@@ -11,3 +11,6 @@ export { repeatedObservation } from "./repeated-observation.js";
|
|
|
11
11
|
export { sourceOfAuthorityObservation, sourceOfAuthorityObservationBuilder, SourceOfAuthorityObservationBuilder, } from "./source-of-authority-observation.js";
|
|
12
12
|
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-workbench/review-presentation.js";
|
|
13
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;
|