@kontourai/survey 3.0.0 → 4.0.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 +4 -0
- package/dist/example-data/public-directory-review-resource.d.ts +3 -3
- package/dist/examples/calibrated-auto-accept.d.ts +22 -15
- package/dist/examples/calibrated-auto-accept.js +40 -36
- package/dist/src/calibration.d.ts +48 -21
- package/dist/src/calibration.js +72 -33
- package/dist/src/console/review-console-server.d.ts +3 -1
- package/dist/src/console/review-console-server.js +203 -50
- package/dist/src/extraction-envelope.d.ts +22 -0
- package/dist/src/extraction-envelope.js +25 -4
- package/dist/src/index.d.ts +8 -7
- package/dist/src/index.js +2 -2
- package/dist/src/inquiry-mapping.d.ts +15 -1
- package/dist/src/inquiry-mapping.js +10 -2
- package/dist/src/mcp/review-mcp.js +70 -65
- package/dist/src/producer-profile.d.ts +41 -2
- package/dist/src/producer-profile.js +29 -2
- package/dist/src/review-session-file.d.ts +64 -0
- package/dist/src/review-session-file.js +320 -0
- package/dist/src/review-workbench/edited-value.d.ts +70 -0
- package/dist/src/review-workbench/edited-value.js +147 -0
- package/dist/src/review-workbench/review-presentation.d.ts +44 -0
- package/dist/src/review-workbench/review-presentation.js +49 -0
- package/dist/src/review-workbench/review-queue-session.js +8 -1
- package/dist/src/review-workbench/review-session-replay.d.ts +35 -1
- package/dist/src/review-workbench/review-session-replay.js +77 -0
- package/dist/src/review-workbench/review-workbench.d.ts +8 -4
- package/dist/src/review-workbench/review-workbench.js +19 -7
- package/dist/src/review-workbench/server-review-session.d.ts +3 -1
- package/dist/src/review-workbench/server-review-session.js +1 -0
- package/dist/src/reviewed-candidate-resolution.js +13 -7
- package/dist/src/schema-mapping.d.ts +23 -0
- package/dist/src/schema-mapping.js +30 -20
- package/dist/src/to-surface.d.ts +30 -6
- package/dist/src/to-surface.js +196 -18
- package/dist/src/types.d.ts +39 -0
- package/package.json +5 -4
package/dist/src/to-surface.js
CHANGED
|
@@ -2,15 +2,34 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { buildReviewProofAnchor } from "./review-proof.js";
|
|
3
3
|
import { assertReviewOutcomeDiscipline } from "./producer-discipline.js";
|
|
4
4
|
import { deriveCalibration } from "./calibration.js";
|
|
5
|
+
import { AUTO_ACCEPT_ACTOR } from "./producer-profile.js";
|
|
6
|
+
import { canonicalJson } from "./review-workbench/canonical.js";
|
|
5
7
|
/** Minimum labeled samples a calibration group needs before its empirical
|
|
6
8
|
* accuracy is emitted as a `conclusionConfidence.value`. */
|
|
7
9
|
const DEFAULT_CALIBRATION_MIN_SAMPLES = 20;
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when a claim's trusted status or value does not agree with the review
|
|
12
|
+
* outcome it cites, or when the governing review cannot be chosen.
|
|
13
|
+
* - `status-mismatch`: a `verified`/`assumed` claim cites a review whose status differs.
|
|
14
|
+
* - `value-mismatch`: a `verified`/`assumed` claim carries a value other than the reviewed value.
|
|
15
|
+
* - `ambiguous-review-order`: several reviews apply to one candidate and the latest
|
|
16
|
+
* cannot be determined from `reviewedAt` (missing, unparseable, or tied).
|
|
17
|
+
*/
|
|
18
|
+
export class ReviewAgreementError extends Error {
|
|
19
|
+
name = "ReviewAgreementError";
|
|
20
|
+
code;
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
8
26
|
export function buildSurveyTrustBundle(input, options = {}) {
|
|
9
27
|
const projectionContextId = validateProjectionContextId(options.projectionContextId);
|
|
10
28
|
const rawSources = indexById(input.rawSources, "raw source");
|
|
11
29
|
const extractions = indexById(input.extractions, "extraction");
|
|
12
30
|
const candidateSets = indexById(input.candidateSets, "candidate set");
|
|
13
31
|
const reviewsByCandidateSet = groupBy(input.reviewOutcomes, (review) => review.candidateSetId);
|
|
32
|
+
// Only the explicit experimental opt-in produces a value (#279).
|
|
14
33
|
const calibrationOptions = normalizeCalibrationOptions(options.calibration);
|
|
15
34
|
const calibrationMetrics = calibrationOptions
|
|
16
35
|
? (calibrationOptions.metrics ?? deriveCalibration({
|
|
@@ -28,18 +47,19 @@ export function buildSurveyTrustBundle(input, options = {}) {
|
|
|
28
47
|
const candidate = selectCandidate(candidateSet, projection.candidateId);
|
|
29
48
|
const extraction = requireMapValue(extractions, candidate.extractionId, "extraction");
|
|
30
49
|
const rawSource = requireMapValue(rawSources, extraction.sourceId, "raw source");
|
|
31
|
-
const review = selectReview(reviewsByCandidateSet.get(candidateSet.id) ?? [], candidate.id);
|
|
50
|
+
const review = selectReview(reviewsByCandidateSet.get(candidateSet.id) ?? [], candidate.id, projection.id);
|
|
32
51
|
const projectionReview = review?.resolution === "could_not_confirm" ? undefined : review;
|
|
33
52
|
const unreviewedStatus = statusFor({ candidateSet, candidate });
|
|
34
53
|
const status = projection.status
|
|
35
54
|
?? (review?.resolution === "could_not_confirm"
|
|
36
55
|
? (unreviewedStatus === "disputed" ? unreviewedStatus : review.status)
|
|
37
56
|
: statusFor({ candidateSet, candidate, review }));
|
|
38
|
-
assertProducerDiscipline({ status, review, candidateSet, extraction, rawSource, projection });
|
|
39
57
|
const claimValue = projection.value ?? candidate.value;
|
|
58
|
+
assertProducerDiscipline({ status, review, candidateSet, candidate, extraction, rawSource, projection, claimValue });
|
|
40
59
|
const createdAt = projection.createdAt ?? extraction.extractedAt;
|
|
41
60
|
const updatedAt = projection.updatedAt ?? projectionReview?.reviewedAt ?? input.generatedAt;
|
|
42
61
|
const evidenceId = projectionRecordId(projection.id, projectionContextId, "claim-evidence", "evidence.source");
|
|
62
|
+
const autoAccepted = projectionReview?.actor === AUTO_ACCEPT_ACTOR;
|
|
43
63
|
const claim = {
|
|
44
64
|
id: projection.id,
|
|
45
65
|
subjectType: projection.subjectType,
|
|
@@ -57,8 +77,13 @@ export function buildSurveyTrustBundle(input, options = {}) {
|
|
|
57
77
|
confidenceBasis: {
|
|
58
78
|
sourceQuality: "moderate",
|
|
59
79
|
extractionConfidence: candidate.confidence ?? extraction.confidence,
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
// An auto-accept policy checked nothing but the proposer's own
|
|
81
|
+
// confidence, so its claims carry system authority and weak evidence;
|
|
82
|
+
// only a human review earns operator authority.
|
|
83
|
+
reviewerAuthority: status === "verified" || status === "assumed"
|
|
84
|
+
? (autoAccepted ? "system" : "operator")
|
|
85
|
+
: "none",
|
|
86
|
+
evidenceStrength: (status === "verified" || status === "assumed") && !autoAccepted ? "moderate" : "weak",
|
|
62
87
|
impactLevel: projection.impactLevel,
|
|
63
88
|
...projection.confidenceBasis,
|
|
64
89
|
},
|
|
@@ -72,18 +97,21 @@ export function buildSurveyTrustBundle(input, options = {}) {
|
|
|
72
97
|
candidate,
|
|
73
98
|
review: projectionReview,
|
|
74
99
|
comfortZoneReview: review,
|
|
100
|
+
valueEdit: (status === "verified" || status === "assumed") && review ? acceptedEdit(review) : undefined,
|
|
75
101
|
}),
|
|
76
102
|
},
|
|
77
103
|
};
|
|
78
|
-
// Promote the review's comfort-zone signal — and,
|
|
79
|
-
//
|
|
104
|
+
// Promote the review's comfort-zone signal — and, under the experimental
|
|
105
|
+
// calibration opt-in, the group affirmation rate — into the
|
|
80
106
|
// first-class conclusionConfidence field (Surface 2.9 / Hachure 0.14) so the
|
|
81
107
|
// signal is portable and comparable, not buried in producer metadata.
|
|
82
108
|
//
|
|
83
|
-
// comfortZone is CARRIED from the review. `value` is PRODUCED
|
|
84
|
-
// review calibration (#114/#137):
|
|
85
|
-
//
|
|
86
|
-
//
|
|
109
|
+
// comfortZone is CARRIED from the review. `value` is PRODUCED, only under the
|
|
110
|
+
// experimental opt-in, from empirical review calibration (#114/#137/#279):
|
|
111
|
+
// the affirmation rate of this claim's extractor/field GROUP. It is a group
|
|
112
|
+
// base rate shared by every affirmed claim in the group, not a per-claim
|
|
113
|
+
// probability, and distinct from the extraction-confidence ingredient in
|
|
114
|
+
// confidenceBasis.
|
|
87
115
|
//
|
|
88
116
|
// A value is produced only for an AFFIRMED conclusion (status verified/assumed)
|
|
89
117
|
// that clears the sample floor. conclusionConfidence.value is "probability the
|
|
@@ -235,7 +263,8 @@ function projectInterpretations(input) {
|
|
|
235
263
|
}
|
|
236
264
|
}
|
|
237
265
|
function buildInterpretationProjection(input) {
|
|
238
|
-
|
|
266
|
+
assertInterpretationReadingShape(input.interpretation);
|
|
267
|
+
const anchorSource = requireInterpretationAnchor(input.interpretation, input.rawSources);
|
|
239
268
|
const claim = resolveInterpretationClaim(input.interpretation, input.claims, input.input);
|
|
240
269
|
if (!input.claimsById.has(claim.id)) {
|
|
241
270
|
throw new Error(`Interpretation ${input.interpretation.id} resolved unknown claim ${claim.id}`);
|
|
@@ -260,6 +289,48 @@ function buildInterpretationProjection(input) {
|
|
|
260
289
|
}),
|
|
261
290
|
};
|
|
262
291
|
}
|
|
292
|
+
const INTERPRETATION_READING_KINDS = ["policy-standard", "gleaned", "answerImpact"];
|
|
293
|
+
const INTERPRETATION_ANSWER_IMPACTS = ["supported", "narrowed", "accepted-risk"];
|
|
294
|
+
/** Absent readingKind means the original policy-standard reading (pre-#259 records). */
|
|
295
|
+
function interpretationReadingKind(interpretation) {
|
|
296
|
+
return interpretation.readingKind ?? "policy-standard";
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Fail-closed shape validation for the reading dimension (#259). Records come
|
|
300
|
+
* from JSON as often as from typed callers, so unknown vocabulary throws
|
|
301
|
+
* instead of silently projecting under a label nothing derived.
|
|
302
|
+
*/
|
|
303
|
+
function assertInterpretationReadingShape(interpretation) {
|
|
304
|
+
if (interpretation.readingKind !== undefined && !INTERPRETATION_READING_KINDS.includes(interpretation.readingKind)) {
|
|
305
|
+
throw new Error(`Interpretation ${interpretation.id} has unknown readingKind ${String(interpretation.readingKind)}; expected one of ${INTERPRETATION_READING_KINDS.join(", ")}`);
|
|
306
|
+
}
|
|
307
|
+
const kind = interpretationReadingKind(interpretation);
|
|
308
|
+
if (kind === "answerImpact") {
|
|
309
|
+
if (interpretation.answerImpact === undefined) {
|
|
310
|
+
throw new Error(`Interpretation ${interpretation.id} readingKind answerImpact requires an answerImpact value`);
|
|
311
|
+
}
|
|
312
|
+
if (!INTERPRETATION_ANSWER_IMPACTS.includes(interpretation.answerImpact)) {
|
|
313
|
+
throw new Error(`Interpretation ${interpretation.id} has unknown answerImpact ${String(interpretation.answerImpact)}; expected one of ${INTERPRETATION_ANSWER_IMPACTS.join(", ")}`);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (interpretation.answerImpact !== undefined) {
|
|
318
|
+
throw new Error(`Interpretation ${interpretation.id} sets answerImpact but readingKind is ${kind}; answerImpact is only valid on answerImpact readings`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Resolves an interpretation's anchor raw source. Every reading kind requires
|
|
323
|
+
* a KNOWN raw source (referential integrity per #16 R3); only the original
|
|
324
|
+
* policy-standard reading additionally requires the source kind to be
|
|
325
|
+
* `policy-standard`. The gleaned / answerImpact readings (#259) anchor to the
|
|
326
|
+
* result source the producer read, whatever its kind.
|
|
327
|
+
*/
|
|
328
|
+
function requireInterpretationAnchor(interpretation, rawSources) {
|
|
329
|
+
if (interpretationReadingKind(interpretation) === "policy-standard") {
|
|
330
|
+
return requirePolicyStandardAnchor(interpretation, rawSources);
|
|
331
|
+
}
|
|
332
|
+
return requireMapValue(rawSources, interpretation.anchorsToSourceId, "interpretation anchor raw source");
|
|
333
|
+
}
|
|
263
334
|
function requirePolicyStandardAnchor(interpretation, rawSources) {
|
|
264
335
|
const anchorSource = requireMapValue(rawSources, interpretation.anchorsToSourceId, "interpretation anchor raw source");
|
|
265
336
|
if (anchorSource.kind !== "policy-standard") {
|
|
@@ -268,14 +339,20 @@ function requirePolicyStandardAnchor(interpretation, rawSources) {
|
|
|
268
339
|
return anchorSource;
|
|
269
340
|
}
|
|
270
341
|
function createInterpretationAnchorEvidence(input) {
|
|
342
|
+
const readingKind = interpretationReadingKind(input.interpretation);
|
|
271
343
|
return {
|
|
272
344
|
id: projectionRecordId(input.interpretation.id, input.projectionContextId, "interpretation-evidence", "evidence.anchor"),
|
|
273
345
|
claimId: input.claim.id,
|
|
274
|
-
|
|
346
|
+
// The policy-standard reading keeps its exact prior projection; the new
|
|
347
|
+
// reading kinds (#259) derive the evidence type from the anchor source
|
|
348
|
+
// they actually read, via the same mapping extraction evidence uses.
|
|
349
|
+
evidenceType: readingKind === "policy-standard" ? "policy_rule" : evidenceTypeFor(input.anchorSource),
|
|
275
350
|
method: "anchoring",
|
|
276
351
|
sourceRef: input.anchorSource.sourceRef,
|
|
277
352
|
sourceLocator: input.interpretation.ruleLocator,
|
|
278
|
-
excerptOrSummary:
|
|
353
|
+
excerptOrSummary: readingKind === "policy-standard"
|
|
354
|
+
? input.policyStandard?.inlineText ?? `Anchored policy-standard reading at ${input.interpretation.ruleLocator}.`
|
|
355
|
+
: input.anchorSource.inlineText ?? `Anchored ${readingKind} reading at ${input.interpretation.ruleLocator}.`,
|
|
279
356
|
observedAt: input.anchorSource.observedAt,
|
|
280
357
|
collectedBy: input.interpretation.actor,
|
|
281
358
|
integrityRef: input.anchorSource.checksum,
|
|
@@ -287,6 +364,10 @@ function createInterpretationAnchorEvidence(input) {
|
|
|
287
364
|
locatorScheme: input.anchorSource.locatorScheme,
|
|
288
365
|
anchorsToSourceId: input.anchorSource.id,
|
|
289
366
|
ruleLocator: input.interpretation.ruleLocator,
|
|
367
|
+
// Only explicitly-set reading kinds project, so legacy batches stay
|
|
368
|
+
// byte-identical. Authored-judgment provenance markers, not machine facts.
|
|
369
|
+
...(input.interpretation.readingKind ? { readingKind: input.interpretation.readingKind } : {}),
|
|
370
|
+
...(input.interpretation.answerImpact ? { answerImpact: input.interpretation.answerImpact } : {}),
|
|
290
371
|
},
|
|
291
372
|
};
|
|
292
373
|
}
|
|
@@ -319,6 +400,10 @@ function attachInterpretationClaimMetadata(input) {
|
|
|
319
400
|
reading: input.interpretation.reading,
|
|
320
401
|
actor: input.interpretation.actor,
|
|
321
402
|
recordedAt: input.interpretation.recordedAt,
|
|
403
|
+
// Explicit reading dimension only (#259): legacy entries keep their
|
|
404
|
+
// exact prior bytes; absent readingKind means policy-standard.
|
|
405
|
+
...(input.interpretation.readingKind ? { readingKind: input.interpretation.readingKind } : {}),
|
|
406
|
+
...(input.interpretation.answerImpact ? { answerImpact: input.interpretation.answerImpact } : {}),
|
|
322
407
|
...(input.interpretation.metadata ? { metadata: input.interpretation.metadata } : {}),
|
|
323
408
|
edges: [
|
|
324
409
|
{
|
|
@@ -394,6 +479,11 @@ function buildSurveyMetadata(input) {
|
|
|
394
479
|
},
|
|
395
480
|
}
|
|
396
481
|
: {}),
|
|
482
|
+
// A trusted claim whose value is a reviewer's edit says so, and keeps the
|
|
483
|
+
// value the reviewer was shown.
|
|
484
|
+
...(input.valueEdit
|
|
485
|
+
? { valueEdit: { edited: true, originalValue: input.candidate.value, reviewOutcomeId: input.comfortZoneReview?.id } }
|
|
486
|
+
: {}),
|
|
397
487
|
...(input.comfortZoneReview?.withinComfortZone === false
|
|
398
488
|
? {
|
|
399
489
|
comfortZone: {
|
|
@@ -423,6 +513,17 @@ function assertProducerDiscipline(input) {
|
|
|
423
513
|
review: input.review,
|
|
424
514
|
candidateSetStatus: input.candidateSet.status,
|
|
425
515
|
});
|
|
516
|
+
if ((input.status === "verified" || input.status === "assumed") && input.review) {
|
|
517
|
+
// A trusted claim may only restate its review: same status, and the value
|
|
518
|
+
// the reviewer saw (the candidate value, or the edit the review records).
|
|
519
|
+
if (input.review.status !== input.status) {
|
|
520
|
+
throw new ReviewAgreementError("status-mismatch", `Claim ${input.projection.id} status ${input.status} disagrees with review outcome ${input.review.id} status ${input.review.status}`);
|
|
521
|
+
}
|
|
522
|
+
const reviewedValue = reviewedValueFor(input.review, input.candidate);
|
|
523
|
+
if (canonicalJson(input.claimValue) !== canonicalJson(reviewedValue)) {
|
|
524
|
+
throw new ReviewAgreementError("value-mismatch", `Claim ${input.projection.id} is ${input.status} but its value differs from the value reviewed in ${input.review.id}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
426
527
|
if (input.rawSource.kind !== "manual-entry" && !input.extraction.locator) {
|
|
427
528
|
throw new Error(`Claim ${input.projection.id} needs a source locator for ${input.rawSource.kind}`);
|
|
428
529
|
}
|
|
@@ -435,15 +536,92 @@ function selectCandidate(candidateSet, candidateId) {
|
|
|
435
536
|
}
|
|
436
537
|
return candidate;
|
|
437
538
|
}
|
|
438
|
-
|
|
439
|
-
|
|
539
|
+
/** An accepted edit the review records. `buildCanonicalReviewedTrustInput`
|
|
540
|
+
* writes `metadata.editedValue` next to `metadata.workbenchDecision`; only an
|
|
541
|
+
* edit on an `accept-proposed` decision replaces the reviewed value. Any other
|
|
542
|
+
* `editedValue` (a bare one, or one on a reject or keep decision) is not an
|
|
543
|
+
* accepted edit and is ignored. */
|
|
544
|
+
function acceptedEdit(review) {
|
|
545
|
+
const metadata = review.metadata;
|
|
546
|
+
return metadata?.workbenchDecision === "accept-proposed" && Object.hasOwn(metadata, "editedValue")
|
|
547
|
+
? { value: metadata.editedValue }
|
|
548
|
+
: undefined;
|
|
549
|
+
}
|
|
550
|
+
/** The value a review outcome attests: its accepted edit, else the candidate value that was reviewed. */
|
|
551
|
+
function reviewedValueFor(review, candidate) {
|
|
552
|
+
const edit = acceptedEdit(review);
|
|
553
|
+
return edit ? edit.value : candidate.value;
|
|
554
|
+
}
|
|
555
|
+
/** The latest applicable review governs. Exact candidate bindings take
|
|
556
|
+
* precedence over unbound (set-wide) reviews; within a tier, several reviews
|
|
557
|
+
* are ordered by `reviewedAt`, and an order that cannot be established is
|
|
558
|
+
* refused rather than guessed. */
|
|
559
|
+
function selectReview(reviews, candidateId, claimId) {
|
|
560
|
+
const exact = reviews.filter((review) => review.candidateId === candidateId);
|
|
561
|
+
return latestReview(exact.length ? exact : reviews.filter((review) => !review.candidateId), claimId);
|
|
562
|
+
}
|
|
563
|
+
function latestReview(reviews, claimId) {
|
|
564
|
+
if (reviews.length <= 1)
|
|
565
|
+
return reviews[0];
|
|
566
|
+
const timed = reviews.map((review) => ({ review, at: review.reviewedAt ? Date.parse(review.reviewedAt) : Number.NaN }));
|
|
567
|
+
const untimed = timed.find((entry) => Number.isNaN(entry.at));
|
|
568
|
+
if (untimed) {
|
|
569
|
+
throw new ReviewAgreementError("ambiguous-review-order", `Claim ${claimId} has ${reviews.length} applicable review outcomes but ${untimed.review.id} has no parseable reviewedAt`);
|
|
570
|
+
}
|
|
571
|
+
const latestAt = Math.max(...timed.map((entry) => entry.at));
|
|
572
|
+
const latest = timed.filter((entry) => entry.at === latestAt).map((entry) => entry.review);
|
|
573
|
+
// Reviews at the same instant (however the timestamp is spelled) that record
|
|
574
|
+
// the same decision are one review recorded twice; conflicting ones are refused.
|
|
575
|
+
if (new Set(latest.map(reviewDecisionKey)).size > 1) {
|
|
576
|
+
throw new ReviewAgreementError("ambiguous-review-order", `Claim ${claimId} has conflicting review outcomes ${latest.map((review) => review.id).join(", ")} at the latest reviewedAt`);
|
|
577
|
+
}
|
|
578
|
+
return [...latest].sort((left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0))[0];
|
|
579
|
+
}
|
|
580
|
+
function reviewDecisionKey(review) {
|
|
581
|
+
const edit = acceptedEdit(review);
|
|
582
|
+
return canonicalJson({
|
|
583
|
+
candidateId: review.candidateId ?? null,
|
|
584
|
+
status: review.status,
|
|
585
|
+
resolution: review.resolution ?? null,
|
|
586
|
+
edit: edit ? { value: edit.value } : null,
|
|
587
|
+
// These reach the claim (comfort zone, testimony provenance), so reviews
|
|
588
|
+
// that differ in them are different decisions, not one recorded twice.
|
|
589
|
+
withinComfortZone: review.withinComfortZone ?? null,
|
|
590
|
+
comfortZoneNote: review.comfortZoneNote ?? null,
|
|
591
|
+
authorizing: review.authorizing ?? null,
|
|
592
|
+
});
|
|
440
593
|
}
|
|
441
594
|
function normalizeCalibrationOptions(calibration) {
|
|
442
595
|
if (calibration === undefined || calibration === false)
|
|
443
596
|
return undefined;
|
|
444
|
-
if (calibration === true)
|
|
445
|
-
|
|
446
|
-
|
|
597
|
+
if (calibration === true || calibration.experimentalConclusionValue === undefined) {
|
|
598
|
+
// Callers written before #279 asked for a value this way; tell them it is
|
|
599
|
+
// now ignored instead of silently dropping it. An explicit `false` is a
|
|
600
|
+
// deliberate opt-out and stays quiet.
|
|
601
|
+
warnCalibrationOptInMissingOnce();
|
|
602
|
+
return undefined;
|
|
603
|
+
}
|
|
604
|
+
return calibration.experimentalConclusionValue === true ? calibration : undefined;
|
|
605
|
+
}
|
|
606
|
+
let warnedCalibrationOptInMissing = false;
|
|
607
|
+
/**
|
|
608
|
+
* Warn once per process, matching the `defineProductVocabulary` deprecation
|
|
609
|
+
* notice: `buildSurveyTrustBundle` has no diagnostics channel in its return
|
|
610
|
+
* value (it returns a TrustBundle), and a per-call warning would flood batch
|
|
611
|
+
* projections.
|
|
612
|
+
*/
|
|
613
|
+
function warnCalibrationOptInMissingOnce() {
|
|
614
|
+
if (warnedCalibrationOptInMissing)
|
|
615
|
+
return;
|
|
616
|
+
warnedCalibrationOptInMissing = true;
|
|
617
|
+
console.warn("[@kontourai/survey] buildSurveyTrustBundle: the `calibration` option no longer sets " +
|
|
618
|
+
"conclusionConfidence.value without `calibration: { experimentalConclusionValue: true }` (#279). " +
|
|
619
|
+
"The value is an experimental extractor/field group base rate, not a per-claim probability. " +
|
|
620
|
+
"Pass the flag to keep it, or `experimentalConclusionValue: false` / omit `calibration` to silence this warning.");
|
|
621
|
+
}
|
|
622
|
+
/** Test-only: re-arm the once-per-process calibration opt-in warning. Not exported from the package index. */
|
|
623
|
+
export function resetCalibrationOptInWarningForTests() {
|
|
624
|
+
warnedCalibrationOptInMissing = false;
|
|
447
625
|
}
|
|
448
626
|
/**
|
|
449
627
|
* Looks up the empirical affirmation rate for an extractor/field, preferring the
|
package/dist/src/types.d.ts
CHANGED
|
@@ -104,11 +104,50 @@ export interface EscalationRecord {
|
|
|
104
104
|
resolvedBy?: string;
|
|
105
105
|
metadata?: Record<string, unknown>;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The reading dimension of an {@link Interpretation} (#259, generalizing #16):
|
|
109
|
+
*
|
|
110
|
+
* - `"policy-standard"` — the original reading of how a policy-standard
|
|
111
|
+
* paragraph applies to the claim. This kind keeps the hard requirement that
|
|
112
|
+
* the anchor raw source is `kind: "policy-standard"`.
|
|
113
|
+
* - `"gleaned"` — what this result taught: an authored reading of what the
|
|
114
|
+
* anchored source material showed for the claim.
|
|
115
|
+
* - `"answerImpact"` — how the reading moved the inquiry answer the claim
|
|
116
|
+
* feeds (see {@link InterpretationAnswerImpact}); requires `answerImpact`.
|
|
117
|
+
*
|
|
118
|
+
* Additive-optional: a record with no `readingKind` is a `"policy-standard"`
|
|
119
|
+
* reading, so existing batches keep their exact prior validation and
|
|
120
|
+
* projection behavior. Interpretations never enter canonical review-proof
|
|
121
|
+
* bytes regardless of kind.
|
|
122
|
+
*/
|
|
123
|
+
export type InterpretationReadingKind = "policy-standard" | "gleaned" | "answerImpact";
|
|
124
|
+
/**
|
|
125
|
+
* How an `"answerImpact"` reading moved the inquiry answer its claim feeds:
|
|
126
|
+
* the result `supported` the answer, `narrowed` it, or was recorded as an
|
|
127
|
+
* `accepted-risk`. "Answer" means the inquiry answer (inquiry-mapping);
|
|
128
|
+
* downstream decisions stay with consumers.
|
|
129
|
+
*/
|
|
130
|
+
export type InterpretationAnswerImpact = "supported" | "narrowed" | "accepted-risk";
|
|
107
131
|
export interface Interpretation {
|
|
108
132
|
id: string;
|
|
109
133
|
appliesToTarget?: string;
|
|
110
134
|
appliesToClaimId?: string;
|
|
135
|
+
/**
|
|
136
|
+
* Which reading dimension this record carries. Absent means
|
|
137
|
+
* `"policy-standard"` (the pre-#259 behavior, unchanged).
|
|
138
|
+
*/
|
|
139
|
+
readingKind?: InterpretationReadingKind;
|
|
140
|
+
/**
|
|
141
|
+
* Required when `readingKind` is `"answerImpact"`; illegal for any other
|
|
142
|
+
* kind. The structured impact beside the free-text `reading`.
|
|
143
|
+
*/
|
|
144
|
+
answerImpact?: InterpretationAnswerImpact;
|
|
111
145
|
anchorsToSourceId: string;
|
|
146
|
+
/**
|
|
147
|
+
* Locator inside the anchor source: the rule paragraph for a
|
|
148
|
+
* `"policy-standard"` reading, or the result location the producer read for
|
|
149
|
+
* `"gleaned"` / `"answerImpact"` readings.
|
|
150
|
+
*/
|
|
112
151
|
ruleLocator: string;
|
|
113
152
|
reading: string;
|
|
114
153
|
actor: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/survey",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.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",
|
|
@@ -72,9 +72,9 @@
|
|
|
72
72
|
"workflow:validate-artifacts": "flow-agents-validate-artifacts"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@kontourai/surface": "^2.13.0",
|
|
75
|
+
"@kontourai/surface": "^2.13.0 || ^3.0.0 || ^4.0.0",
|
|
76
76
|
"@modelcontextprotocol/server": "2.0.0",
|
|
77
|
-
"zod": "4.
|
|
77
|
+
"zod": "4.4.3"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
80
|
"typescript": ">=5.0.0"
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
|
-
"@kontourai/flow-agents": "5.
|
|
88
|
+
"@kontourai/flow-agents": "5.10.0",
|
|
89
89
|
"@kontourai/traverse": "^0.25.0",
|
|
90
90
|
"@kontourai/ui": "^1.1.0",
|
|
91
91
|
"@playwright/test": "^1.62.0",
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
"engines": {
|
|
97
97
|
"node": ">=22"
|
|
98
98
|
},
|
|
99
|
+
"packageManager": "pnpm@11.25.0",
|
|
99
100
|
"bin": {
|
|
100
101
|
"survey-review-mcp": "./bin/survey-review-mcp.mjs",
|
|
101
102
|
"survey-review-console": "./bin/survey-review-console.mjs"
|