@kontourai/survey 1.4.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/dist/src/agent-utterance.d.ts +25 -0
- package/dist/src/agent-utterance.js +206 -94
- 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/inquiry-mapping.d.ts +2 -46
- package/dist/src/inquiry-mapping.js +36 -38
- 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/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-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 +30 -36
- package/dist/src/source-of-authority-observation.js +6 -9
- package/dist/src/to-surface.js +6 -9
- package/package.json +5 -2
|
@@ -2,7 +2,7 @@ import { type DeriveReviewSessionApplyResultForSnapshotResult, type MapReviewWor
|
|
|
2
2
|
import { type ReviewDecisionModeIssue } from "./producer-decision-mode.js";
|
|
3
3
|
import { type ReviewSessionReplayIssue } from "./review-session-replay.js";
|
|
4
4
|
import type { ReviewDecision, ReviewSessionEvent } from "../review-resource.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type ReviewQueueSessionState } from "./review-queue-session.js";
|
|
6
6
|
export interface ServerReviewSessionRecord {
|
|
7
7
|
readonly sessionName: string;
|
|
8
8
|
readonly snapshot: ReviewQueueSessionState;
|
|
@@ -59,6 +59,19 @@ export interface DeriveServerReviewSessionApplyResultOptions {
|
|
|
59
59
|
}
|
|
60
60
|
export declare function createServerReviewSessionRecord(options: CreateServerReviewSessionRecordOptions): ServerReviewSessionRecord;
|
|
61
61
|
export declare function hashReviewSessionSnapshot(snapshot: ReviewQueueSessionState): string;
|
|
62
|
+
/**
|
|
63
|
+
* Project the current review session state from a snapshot plus its
|
|
64
|
+
* append-only event log: replay events over the snapshot when any exist,
|
|
65
|
+
* otherwise return the snapshot itself unchanged (same object reference —
|
|
66
|
+
* no eager replay, no copy). Collapses the
|
|
67
|
+
* `events.length > 0 ? replayReviewSessionEvents(snapshot, events) : snapshot`
|
|
68
|
+
* conditional that was duplicated across the MCP and console review
|
|
69
|
+
* adapters (src/mcp/review-mcp.ts, src/console/review-console-server.ts) —
|
|
70
|
+
* same shape of fix as canonicalJson in ./canonical.ts, which was
|
|
71
|
+
* extracted after two near-identical copies of a derivation desynced
|
|
72
|
+
* (audit 2026-06-28, ops#24).
|
|
73
|
+
*/
|
|
74
|
+
export declare function currentSessionState(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewQueueSessionState;
|
|
62
75
|
export declare function compareServerReviewSessionFreshness(record: ServerReviewSessionRecord, snapshot: ReviewQueueSessionState, eventCount?: number): ServerReviewSessionFreshnessComparison;
|
|
63
76
|
export declare function assertServerReviewSessionFreshness(record: ServerReviewSessionRecord, snapshot: ReviewQueueSessionState, eventCount?: number): void;
|
|
64
77
|
export declare function validateServerReviewSessionEvents(record: ServerReviewSessionRecord, events: readonly ReviewSessionEvent[]): ServerReviewSessionEventValidationIssue[];
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { deriveReviewSessionApplyResultForSnapshot, mapReviewWorkbenchResultsToApplyActions, ReviewApplyActionMappingError, } from "./review-workbench.js";
|
|
3
3
|
import { validateReviewDecisionMode, } from "./producer-decision-mode.js";
|
|
4
4
|
import { validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
5
|
+
import { replayReviewSessionEvents } from "./review-queue-session.js";
|
|
5
6
|
import { canonicalJson } from "./canonical.js";
|
|
6
7
|
export class StaleServerReviewSessionError extends Error {
|
|
7
8
|
name = "StaleServerReviewSessionError";
|
|
@@ -34,6 +35,21 @@ export function createServerReviewSessionRecord(options) {
|
|
|
34
35
|
export function hashReviewSessionSnapshot(snapshot) {
|
|
35
36
|
return sha256(canonicalJson(snapshot));
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Project the current review session state from a snapshot plus its
|
|
40
|
+
* append-only event log: replay events over the snapshot when any exist,
|
|
41
|
+
* otherwise return the snapshot itself unchanged (same object reference —
|
|
42
|
+
* no eager replay, no copy). Collapses the
|
|
43
|
+
* `events.length > 0 ? replayReviewSessionEvents(snapshot, events) : snapshot`
|
|
44
|
+
* conditional that was duplicated across the MCP and console review
|
|
45
|
+
* adapters (src/mcp/review-mcp.ts, src/console/review-console-server.ts) —
|
|
46
|
+
* same shape of fix as canonicalJson in ./canonical.ts, which was
|
|
47
|
+
* extracted after two near-identical copies of a derivation desynced
|
|
48
|
+
* (audit 2026-06-28, ops#24).
|
|
49
|
+
*/
|
|
50
|
+
export function currentSessionState(snapshot, events) {
|
|
51
|
+
return events.length > 0 ? replayReviewSessionEvents(snapshot, events) : snapshot;
|
|
52
|
+
}
|
|
37
53
|
export function compareServerReviewSessionFreshness(record, snapshot, eventCount) {
|
|
38
54
|
const actualSnapshotHash = hashReviewSessionSnapshot(snapshot);
|
|
39
55
|
const eventCountMatches = record.eventCount === undefined || eventCount === undefined || record.eventCount === eventCount;
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* so that resolveInquiry can resolve across systems with weakest-link
|
|
21
21
|
* capping.
|
|
22
22
|
*/
|
|
23
|
+
import { AUTO_ACCEPT_ACTOR, AUTO_ACCEPT_WITHIN_COMFORT_ZONE, getProducerProposal, meetsAutoAcceptThreshold, projectProposalsToCandidateSet, } from "./producer-profile.js";
|
|
23
24
|
import { buildSurveyTrustBundle } from "./to-surface.js";
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
// Canonical pair key
|
|
@@ -84,10 +85,7 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
84
85
|
const subjectId = mappingSubjectId(first.sourceField, first.targetField);
|
|
85
86
|
const candidateSetId = `schema-mapping.candidate-set.${pairKey}`;
|
|
86
87
|
const claimId = `schema-mapping.claim.${pairKey}`;
|
|
87
|
-
|
|
88
|
-
const relations = new Set(pairProposals.map((p) => p.relation));
|
|
89
|
-
const status = relations.size > 1 ? "conflict" : "needs-review";
|
|
90
|
-
const candidates = pairProposals.map((proposal) => {
|
|
88
|
+
const candidateSetProposals = pairProposals.map((proposal) => {
|
|
91
89
|
// Use the source system's RawSource for this extraction
|
|
92
90
|
const rawSource = sourceById.get(proposal.sourceField.system) ?? rawSources[0];
|
|
93
91
|
const extractionId = `schema-mapping.extraction.${proposal.id}`;
|
|
@@ -120,7 +118,7 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
120
118
|
};
|
|
121
119
|
extractions.push(extraction);
|
|
122
120
|
return {
|
|
123
|
-
|
|
121
|
+
candidateId: `schema-mapping.candidate.${proposal.id}`,
|
|
124
122
|
extractionId,
|
|
125
123
|
value: {
|
|
126
124
|
relation: proposal.relation,
|
|
@@ -128,45 +126,41 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
128
126
|
conversion: proposal.conversion,
|
|
129
127
|
},
|
|
130
128
|
confidence: proposal.confidence,
|
|
129
|
+
equivalenceKey: proposal.relation,
|
|
131
130
|
metadata: {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
proposedAt: proposal.proposedAt,
|
|
143
|
-
},
|
|
131
|
+
proposalId: proposal.id,
|
|
132
|
+
sourceField: proposal.sourceField,
|
|
133
|
+
targetField: proposal.targetField,
|
|
134
|
+
relation: proposal.relation,
|
|
135
|
+
conversion: proposal.conversion,
|
|
136
|
+
evidence: proposal.evidence,
|
|
137
|
+
confidence: proposal.confidence,
|
|
138
|
+
rationale: proposal.rationale,
|
|
139
|
+
proposedBy: proposal.proposedBy,
|
|
140
|
+
proposedAt: proposal.proposedAt,
|
|
144
141
|
},
|
|
145
142
|
};
|
|
146
143
|
});
|
|
147
|
-
const candidateSet = {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
candidates,
|
|
151
|
-
selectedCandidateId: status !== "conflict" ? candidates[0]?.id : undefined,
|
|
152
|
-
status,
|
|
153
|
-
rationale: status === "conflict"
|
|
154
|
-
? `Proposals disagree on relation for pair ${pairKey}: ${[...relations].join(", ")}`
|
|
155
|
-
: `${candidates.length} proposal(s) agree on relation "${first.relation}" for pair ${pairKey}.`,
|
|
156
|
-
metadata: {
|
|
144
|
+
const { candidateSet, candidates } = projectProposalsToCandidateSet(`schema-mapping:${pairKey}`, candidateSetProposals, {
|
|
145
|
+
candidateSetId,
|
|
146
|
+
candidateSetMetadata: {
|
|
157
147
|
schemaMapping: {
|
|
158
148
|
pairKey,
|
|
159
149
|
sourceField: first.sourceField,
|
|
160
150
|
targetField: first.targetField,
|
|
161
151
|
},
|
|
162
152
|
},
|
|
163
|
-
|
|
153
|
+
candidateSetRationale: (status, proposals) => status === "conflict"
|
|
154
|
+
? `Proposals disagree on relation for pair ${pairKey}: ${[...new Set(proposals.map((p) => p.equivalenceKey))].join(", ")}`
|
|
155
|
+
: `${proposals.length} proposal(s) agree on relation "${first.relation}" for pair ${pairKey}.`,
|
|
156
|
+
});
|
|
157
|
+
candidateSet.selectedCandidateId = candidateSet.status !== "conflict" ? candidates[0]?.id : undefined;
|
|
164
158
|
candidateSets.push(candidateSet);
|
|
165
159
|
// Auto-accept policy: non-conflicting proposals above threshold → assumed
|
|
166
160
|
let autoReviewStatus;
|
|
167
|
-
if (status !== "conflict" && options.autoAcceptMinConfidence !== undefined) {
|
|
161
|
+
if (candidateSet.status !== "conflict" && options.autoAcceptMinConfidence !== undefined) {
|
|
168
162
|
const topConfidence = Math.max(...pairProposals.map((p) => p.confidence));
|
|
169
|
-
if (topConfidence
|
|
163
|
+
if (meetsAutoAcceptThreshold(topConfidence, options.autoAcceptMinConfidence)) {
|
|
170
164
|
autoReviewStatus = "assumed";
|
|
171
165
|
}
|
|
172
166
|
}
|
|
@@ -180,10 +174,10 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
180
174
|
candidateSetId,
|
|
181
175
|
candidateId: selectedCandidate.id,
|
|
182
176
|
status: autoReviewStatus,
|
|
183
|
-
actor:
|
|
177
|
+
actor: AUTO_ACCEPT_ACTOR,
|
|
184
178
|
reviewedAt: generatedAt,
|
|
185
179
|
rationale: `Auto-accepted: confidence ${selectedCandidate.confidence} >= threshold ${options.autoAcceptMinConfidence}`,
|
|
186
|
-
withinComfortZone:
|
|
180
|
+
withinComfortZone: AUTO_ACCEPT_WITHIN_COMFORT_ZONE,
|
|
187
181
|
});
|
|
188
182
|
}
|
|
189
183
|
// Project to ClaimTarget (subjectType "system-field", fieldOrBehavior "maps-to")
|
|
@@ -191,7 +185,7 @@ export async function surveySchemaMapping(context, extractor, options = {}) {
|
|
|
191
185
|
const review = reviewOutcomes.find((r) => r.candidateSetId === candidateSetId);
|
|
192
186
|
const claimStatus = review
|
|
193
187
|
? review.status
|
|
194
|
-
: (status === "conflict" ? "disputed" : undefined);
|
|
188
|
+
: (candidateSet.status === "conflict" ? "disputed" : undefined);
|
|
195
189
|
const claimTarget = {
|
|
196
190
|
id: claimId,
|
|
197
191
|
candidateSetId,
|
|
@@ -266,7 +260,7 @@ export function mappingReviewToSurface(reviewedMappings, options = {}) {
|
|
|
266
260
|
const rawSources = [];
|
|
267
261
|
const seenSources = new Set();
|
|
268
262
|
for (const rm of accepted) {
|
|
269
|
-
const meta = rm.selectedCandidate
|
|
263
|
+
const meta = getProducerProposal(rm.selectedCandidate);
|
|
270
264
|
const sourceField = meta?.sourceField ?? rm.proposal.sourceField;
|
|
271
265
|
const sourceId = `schema-mapping.source.${sourceField.system}`;
|
|
272
266
|
if (!seenSources.has(sourceId)) {
|
|
@@ -290,7 +284,7 @@ export function mappingReviewToSurface(reviewedMappings, options = {}) {
|
|
|
290
284
|
const reviewOutcomes = [];
|
|
291
285
|
const claims = [];
|
|
292
286
|
for (const rm of accepted) {
|
|
293
|
-
const meta = rm.selectedCandidate
|
|
287
|
+
const meta = getProducerProposal(rm.selectedCandidate);
|
|
294
288
|
const sourceField = meta?.sourceField ?? rm.proposal.sourceField;
|
|
295
289
|
const targetField = meta?.targetField ?? rm.proposal.targetField;
|
|
296
290
|
const relation = (meta?.relation ?? rm.proposal.relation);
|
|
@@ -379,7 +373,7 @@ export function mappingReviewToSurface(reviewedMappings, options = {}) {
|
|
|
379
373
|
// subject and back-references the mapping claim via mappingClaimId.
|
|
380
374
|
const identityLinks = [];
|
|
381
375
|
for (const rm of accepted) {
|
|
382
|
-
const meta = rm.selectedCandidate
|
|
376
|
+
const meta = getProducerProposal(rm.selectedCandidate);
|
|
383
377
|
const sourceField = meta?.sourceField ?? rm.proposal.sourceField;
|
|
384
378
|
const targetField = meta?.targetField ?? rm.proposal.targetField;
|
|
385
379
|
const relation = (meta?.relation ?? rm.proposal.relation);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildObservation } from "./observation-helper.js";
|
|
2
|
+
import { assertReviewOutcomeDiscipline } from "./producer-discipline.js";
|
|
2
3
|
export class SourceOfAuthorityObservationBuilder {
|
|
3
4
|
state;
|
|
4
5
|
constructor(args) {
|
|
@@ -115,15 +116,11 @@ function assertVerifiedPosture(input) {
|
|
|
115
116
|
if (!input.extraction.locator) {
|
|
116
117
|
throw new Error(`Source-of-authority observation ${input.id} cannot be ${status} without a source locator`);
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (!input.reviewOutcome.reviewedAt) {
|
|
125
|
-
throw new Error(`Source-of-authority observation ${input.id} cannot be ${status} without reviewedAt`);
|
|
126
|
-
}
|
|
119
|
+
assertReviewOutcomeDiscipline({
|
|
120
|
+
subject: `Source-of-authority observation ${input.id}`,
|
|
121
|
+
status,
|
|
122
|
+
review: input.reviewOutcome,
|
|
123
|
+
});
|
|
127
124
|
}
|
|
128
125
|
function claimStatus(claimStatusValue, reviewStatus) {
|
|
129
126
|
return claimStatusValue ?? reviewStatus;
|
package/dist/src/to-surface.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildReviewProofAnchor } from "./review-proof.js";
|
|
2
|
+
import { assertReviewOutcomeDiscipline } from "./producer-discipline.js";
|
|
2
3
|
export function buildSurveyTrustBundle(input, options = {}) {
|
|
3
4
|
const rawSources = indexById(input.rawSources, "raw source");
|
|
4
5
|
const extractions = indexById(input.extractions, "extraction");
|
|
@@ -334,15 +335,11 @@ function statusFor(input) {
|
|
|
334
335
|
return "proposed";
|
|
335
336
|
}
|
|
336
337
|
function assertProducerDiscipline(input) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
if ((input.status === "verified" || input.status === "assumed") && !input.review?.reviewedAt) {
|
|
344
|
-
throw new Error(`Claim ${input.projection.id} cannot be ${input.status} without reviewedAt`);
|
|
345
|
-
}
|
|
338
|
+
assertReviewOutcomeDiscipline({
|
|
339
|
+
subject: `Claim ${input.projection.id}`,
|
|
340
|
+
status: input.status,
|
|
341
|
+
review: input.review,
|
|
342
|
+
});
|
|
346
343
|
if (input.rawSource.kind !== "manual-entry" && !input.extraction.locator) {
|
|
347
344
|
throw new Error(`Claim ${input.projection.id} needs a source locator for ${input.rawSource.kind}`);
|
|
348
345
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/survey",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Producer-side source, extraction, candidate, and review contracts for projecting verified claims into Surface.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -55,8 +55,11 @@
|
|
|
55
55
|
"typecheck": "tsc --noEmit",
|
|
56
56
|
"test": "npm run build && node --test dist/tests/*.test.js",
|
|
57
57
|
"test:browser": "playwright test",
|
|
58
|
-
"verify": "node scripts/check-content-boundary.cjs && npm run typecheck && npm test && npm run check:review-workbench-assets && npm run check:review-workbench && npm run test:browser",
|
|
58
|
+
"verify": "node scripts/check-content-boundary.cjs && npm run check:decisions && npm run typecheck && npm test && npm run check:review-workbench-assets && npm run check:review-workbench && npm run test:browser",
|
|
59
59
|
"check:content-boundary": "node scripts/check-content-boundary.cjs",
|
|
60
|
+
"check:decisions": "node scripts/check-decisions.cjs check",
|
|
61
|
+
"gen:decisions-index": "node scripts/check-decisions.cjs gen-index",
|
|
62
|
+
"freeze:adrs": "node scripts/freeze-adrs.mjs",
|
|
60
63
|
"sync:review-workbench-assets": "node scripts/sync-review-workbench-assets.cjs",
|
|
61
64
|
"check:review-workbench-assets": "node scripts/sync-review-workbench-assets.cjs --check",
|
|
62
65
|
"check:review-workbench": "npm run check:review-workbench:static && npm run check:generated-css",
|