@kontourai/survey 1.3.0 → 1.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 +1 -1
- package/dist/example-data/corrected-document-candidates.js +2 -2
- package/dist/example-data/public-directory-review-resource.d.ts +2 -2
- package/dist/example-data/public-directory-review-resource.js +2 -2
- package/dist/example-data/public-field-review.js +2 -2
- package/dist/example-data/regulated-document-review-resource.d.ts +2 -2
- package/dist/example-data/regulated-document-review-resource.js +2 -2
- package/dist/examples/public-field-observation.js +1 -1
- package/dist/examples/review-workbench/downstream-public-directory-adapter.js +1 -1
- package/dist/src/agent-utterance.d.ts +25 -0
- package/dist/src/agent-utterance.js +207 -95
- package/dist/src/console/review-console-server.js +3 -7
- package/dist/src/field-observation.d.ts +2 -16
- package/dist/src/field-observation.js +2 -14
- package/dist/src/index.d.ts +1 -1
- package/dist/src/inquiry-mapping.d.ts +2 -46
- package/dist/src/inquiry-mapping.js +37 -39
- package/dist/src/mcp/review-mcp.js +12 -12
- package/dist/src/observation-helper.d.ts +47 -1
- package/dist/src/observation-helper.js +41 -2
- package/dist/src/oversight-metrics.d.ts +2 -2
- package/dist/src/oversight-metrics.js +1 -1
- package/dist/src/producer-discipline.d.ts +40 -0
- package/dist/src/producer-discipline.js +13 -0
- package/dist/src/producer-profile.d.ts +134 -0
- package/dist/src/producer-profile.js +124 -0
- package/dist/src/raw-source.d.ts +18 -0
- package/dist/src/raw-source.js +28 -13
- package/dist/src/repeated-observation.d.ts +2 -16
- package/dist/src/repeated-observation.js +2 -14
- package/dist/src/review-proof.d.ts +2 -2
- package/dist/src/review-proof.js +2 -2
- package/dist/src/review-resource.d.ts +5 -1
- package/dist/src/review-workbench/review-workbench-data.d.ts +10 -10
- package/dist/src/review-workbench/review-workbench-data.js +6 -6
- package/dist/src/review-workbench/review-workbench.js +1 -1
- package/dist/src/review-workbench/server-review-session.d.ts +14 -1
- package/dist/src/review-workbench/server-review-session.js +16 -0
- package/dist/src/schema-mapping.js +32 -38
- package/dist/src/source-of-authority-observation.js +6 -9
- package/dist/src/to-surface.js +8 -11
- package/dist/src/types.d.ts +6 -1
- package/dist/src/vocabulary.d.ts +46 -4
- package/dist/src/vocabulary.js +29 -3
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ const surveyInput = new SurveyInputBuilder({
|
|
|
79
79
|
claim: {
|
|
80
80
|
subjectType: "public-record.entity",
|
|
81
81
|
subjectId: "listing-123",
|
|
82
|
-
|
|
82
|
+
facet: "public-record.profile",
|
|
83
83
|
claimType: "public-data.field",
|
|
84
84
|
fieldOrBehavior: "availabilityStatus",
|
|
85
85
|
impactLevel: "medium",
|
|
@@ -143,7 +143,7 @@ function documentClaim(id, target, candidateId, fieldOrBehavior, status) {
|
|
|
143
143
|
candidateId: `document:candidate:${candidateId}`,
|
|
144
144
|
subjectType: "verified-record.period",
|
|
145
145
|
subjectId: "entity-1:2026",
|
|
146
|
-
|
|
146
|
+
facet: "document-review",
|
|
147
147
|
claimType: "source-field",
|
|
148
148
|
fieldOrBehavior,
|
|
149
149
|
status,
|
|
@@ -159,7 +159,7 @@ function computationClaim(id, status, inputClaimIds) {
|
|
|
159
159
|
candidateId: `document:candidate:statement-position:${id}`,
|
|
160
160
|
subjectType: "verified-record.period",
|
|
161
161
|
subjectId: "entity-1:2026",
|
|
162
|
-
|
|
162
|
+
facet: "document-review",
|
|
163
163
|
claimType: "computed-field",
|
|
164
164
|
fieldOrBehavior: "statementPosition",
|
|
165
165
|
status,
|
|
@@ -45,7 +45,7 @@ export declare const publicDirectoryReviewItemExample: {
|
|
|
45
45
|
claimId: string;
|
|
46
46
|
subjectType: string;
|
|
47
47
|
subjectId: string;
|
|
48
|
-
|
|
48
|
+
facet: string;
|
|
49
49
|
claimType: string;
|
|
50
50
|
fieldOrBehavior: string;
|
|
51
51
|
impactLevel: "medium";
|
|
@@ -97,7 +97,7 @@ export declare const publicDirectoryReviewItemExample: {
|
|
|
97
97
|
claimId: string;
|
|
98
98
|
subjectType: string;
|
|
99
99
|
subjectId: string;
|
|
100
|
-
|
|
100
|
+
facet: string;
|
|
101
101
|
claimType: string;
|
|
102
102
|
fieldOrBehavior: string;
|
|
103
103
|
impactLevel: "medium";
|
|
@@ -47,7 +47,7 @@ export const publicDirectoryReviewItemExample = {
|
|
|
47
47
|
claimId: "public-field.entity-123.availability-status.current",
|
|
48
48
|
subjectType: "public-record.entity",
|
|
49
49
|
subjectId: "entity-123",
|
|
50
|
-
|
|
50
|
+
facet: "public-record.profile",
|
|
51
51
|
claimType: "public-data.field",
|
|
52
52
|
fieldOrBehavior: "availabilityStatus",
|
|
53
53
|
impactLevel: "medium",
|
|
@@ -98,7 +98,7 @@ export const publicDirectoryReviewItemExample = {
|
|
|
98
98
|
claimId: "public-field.entity-123.availability-status.proposal-456",
|
|
99
99
|
subjectType: "public-record.entity",
|
|
100
100
|
subjectId: "entity-123",
|
|
101
|
-
|
|
101
|
+
facet: "public-record.profile",
|
|
102
102
|
claimType: "public-data.field-candidate",
|
|
103
103
|
fieldOrBehavior: "availabilityStatus",
|
|
104
104
|
impactLevel: "medium",
|
|
@@ -91,7 +91,7 @@ export const publicFieldReviewExample = {
|
|
|
91
91
|
candidateId: "public-field:candidate:approved",
|
|
92
92
|
subjectType: "public-record.entity",
|
|
93
93
|
subjectId: "entity-123",
|
|
94
|
-
|
|
94
|
+
facet: "public-record.profile",
|
|
95
95
|
claimType: "public-data.field",
|
|
96
96
|
fieldOrBehavior: "availabilityStatus",
|
|
97
97
|
impactLevel: "medium",
|
|
@@ -109,7 +109,7 @@ export const publicFieldReviewExample = {
|
|
|
109
109
|
candidateId: "public-field:candidate:proposal",
|
|
110
110
|
subjectType: "public-record.entity",
|
|
111
111
|
subjectId: "entity-123",
|
|
112
|
-
|
|
112
|
+
facet: "public-record.profile",
|
|
113
113
|
claimType: "public-data.field-candidate",
|
|
114
114
|
fieldOrBehavior: "availabilityStatus",
|
|
115
115
|
impactLevel: "medium",
|
|
@@ -48,7 +48,7 @@ export declare const regulatedDocumentReviewItemExample: {
|
|
|
48
48
|
claimId: string;
|
|
49
49
|
subjectType: string;
|
|
50
50
|
subjectId: string;
|
|
51
|
-
|
|
51
|
+
facet: string;
|
|
52
52
|
claimType: string;
|
|
53
53
|
fieldOrBehavior: string;
|
|
54
54
|
impactLevel: "high";
|
|
@@ -96,7 +96,7 @@ export declare const regulatedDocumentReviewItemExample: {
|
|
|
96
96
|
claimId: string;
|
|
97
97
|
subjectType: string;
|
|
98
98
|
subjectId: string;
|
|
99
|
-
|
|
99
|
+
facet: string;
|
|
100
100
|
claimType: string;
|
|
101
101
|
fieldOrBehavior: string;
|
|
102
102
|
impactLevel: "high";
|
|
@@ -50,7 +50,7 @@ export const regulatedDocumentReviewItemExample = {
|
|
|
50
50
|
claimId: "document.entity-1.statement-position.original",
|
|
51
51
|
subjectType: "verified-record.period",
|
|
52
52
|
subjectId: "entity-1:2026",
|
|
53
|
-
|
|
53
|
+
facet: "document-review",
|
|
54
54
|
claimType: "computed-field",
|
|
55
55
|
fieldOrBehavior: "statementPosition",
|
|
56
56
|
impactLevel: "high",
|
|
@@ -102,7 +102,7 @@ export const regulatedDocumentReviewItemExample = {
|
|
|
102
102
|
claimId: "document.entity-1.statement-position.current",
|
|
103
103
|
subjectType: "verified-record.period",
|
|
104
104
|
subjectId: "entity-1:2026",
|
|
105
|
-
|
|
105
|
+
facet: "document-review",
|
|
106
106
|
claimType: "computed-field",
|
|
107
107
|
fieldOrBehavior: "statementPosition",
|
|
108
108
|
impactLevel: "high",
|
|
@@ -30,7 +30,7 @@ const surveyInput = new SurveyInputBuilder({
|
|
|
30
30
|
claim: {
|
|
31
31
|
subjectType: "public-record.entity",
|
|
32
32
|
subjectId: "entity-1",
|
|
33
|
-
|
|
33
|
+
facet: "public-record.profile",
|
|
34
34
|
claimType: "public-data.field",
|
|
35
35
|
fieldOrBehavior: "availabilityStatus",
|
|
36
36
|
impactLevel: "medium",
|
|
@@ -201,7 +201,7 @@ function candidateClaimTarget(args, claimId) {
|
|
|
201
201
|
claimId,
|
|
202
202
|
subjectType: "public-record.entity",
|
|
203
203
|
subjectId: args.proposal.publicRecordId,
|
|
204
|
-
|
|
204
|
+
facet: "public-directory.profile",
|
|
205
205
|
claimType: args.role === "current" ? "public-data.field" : "public-data.field-candidate",
|
|
206
206
|
fieldOrBehavior: args.field,
|
|
207
207
|
impactLevel: "medium",
|
|
@@ -96,6 +96,30 @@ export interface UtteranceStatementRecords {
|
|
|
96
96
|
candidate: Candidate;
|
|
97
97
|
candidateSet: CandidateSet;
|
|
98
98
|
}
|
|
99
|
+
interface BuildUtteranceRecordsParams {
|
|
100
|
+
sourceId: string;
|
|
101
|
+
utterance: string;
|
|
102
|
+
extracted: ExtractedStatement[];
|
|
103
|
+
extractorName: string;
|
|
104
|
+
observedAt: string;
|
|
105
|
+
}
|
|
106
|
+
interface UtteranceRecordsResult {
|
|
107
|
+
records: UtteranceStatementRecords[];
|
|
108
|
+
extractions: Extraction[];
|
|
109
|
+
candidateSets: CandidateSet[];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Build the full set of Survey records for every extracted statement in one
|
|
113
|
+
* utterance: per-statement Extractions/Candidates plus per-target grouped
|
|
114
|
+
* Candidate Sets. This is the shared orchestrator both `utteranceToSurveyInput`
|
|
115
|
+
* and `surveyAgentUtterance` call, so both callers derive these records
|
|
116
|
+
* identically — Slice 1's "single derivation path" invariant, preserved.
|
|
117
|
+
*
|
|
118
|
+
* `records[idx]` corresponds to `extracted[idx]` for every idx — `items` and
|
|
119
|
+
* `records` are both built via `.map` over the same `extracted[]` array in
|
|
120
|
+
* the same order; only `candidateSets` is deduped/grouped by target.
|
|
121
|
+
*/
|
|
122
|
+
export declare function buildUtteranceRecords(params: BuildUtteranceRecordsParams): UtteranceRecordsResult;
|
|
99
123
|
/**
|
|
100
124
|
* Project an agent utterance and its extracted statements into the standard
|
|
101
125
|
* SurveyInput shape so they can flow into buildSurveyTrustBundle.
|
|
@@ -164,3 +188,4 @@ export declare function surveyAgentUtterance(utterance: string, extractor: Utter
|
|
|
164
188
|
* alone in this reference implementation.
|
|
165
189
|
*/
|
|
166
190
|
export declare const referenceUtteranceExtractor: UtteranceClaimExtractor;
|
|
191
|
+
export {};
|
|
@@ -18,6 +18,177 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { resolveInquiry } from "@kontourai/surface";
|
|
20
20
|
import { lookupMapping, resolveQuestion } from "./inquiry-mapping.js";
|
|
21
|
+
import { projectProposalsToCandidateSet } from "./producer-profile.js";
|
|
22
|
+
/**
|
|
23
|
+
* The Candidate Conflict comparison key for an utterance-proposed value.
|
|
24
|
+
*
|
|
25
|
+
* Neither extractor (the reference extractor below, nor the Anthropic-backed
|
|
26
|
+
* one in `./anthropic.js`) normalizes `value` before it reaches
|
|
27
|
+
* `ExtractedStatement` — case and internal formatting are preserved
|
|
28
|
+
* verbatim. String values are the only case where "representation noise"
|
|
29
|
+
* (leading/trailing whitespace from excerpt boundaries, incidental case
|
|
30
|
+
* differences like "Healthy" vs "healthy") is plausible given the two
|
|
31
|
+
* extractors' actual output, so this key trims + lowercases STRING values
|
|
32
|
+
* in the COMPARISON KEY ONLY — the stored `Candidate.value`/`Extraction.value`
|
|
33
|
+
* stay byte-for-byte verbatim; this function only feeds `equivalenceKey`,
|
|
34
|
+
* never `value`. Non-string values (number, boolean, null — the other types
|
|
35
|
+
* the Anthropic tool schema permits) compare via exact canonical
|
|
36
|
+
* `JSON.stringify`, so there is no cross-type coercion that could silently
|
|
37
|
+
* equate e.g. "5" and 5, or lose a genuine numeric disagreement (5 vs 6 is
|
|
38
|
+
* never noise). This mirrors the Producer Profile core's established
|
|
39
|
+
* pattern: each profile decides its own narrow equivalence definition
|
|
40
|
+
* (see `./producer-profile.js`); the core itself does not own this decision.
|
|
41
|
+
*/
|
|
42
|
+
function utteranceEquivalenceKey(value) {
|
|
43
|
+
const normalized = value ?? null;
|
|
44
|
+
if (typeof normalized === "string") {
|
|
45
|
+
return `str:${normalized.trim().toLowerCase()}`;
|
|
46
|
+
}
|
|
47
|
+
return `json:${JSON.stringify(normalized)}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build the Extraction and CandidateSetProposal for a single extracted
|
|
51
|
+
* statement. This centralizes the Source Locator rule (span-first,
|
|
52
|
+
* excerpt-fallback — the single locator rule this module guarantees) and
|
|
53
|
+
* hands the resulting proposal off to `groupUtteranceExtractionsByTarget`
|
|
54
|
+
* for per-target projection through the Producer Profile core.
|
|
55
|
+
*/
|
|
56
|
+
function buildUtteranceExtraction(params) {
|
|
57
|
+
const { sourceId, idx, statement, utterance, extractorName, observedAt } = params;
|
|
58
|
+
const statementId = `${sourceId}.statement.${idx}`;
|
|
59
|
+
const extractionId = `${statementId}.extraction`;
|
|
60
|
+
const candidateId = `${statementId}.candidate`;
|
|
61
|
+
// Compute locator — required for non-manual-entry sources
|
|
62
|
+
// (assertProducerDiscipline throws without it). Source Locator rule:
|
|
63
|
+
// span-first, excerpt-fallback — UNCHANGED from Slice 1.
|
|
64
|
+
const locator = spanToLocator(statement.span) ?? excerptLocator(utterance, statement.excerpt);
|
|
65
|
+
const extraction = {
|
|
66
|
+
id: extractionId,
|
|
67
|
+
sourceId,
|
|
68
|
+
target: canonicalTargetKey(statement.target),
|
|
69
|
+
value: statement.value ?? null,
|
|
70
|
+
confidence: statement.confidence,
|
|
71
|
+
locator,
|
|
72
|
+
excerpt: statement.excerpt,
|
|
73
|
+
extractor: extractorName,
|
|
74
|
+
extractedAt: observedAt,
|
|
75
|
+
metadata: {
|
|
76
|
+
agentUtterance: {
|
|
77
|
+
span: statement.span,
|
|
78
|
+
excerpt: statement.excerpt,
|
|
79
|
+
extractorName,
|
|
80
|
+
confidence: statement.confidence,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const proposal = {
|
|
85
|
+
candidateId,
|
|
86
|
+
extractionId,
|
|
87
|
+
value: statement.value ?? null,
|
|
88
|
+
confidence: statement.confidence,
|
|
89
|
+
equivalenceKey: utteranceEquivalenceKey(statement.value),
|
|
90
|
+
metadata: {
|
|
91
|
+
span: statement.span,
|
|
92
|
+
excerpt: statement.excerpt,
|
|
93
|
+
extractorName,
|
|
94
|
+
confidence: statement.confidence,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
return { extraction, proposal };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Group extraction/proposal pairs by canonical target and project each
|
|
101
|
+
* group through the Producer Profile core's `projectProposalsToCandidateSet`
|
|
102
|
+
* — one Candidate Set per target, carrying every statement's Candidate for
|
|
103
|
+
* that target. Status is `"conflict"` when the group's statements disagree
|
|
104
|
+
* under `utteranceEquivalenceKey`, `"needs-review"` otherwise (including the
|
|
105
|
+
* common single-statement case, which reproduces Slice 1's exact prior
|
|
106
|
+
* per-statement behavior).
|
|
107
|
+
*
|
|
108
|
+
* `Map` preserves insertion order, so the returned groups (and therefore the
|
|
109
|
+
* `candidateSets` array `buildUtteranceRecords` derives from them) are in
|
|
110
|
+
* deterministic first-occurrence-of-target order across the utterance's
|
|
111
|
+
* statements.
|
|
112
|
+
*/
|
|
113
|
+
function groupUtteranceExtractionsByTarget(sourceId, items) {
|
|
114
|
+
const order = [];
|
|
115
|
+
const byTarget = new Map();
|
|
116
|
+
for (const item of items) {
|
|
117
|
+
const key = canonicalTargetKey(item.statement.target);
|
|
118
|
+
if (!byTarget.has(key)) {
|
|
119
|
+
byTarget.set(key, []);
|
|
120
|
+
order.push(key);
|
|
121
|
+
}
|
|
122
|
+
byTarget.get(key).push(item);
|
|
123
|
+
}
|
|
124
|
+
const groups = new Map();
|
|
125
|
+
for (const targetKey of order) {
|
|
126
|
+
const groupItems = byTarget.get(targetKey);
|
|
127
|
+
const first = groupItems[0].statement.target;
|
|
128
|
+
const proposals = groupItems.map((i) => i.proposal);
|
|
129
|
+
const { candidateSet, candidates } = projectProposalsToCandidateSet(targetKey, proposals, {
|
|
130
|
+
candidateSetId: `${sourceId}.target.${targetKey}.candidate-set`,
|
|
131
|
+
candidateSetMetadata: {
|
|
132
|
+
agentUtterance: {
|
|
133
|
+
target: {
|
|
134
|
+
subjectType: first.subjectType,
|
|
135
|
+
subjectId: first.subjectId,
|
|
136
|
+
fieldOrBehavior: first.fieldOrBehavior,
|
|
137
|
+
},
|
|
138
|
+
statementCount: proposals.length,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
candidateSetRationale: (status, groupProposals) => status === "conflict"
|
|
142
|
+
? `${groupProposals.length} statement(s) disagree for ${targetKey}: ${[...new Set(groupProposals.map((p) => p.equivalenceKey))].join(", ")}`
|
|
143
|
+
: `${groupProposals.length} statement(s) agree for ${targetKey}.`,
|
|
144
|
+
});
|
|
145
|
+
// Mirrors schema-mapping's post-core convention: a winner only exists
|
|
146
|
+
// when the group agrees; a conflicting group has no selected candidate
|
|
147
|
+
// yet (nothing to select — that's the point of a Candidate Conflict,
|
|
148
|
+
// CONTEXT.md's "Candidate Conflict" entry). For a single-statement group
|
|
149
|
+
// this reproduces Slice 1's exact old behavior (selectedCandidateId ===
|
|
150
|
+
// the one candidate's id).
|
|
151
|
+
candidateSet.selectedCandidateId = candidateSet.status !== "conflict" ? candidates[0]?.id : undefined;
|
|
152
|
+
groups.set(targetKey, { targetKey, candidateSet, candidates });
|
|
153
|
+
}
|
|
154
|
+
return groups;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Build the full set of Survey records for every extracted statement in one
|
|
158
|
+
* utterance: per-statement Extractions/Candidates plus per-target grouped
|
|
159
|
+
* Candidate Sets. This is the shared orchestrator both `utteranceToSurveyInput`
|
|
160
|
+
* and `surveyAgentUtterance` call, so both callers derive these records
|
|
161
|
+
* identically — Slice 1's "single derivation path" invariant, preserved.
|
|
162
|
+
*
|
|
163
|
+
* `records[idx]` corresponds to `extracted[idx]` for every idx — `items` and
|
|
164
|
+
* `records` are both built via `.map` over the same `extracted[]` array in
|
|
165
|
+
* the same order; only `candidateSets` is deduped/grouped by target.
|
|
166
|
+
*/
|
|
167
|
+
export function buildUtteranceRecords(params) {
|
|
168
|
+
const { sourceId, utterance, extracted, extractorName, observedAt } = params;
|
|
169
|
+
const items = extracted.map((statement, idx) => {
|
|
170
|
+
const { extraction, proposal } = buildUtteranceExtraction({
|
|
171
|
+
sourceId,
|
|
172
|
+
idx,
|
|
173
|
+
statement,
|
|
174
|
+
utterance,
|
|
175
|
+
extractorName,
|
|
176
|
+
observedAt,
|
|
177
|
+
});
|
|
178
|
+
return { statement, extraction, proposal };
|
|
179
|
+
});
|
|
180
|
+
const groups = groupUtteranceExtractionsByTarget(sourceId, items);
|
|
181
|
+
const records = items.map((item) => {
|
|
182
|
+
const group = groups.get(canonicalTargetKey(item.statement.target));
|
|
183
|
+
const candidate = group.candidates.find((c) => c.id === item.proposal.candidateId);
|
|
184
|
+
return { extraction: item.extraction, candidate, candidateSet: group.candidateSet };
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
records,
|
|
188
|
+
extractions: items.map((i) => i.extraction),
|
|
189
|
+
candidateSets: [...groups.values()].map((g) => g.candidateSet),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
21
192
|
// ---------------------------------------------------------------------------
|
|
22
193
|
// SurveyInput projection
|
|
23
194
|
// ---------------------------------------------------------------------------
|
|
@@ -60,80 +231,35 @@ export function utteranceToSurveyInput(utterance, extracted, context) {
|
|
|
60
231
|
inlineText: utterance,
|
|
61
232
|
metadata: { agentId },
|
|
62
233
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
234
|
+
// Batched, per-target-grouped provenance construction (Producer Profile
|
|
235
|
+
// core) — replaces the old per-statement builder call. Claims below stay
|
|
236
|
+
// one-per-statement; `record.candidateSet.id`/`record.candidate.id` may be
|
|
237
|
+
// shared across several claims when statements share a target (legal).
|
|
238
|
+
const { records, extractions, candidateSets } = buildUtteranceRecords({
|
|
239
|
+
sourceId,
|
|
240
|
+
utterance,
|
|
241
|
+
extracted,
|
|
242
|
+
extractorName,
|
|
243
|
+
observedAt,
|
|
244
|
+
});
|
|
245
|
+
const claims = extracted.map((statement, idx) => {
|
|
68
246
|
const statementId = `${sourceId}.statement.${idx}`;
|
|
69
|
-
const extractionId = `${statementId}.extraction`;
|
|
70
|
-
const candidateId = `${statementId}.candidate`;
|
|
71
|
-
const candidateSetId = `${statementId}.candidate-set`;
|
|
72
247
|
const claimId = `${statementId}.claim`;
|
|
73
|
-
|
|
74
|
-
// (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
sourceId,
|
|
79
|
-
target: canonicalTargetKey(statement.target),
|
|
80
|
-
value: statement.value ?? null,
|
|
81
|
-
confidence: statement.confidence,
|
|
82
|
-
locator,
|
|
83
|
-
excerpt: statement.excerpt,
|
|
84
|
-
extractor: extractorName,
|
|
85
|
-
extractedAt: observedAt,
|
|
86
|
-
metadata: {
|
|
87
|
-
agentUtterance: {
|
|
88
|
-
span: statement.span,
|
|
89
|
-
excerpt: statement.excerpt,
|
|
90
|
-
extractorName,
|
|
91
|
-
confidence: statement.confidence,
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
const candidate = {
|
|
96
|
-
id: candidateId,
|
|
97
|
-
extractionId,
|
|
98
|
-
value: statement.value ?? null,
|
|
99
|
-
confidence: statement.confidence,
|
|
100
|
-
metadata: {
|
|
101
|
-
agentUtterance: {
|
|
102
|
-
span: statement.span,
|
|
103
|
-
excerpt: statement.excerpt,
|
|
104
|
-
extractorName,
|
|
105
|
-
confidence: statement.confidence,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
// needs-review status → statusFor returns "proposed" (no review outcome)
|
|
110
|
-
const candidateSet = {
|
|
111
|
-
id: candidateSetId,
|
|
112
|
-
target: canonicalTargetKey(statement.target),
|
|
113
|
-
candidates: [candidate],
|
|
114
|
-
selectedCandidateId: candidateId,
|
|
115
|
-
status: "needs-review",
|
|
116
|
-
metadata: {
|
|
117
|
-
agentUtterance: {
|
|
118
|
-
subjectType: statement.target.subjectType,
|
|
119
|
-
subjectId: statement.target.subjectId,
|
|
120
|
-
fieldOrBehavior: statement.target.fieldOrBehavior,
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
// Unreviewed: status is omitted so statusFor() computes "proposed"
|
|
125
|
-
// assertProducerDiscipline: no verified/assumed without review → compliant
|
|
126
|
-
const claimTarget = {
|
|
248
|
+
const record = records[idx];
|
|
249
|
+
// Unreviewed: status is omitted so statusFor() computes "proposed" (or
|
|
250
|
+
// "disputed" for a claim whose shared candidateSet.status is "conflict").
|
|
251
|
+
// assertProducerDiscipline: no verified/assumed without review → compliant.
|
|
252
|
+
return {
|
|
127
253
|
id: claimId,
|
|
128
|
-
candidateSetId,
|
|
129
|
-
candidateId,
|
|
254
|
+
candidateSetId: record.candidateSet.id,
|
|
255
|
+
candidateId: record.candidate.id,
|
|
130
256
|
subjectType: statement.target.subjectType,
|
|
131
257
|
subjectId: statement.target.subjectId,
|
|
132
|
-
|
|
258
|
+
facet: "agent-utterance.profile",
|
|
133
259
|
claimType: "agent-extraction",
|
|
134
260
|
fieldOrBehavior: statement.target.fieldOrBehavior,
|
|
135
261
|
value: statement.value,
|
|
136
|
-
// status intentionally omitted → computed
|
|
262
|
+
// status intentionally omitted → computed by statusFor
|
|
137
263
|
impactLevel: "low",
|
|
138
264
|
collectedBy: extractorName,
|
|
139
265
|
metadata: {
|
|
@@ -144,15 +270,12 @@ export function utteranceToSurveyInput(utterance, extracted, context) {
|
|
|
144
270
|
excerpt: statement.excerpt,
|
|
145
271
|
span: statement.span,
|
|
146
272
|
confidence: statement.confidence,
|
|
147
|
-
locator,
|
|
273
|
+
locator: record.extraction.locator,
|
|
148
274
|
},
|
|
149
275
|
},
|
|
150
276
|
},
|
|
151
277
|
};
|
|
152
|
-
|
|
153
|
-
candidateSets.push(candidateSet);
|
|
154
|
-
claims.push(claimTarget);
|
|
155
|
-
}
|
|
278
|
+
});
|
|
156
279
|
return {
|
|
157
280
|
source,
|
|
158
281
|
generatedAt: observedAt,
|
|
@@ -196,31 +319,22 @@ export async function surveyAgentUtterance(utterance, extractor, context) {
|
|
|
196
319
|
};
|
|
197
320
|
// Step 2: Extract statements
|
|
198
321
|
const extracted = await Promise.resolve(extractor.extract(utterance));
|
|
322
|
+
// Batched, grouped provenance construction — kept for provenance/doc-comment
|
|
323
|
+
// fidelity across the whole utterance (grouping needs every statement of a
|
|
324
|
+
// target's group present at once; a per-statement call cannot compute it).
|
|
325
|
+
// Still not surfaced on UtteranceStatement/UtteranceTrustReport this slice
|
|
326
|
+
// (unchanged from Slice 1's own scoping note) — wiring is left for a future
|
|
327
|
+
// slice, same as before.
|
|
328
|
+
buildUtteranceRecords({
|
|
329
|
+
sourceId,
|
|
330
|
+
utterance,
|
|
331
|
+
extracted,
|
|
332
|
+
extractorName: extractor.name,
|
|
333
|
+
observedAt,
|
|
334
|
+
});
|
|
199
335
|
// Step 3 & 4: Resolve each statement and build the report
|
|
200
336
|
const statements = [];
|
|
201
337
|
for (const statement of extracted) {
|
|
202
|
-
const statementId = `${sourceId}.statement.${statements.length}`;
|
|
203
|
-
// Build Survey records for provenance
|
|
204
|
-
const extractionId = `${statementId}.extraction`;
|
|
205
|
-
const extraction = {
|
|
206
|
-
id: extractionId,
|
|
207
|
-
sourceId,
|
|
208
|
-
target: canonicalTargetKey(statement.target),
|
|
209
|
-
value: statement.value ?? null,
|
|
210
|
-
confidence: statement.confidence,
|
|
211
|
-
locator: statement.span ? `text-span:${statement.span.start}-${statement.span.end}` : undefined,
|
|
212
|
-
excerpt: statement.excerpt,
|
|
213
|
-
extractor: extractor.name,
|
|
214
|
-
extractedAt: observedAt,
|
|
215
|
-
metadata: {
|
|
216
|
-
agentUtterance: {
|
|
217
|
-
span: statement.span,
|
|
218
|
-
excerpt: statement.excerpt,
|
|
219
|
-
extractorName: extractor.name,
|
|
220
|
-
confidence: statement.confidence,
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
338
|
// Resolve the claim
|
|
225
339
|
let inquiryRecord;
|
|
226
340
|
if (mappings && mappings.length > 0) {
|
|
@@ -253,8 +367,6 @@ export async function surveyAgentUtterance(utterance, extractor, context) {
|
|
|
253
367
|
inquiryRecord,
|
|
254
368
|
badge,
|
|
255
369
|
});
|
|
256
|
-
// Suppress unused-variable warning for extraction
|
|
257
|
-
void extraction;
|
|
258
370
|
}
|
|
259
371
|
return { source, statements };
|
|
260
372
|
}
|
|
@@ -15,8 +15,8 @@ import { watch } from "node:fs";
|
|
|
15
15
|
import { readFile as readFileAsync, writeFile as writeFileAsync, rename as renameAsync } from "node:fs/promises";
|
|
16
16
|
import { resolve, join, dirname, extname, normalize, sep } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
|
-
import {
|
|
19
|
-
import { createServerReviewSessionRecord, deriveServerReviewSessionApplyResult, } from "../review-workbench/server-review-session.js";
|
|
18
|
+
import { defaultReviewSessionName, } from "../review-workbench/review-workbench.js";
|
|
19
|
+
import { createServerReviewSessionRecord, currentSessionState, deriveServerReviewSessionApplyResult, } from "../review-workbench/server-review-session.js";
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
21
|
// Constants
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
@@ -63,10 +63,6 @@ async function writeSessionAtomic(path, content) {
|
|
|
63
63
|
await writeFileAsync(tmp, JSON.stringify(content, null, 2), "utf8");
|
|
64
64
|
await renameAsync(tmp, path);
|
|
65
65
|
}
|
|
66
|
-
function currentState(content) {
|
|
67
|
-
const { snapshot, events } = content;
|
|
68
|
-
return events.length > 0 ? replayReviewSessionEvents(snapshot, events) : snapshot;
|
|
69
|
-
}
|
|
70
66
|
// ---------------------------------------------------------------------------
|
|
71
67
|
// SSE broadcaster
|
|
72
68
|
// ---------------------------------------------------------------------------
|
|
@@ -496,7 +492,7 @@ export async function startReviewConsoleServer(options) {
|
|
|
496
492
|
session: content.session,
|
|
497
493
|
snapshot: content.snapshot,
|
|
498
494
|
events: content.events,
|
|
499
|
-
state:
|
|
495
|
+
state: currentSessionState(content.snapshot, content.events),
|
|
500
496
|
});
|
|
501
497
|
return;
|
|
502
498
|
}
|
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import type { SurveyObservationInput } from "./builder.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
field: string;
|
|
5
|
-
value: TValue;
|
|
6
|
-
rawSource: SurveyObservationInput["rawSource"];
|
|
7
|
-
extraction: Omit<SurveyObservationInput["extraction"], "target" | "value" | "excerpt"> & {
|
|
8
|
-
target?: string;
|
|
9
|
-
excerpt?: string | null;
|
|
10
|
-
};
|
|
11
|
-
reviewOutcome?: SurveyObservationInput["reviewOutcome"];
|
|
12
|
-
claim: Omit<SurveyObservationInput["claim"], "fieldOrBehavior" | "value"> & {
|
|
13
|
-
fieldOrBehavior?: string;
|
|
14
|
-
};
|
|
15
|
-
candidate?: SurveyObservationInput["candidate"];
|
|
16
|
-
candidateSet?: SurveyObservationInput["candidateSet"];
|
|
2
|
+
import { type ObservationAuthoringInput } from "./observation-helper.js";
|
|
3
|
+
export interface FieldObservationInput<TValue> extends ObservationAuthoringInput<TValue> {
|
|
17
4
|
representation?: "scalar";
|
|
18
|
-
metadata?: Record<string, unknown>;
|
|
19
5
|
}
|
|
20
6
|
export declare function fieldObservation<TValue>(input: FieldObservationInput<TValue>): SurveyObservationInput;
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildFieldObservation } from "./observation-helper.js";
|
|
2
2
|
export function fieldObservation(input) {
|
|
3
|
-
|
|
4
|
-
return buildObservation({
|
|
5
|
-
...input,
|
|
6
|
-
surveyMetadata: {
|
|
7
|
-
field: { representation },
|
|
8
|
-
},
|
|
9
|
-
defaultExcerpt: `${input.field}: ${valueSummary(input.value)}`,
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
function valueSummary(value) {
|
|
13
|
-
if (value === null || value === undefined)
|
|
14
|
-
return "<empty>";
|
|
15
|
-
return String(value);
|
|
3
|
+
return buildFieldObservation(input);
|
|
16
4
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type { MappingProposalRecord, ReviewedMapping, SchemaMappingExtractor, Sc
|
|
|
33
33
|
export { buildAuthorizedActionAuthorizing, buildPromptRef, isValidAuthorizing, validateAuthorizing } from "./review-authorizing.js";
|
|
34
34
|
export type { BuildAuthorizedActionAuthorizingInput, BuildPromptRefInput, ReviewAuthorizingIssue, ReviewAuthorizingIssueCode, } from "./review-authorizing.js";
|
|
35
35
|
export { confidenceBasisForReview, defineProductVocabulary, stableId } from "./vocabulary.js";
|
|
36
|
-
export type { ConfidenceBasisForReviewInput, ProductVocabularyDefinition, } from "./vocabulary.js";
|
|
36
|
+
export type { ConfidenceBasisForReviewInput, ProductVocabularyDefinition, ProductVocabularyInput, } from "./vocabulary.js";
|
|
37
37
|
export { currentProposedReviewItem } from "./current-proposed-review-item.js";
|
|
38
38
|
export type { CurrentProposedCandidateInput, CurrentProposedReviewItemInput, } from "./current-proposed-review-item.js";
|
|
39
39
|
export { deriveOversightMetrics, mergeTrustBundleWithOversightMetrics, oversightMetricsToClaims, } from "./oversight-metrics.js";
|