@kontourai/survey 0.4.0 → 0.4.2
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 +275 -0
- package/dist/src/builder.d.ts +3 -1
- package/dist/src/builder.js +6 -0
- package/dist/src/index.d.ts +6 -1
- package/dist/src/index.js +2 -0
- package/dist/src/review-proof.d.ts +88 -0
- package/dist/src/review-proof.js +124 -0
- package/dist/src/source-of-authority-observation.d.ts +31 -0
- package/dist/src/source-of-authority-observation.js +70 -0
- package/dist/src/to-surface.d.ts +4 -1
- package/dist/src/to-surface.js +50 -4
- package/dist/src/types.d.ts +16 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -183,6 +183,94 @@ preserved. Producers still own scalar semantics, validation, candidate ranking,
|
|
|
183
183
|
review policy, and whether a value should be verified, proposed, rejected, or
|
|
184
184
|
assumed.
|
|
185
185
|
|
|
186
|
+
## Source-of-authority observations
|
|
187
|
+
|
|
188
|
+
Use `sourceOfAuthorityObservation` when a producer treats the raw source as
|
|
189
|
+
authoritative for the extracted target: an official publication, registration
|
|
190
|
+
platform page, policy document, contract record, or system-of-record response.
|
|
191
|
+
The helper does not decide whether the source is truly authoritative. It
|
|
192
|
+
enforces record discipline around the producer's declared source posture.
|
|
193
|
+
|
|
194
|
+
Verified or assumed source-of-authority observations require:
|
|
195
|
+
|
|
196
|
+
- source reference
|
|
197
|
+
- source locator
|
|
198
|
+
- source-authority class
|
|
199
|
+
- source-authority scope
|
|
200
|
+
- review actor
|
|
201
|
+
- reviewed time
|
|
202
|
+
|
|
203
|
+
Source-authority metadata projects through Surface Evidence metadata under
|
|
204
|
+
`sourceAuthority`. It does not project to Surface `authorityTrace`, which is
|
|
205
|
+
reserved for actor, credential, role, organization, policy, or system authority.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import {
|
|
209
|
+
buildSurveyTrustInput,
|
|
210
|
+
sourceOfAuthorityObservation,
|
|
211
|
+
SurveyInputBuilder,
|
|
212
|
+
uploadedDocumentSource,
|
|
213
|
+
} from "@kontourai/survey";
|
|
214
|
+
|
|
215
|
+
const observedAt = new Date().toISOString();
|
|
216
|
+
const rawSource = uploadedDocumentSource({
|
|
217
|
+
sourceRef: "https://rules.example.test/standard-deduction.pdf",
|
|
218
|
+
observedAt,
|
|
219
|
+
checksum: "abc123",
|
|
220
|
+
locatorScheme: "pdf",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const surveyInput = new SurveyInputBuilder({
|
|
224
|
+
source: "rule-producer:run-1",
|
|
225
|
+
})
|
|
226
|
+
.addObservation(sourceOfAuthorityObservation({
|
|
227
|
+
id: "rule.standard-deduction.mfj.2026",
|
|
228
|
+
field: "federal.standardDeduction.mfj.2026",
|
|
229
|
+
value: 30000,
|
|
230
|
+
sourceAuthority: {
|
|
231
|
+
authorityClass: "official_publication",
|
|
232
|
+
scope: {
|
|
233
|
+
jurisdiction: "federal",
|
|
234
|
+
productArea: "regulated-rule",
|
|
235
|
+
taxYear: 2026,
|
|
236
|
+
},
|
|
237
|
+
sourceVersion: "2026",
|
|
238
|
+
declaredBy: "rule-producer",
|
|
239
|
+
},
|
|
240
|
+
rawSource,
|
|
241
|
+
extraction: {
|
|
242
|
+
confidence: 0.94,
|
|
243
|
+
locator: "pdf:page=12;table=standard-deduction;row=mfj",
|
|
244
|
+
extractor: "rule-producer",
|
|
245
|
+
extractedAt: observedAt,
|
|
246
|
+
},
|
|
247
|
+
reviewOutcome: {
|
|
248
|
+
status: "verified",
|
|
249
|
+
actor: "rule-reviewer",
|
|
250
|
+
reviewedAt: new Date().toISOString(),
|
|
251
|
+
},
|
|
252
|
+
claim: {
|
|
253
|
+
subjectType: "regulated-rule",
|
|
254
|
+
subjectId: "federal:standard-deduction:mfj:2026",
|
|
255
|
+
surface: "regulated.rules",
|
|
256
|
+
claimType: "regulated.rule-value",
|
|
257
|
+
status: "verified",
|
|
258
|
+
impactLevel: "high",
|
|
259
|
+
evidenceType: "policy_rule",
|
|
260
|
+
evidenceMethod: "extraction",
|
|
261
|
+
collectedBy: "rule-producer",
|
|
262
|
+
},
|
|
263
|
+
}))
|
|
264
|
+
.build();
|
|
265
|
+
|
|
266
|
+
const trustInput = buildSurveyTrustInput(surveyInput);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Contextual claims such as "this return position is compliant" or "this camp is
|
|
270
|
+
eligible for an 8-year-old in June" are not source-of-authority observations.
|
|
271
|
+
They are Surface claims with Claim Dependencies on source-of-authority claims
|
|
272
|
+
and other product facts. The vertical product owns that domain logic.
|
|
273
|
+
|
|
186
274
|
## Reviewed candidate resolutions
|
|
187
275
|
|
|
188
276
|
Use `reviewedCandidateResolution` when a producer has multiple candidate
|
|
@@ -384,6 +472,53 @@ Conflict before review has resolved which candidate should win. When no review
|
|
|
384
472
|
outcome overrides it, `buildSurveyTrustInput` projects the claim to Surface
|
|
385
473
|
status `"disputed"` and records a `"candidate-conflict"` verification event.
|
|
386
474
|
|
|
475
|
+
## Review proofs
|
|
476
|
+
|
|
477
|
+
Use review proof helpers when a producer wants a Surface-compatible integrity
|
|
478
|
+
anchor for one reviewed Survey source -> extraction -> candidate -> review ->
|
|
479
|
+
claim path.
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
import {
|
|
483
|
+
buildCanonicalReviewProofPayload,
|
|
484
|
+
buildReviewProofAnchor,
|
|
485
|
+
canonicalReviewProofJson,
|
|
486
|
+
hashCanonicalReviewProofPayload,
|
|
487
|
+
} from "@kontourai/survey";
|
|
488
|
+
|
|
489
|
+
const proofInput = {
|
|
490
|
+
rawSource,
|
|
491
|
+
extraction,
|
|
492
|
+
candidate,
|
|
493
|
+
candidateSet,
|
|
494
|
+
reviewOutcome,
|
|
495
|
+
claim,
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const payload = buildCanonicalReviewProofPayload(proofInput);
|
|
499
|
+
const canonicalJson = canonicalReviewProofJson(payload);
|
|
500
|
+
const hash = hashCanonicalReviewProofPayload(payload);
|
|
501
|
+
const anchor = buildReviewProofAnchor(proofInput);
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
`buildReviewProofAnchor` returns a hash-only Surface `IntegrityAnchor` for the
|
|
505
|
+
canonical payload. The lower-level payload, JSON, and hash helpers are exported
|
|
506
|
+
so producers can store or recompute the exact canonical proof material used for
|
|
507
|
+
the anchor. Producer metadata is not part of the canonical payload; any
|
|
508
|
+
non-portable context belongs outside the hash, such as anchor metadata.
|
|
509
|
+
|
|
510
|
+
When the Surface projection proof option is enabled, `buildSurveyTrustInput`
|
|
511
|
+
will attach the same kind of anchor to the projected reviewed claim:
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
const trustInput = buildSurveyTrustInput(surveyInput, { reviewProofs: true });
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
The proof provides hash-only tamper evidence for the Survey review/provenance
|
|
518
|
+
trail in the canonical payload. It does not authenticate an actor, sign the
|
|
519
|
+
payload, or prove the real-world truth of the claim. Non-goals include JWT/JWS
|
|
520
|
+
signing, key management, a transparency log, and any veracity guarantee.
|
|
521
|
+
|
|
387
522
|
## Computed values
|
|
388
523
|
|
|
389
524
|
Computed values are normal `ClaimTarget` entries in `claims`. Producers should
|
|
@@ -395,6 +530,146 @@ Survey passes those fields through to Surface while keeping the same
|
|
|
395
530
|
source -> extraction -> candidate -> review -> claim projection path. Surface
|
|
396
531
|
owns dependency semantics such as recompute pressure and status ceilings.
|
|
397
532
|
|
|
533
|
+
## Adversarial passes
|
|
534
|
+
|
|
535
|
+
Producers that run a second adversarial pass — whether an LLM judge, a rules
|
|
536
|
+
engine, or a second human reviewer — emit their output into Survey as a normal
|
|
537
|
+
producer pass with a distinct `extractor` id. Survey does not know or care that
|
|
538
|
+
a second pass ran; it sees two producers disagreeing on the same target, which
|
|
539
|
+
is exactly what `conflict` and escalation records are for.
|
|
540
|
+
|
|
541
|
+
Two patterns cover the adversary's output:
|
|
542
|
+
|
|
543
|
+
**Conflicting candidate.** The adversary disagrees with the first-pass extraction
|
|
544
|
+
value. Add the adversary's extraction as a second candidate to the same candidate
|
|
545
|
+
set using `candidateReviewRecord` with `status: "conflict"`. Survey projects the
|
|
546
|
+
conflict to a `disputed` claim in Surface.
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import { candidateReviewRecord, fieldObservation, SurveyInputBuilder } from "@kontourai/survey";
|
|
550
|
+
|
|
551
|
+
const records = candidateReviewRecord({
|
|
552
|
+
id: "candidate-set.entity-1.registration-status",
|
|
553
|
+
target: "registrationStatus",
|
|
554
|
+
status: "conflict",
|
|
555
|
+
rationale: "First pass and adversary disagree; human review required.",
|
|
556
|
+
observations: [
|
|
557
|
+
fieldObservation({
|
|
558
|
+
id: "observation.entity-1.status.first-pass",
|
|
559
|
+
field: "registrationStatus",
|
|
560
|
+
value: "ACTIVE",
|
|
561
|
+
rawSource: {
|
|
562
|
+
kind: "api-record",
|
|
563
|
+
sourceRef: "records://entity-1/registry",
|
|
564
|
+
observedAt: new Date().toISOString(),
|
|
565
|
+
locatorScheme: "structured-field",
|
|
566
|
+
},
|
|
567
|
+
extraction: {
|
|
568
|
+
confidence: 0.91,
|
|
569
|
+
locator: "json:$.registrationStatus",
|
|
570
|
+
extractor: "agent-v1",
|
|
571
|
+
extractedAt: new Date().toISOString(),
|
|
572
|
+
},
|
|
573
|
+
candidate: { id: "candidate.first-pass", confidence: 0.91 },
|
|
574
|
+
claim: {
|
|
575
|
+
subjectType: "public-record.entity",
|
|
576
|
+
subjectId: "entity-1",
|
|
577
|
+
surface: "public-record.profile",
|
|
578
|
+
claimType: "public-data.field",
|
|
579
|
+
impactLevel: "high",
|
|
580
|
+
collectedBy: "agent-v1",
|
|
581
|
+
},
|
|
582
|
+
}),
|
|
583
|
+
fieldObservation({
|
|
584
|
+
id: "observation.entity-1.status.adversary",
|
|
585
|
+
field: "registrationStatus",
|
|
586
|
+
value: "INACTIVE",
|
|
587
|
+
rawSource: {
|
|
588
|
+
kind: "api-record",
|
|
589
|
+
sourceRef: "records://entity-1/registry",
|
|
590
|
+
observedAt: new Date().toISOString(),
|
|
591
|
+
locatorScheme: "structured-field",
|
|
592
|
+
},
|
|
593
|
+
extraction: {
|
|
594
|
+
confidence: 0.84,
|
|
595
|
+
locator: "json:$.registrationStatus",
|
|
596
|
+
extractor: "adversary-v1",
|
|
597
|
+
extractedAt: new Date().toISOString(),
|
|
598
|
+
},
|
|
599
|
+
candidate: { id: "candidate.adversary", confidence: 0.84 },
|
|
600
|
+
claim: {
|
|
601
|
+
subjectType: "public-record.entity",
|
|
602
|
+
subjectId: "entity-1",
|
|
603
|
+
surface: "public-record.profile",
|
|
604
|
+
claimType: "public-data.field",
|
|
605
|
+
impactLevel: "high",
|
|
606
|
+
collectedBy: "adversary-v1",
|
|
607
|
+
},
|
|
608
|
+
}),
|
|
609
|
+
],
|
|
610
|
+
});
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Framing challenge.** The adversary identifies a target that was not addressed
|
|
614
|
+
at all — a missed standard, an unconsidered alternative, or a misframed question.
|
|
615
|
+
Use `addEscalation` to record the challenge. Attach it to the closest relevant
|
|
616
|
+
claim with `attachToClaimId`; Survey projects it as an additional `disputed`
|
|
617
|
+
verification event on that claim so the reviewer sees it prominently.
|
|
618
|
+
|
|
619
|
+
```ts
|
|
620
|
+
import { SurveyInputBuilder, fieldObservation } from "@kontourai/survey";
|
|
621
|
+
|
|
622
|
+
const builder = new SurveyInputBuilder({ source: "example-producer:run-2" });
|
|
623
|
+
|
|
624
|
+
// First-pass observation
|
|
625
|
+
builder.addObservation(fieldObservation({ /* ... */ }));
|
|
626
|
+
|
|
627
|
+
// Adversary raises a framing challenge
|
|
628
|
+
builder.addEscalation({
|
|
629
|
+
id: "escalation.entity-1.fair-value.completeness",
|
|
630
|
+
target: "fairValue",
|
|
631
|
+
dimension: "completeness",
|
|
632
|
+
reason: "Measurement standard Level 3 inputs were not documented; sensitivity range and unobservable input assumptions are missing.",
|
|
633
|
+
raisedBy: "adversary-v1",
|
|
634
|
+
raisedAt: new Date().toISOString(),
|
|
635
|
+
attachToClaimId: "claim.entity-1.fair-value",
|
|
636
|
+
});
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
If a subsequent first-pass or human-review pass resolves the challenge, set
|
|
640
|
+
`resolvedBy` to the id of the observation that closes it. Survey will not project
|
|
641
|
+
a `disputed` event for resolved escalations.
|
|
642
|
+
|
|
643
|
+
Escalation dimensions follow the adversary's attack surface: `framing` (wrong
|
|
644
|
+
question framed), `completeness` (missing standards, alternatives, or evidence),
|
|
645
|
+
`conclusion` (reasoning would not survive challenge), and `citation` (cited
|
|
646
|
+
sources do not support the claims attached to them).
|
|
647
|
+
|
|
648
|
+
Framing challenges without an `attachToClaimId` are carried in `SurveyInput`
|
|
649
|
+
for producer tooling but are not projected to Surface. If the adversary cannot
|
|
650
|
+
identify a target claim to attach a framing challenge to, emit a candidate set
|
|
651
|
+
with `status: "escalated"` for the affected target — that projects to `disputed`
|
|
652
|
+
in Surface with a `candidate-escalation` event.
|
|
653
|
+
|
|
654
|
+
## Comfort zone flags
|
|
655
|
+
|
|
656
|
+
Use `withinComfortZone: false` on a `ReviewOutcome` when the reviewer is
|
|
657
|
+
recording a decision outside their domain expertise or is flagging that the
|
|
658
|
+
conclusion requires a different authority to confirm. The flag and optional
|
|
659
|
+
`comfortZoneNote` are carried forward to the Surface verification event `notes`
|
|
660
|
+
so the reviewer chain sees the signal without having to read into the rationale.
|
|
661
|
+
|
|
662
|
+
```ts
|
|
663
|
+
reviewOutcome: {
|
|
664
|
+
status: "assumed",
|
|
665
|
+
actor: "records-operator",
|
|
666
|
+
reviewedAt: new Date().toISOString(),
|
|
667
|
+
rationale: "Assumed from registry source pending specialist review.",
|
|
668
|
+
withinComfortZone: false,
|
|
669
|
+
comfortZoneNote: "Renewal clause interpretation requires specialist counsel.",
|
|
670
|
+
},
|
|
671
|
+
```
|
|
672
|
+
|
|
398
673
|
## Product Boundary
|
|
399
674
|
|
|
400
675
|
Survey does not crawl pages, parse PDFs, rank candidates, decide review policy,
|
package/dist/src/builder.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CandidateSet, ClaimTarget, Extraction, RawSource, ReviewOutcome, SurveyInput } from "./types.js";
|
|
1
|
+
import type { CandidateSet, ClaimTarget, EscalationRecord, Extraction, RawSource, ReviewOutcome, SurveyInput } from "./types.js";
|
|
2
2
|
export interface SurveyInputBuilderArgs {
|
|
3
3
|
source: string;
|
|
4
4
|
generatedAt?: string;
|
|
@@ -58,12 +58,14 @@ export declare class SurveyInputBuilder {
|
|
|
58
58
|
private readonly candidateSets;
|
|
59
59
|
private readonly reviewOutcomes;
|
|
60
60
|
private readonly claims;
|
|
61
|
+
private readonly escalations;
|
|
61
62
|
constructor(args: SurveyInputBuilderArgs);
|
|
62
63
|
addRawSource(rawSource: RawSource): this;
|
|
63
64
|
addExtraction(extraction: Extraction): this;
|
|
64
65
|
addCandidateSet(candidateSet: CandidateSet): this;
|
|
65
66
|
addReviewOutcome(reviewOutcome: ReviewOutcome): this;
|
|
66
67
|
addClaim(claim: ClaimTarget): this;
|
|
68
|
+
addEscalation(escalation: EscalationRecord): this;
|
|
67
69
|
addClaimRecord(record: SurveyClaimRecord): this;
|
|
68
70
|
addClaimRecords(records: SurveyClaimRecord[]): this;
|
|
69
71
|
addObservation(observation: SurveyObservationInput): this;
|
package/dist/src/builder.js
CHANGED
|
@@ -6,6 +6,7 @@ export class SurveyInputBuilder {
|
|
|
6
6
|
candidateSets = new Map();
|
|
7
7
|
reviewOutcomes = new Map();
|
|
8
8
|
claims = new Map();
|
|
9
|
+
escalations = new Map();
|
|
9
10
|
constructor(args) {
|
|
10
11
|
this.source = args.source;
|
|
11
12
|
this.generatedAt = args.generatedAt ?? new Date().toISOString();
|
|
@@ -30,6 +31,10 @@ export class SurveyInputBuilder {
|
|
|
30
31
|
addUnique(this.claims, claim, "claim target");
|
|
31
32
|
return this;
|
|
32
33
|
}
|
|
34
|
+
addEscalation(escalation) {
|
|
35
|
+
addUnique(this.escalations, escalation, "escalation");
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
33
38
|
addClaimRecord(record) {
|
|
34
39
|
this.addRecordRawSource(record.rawSource);
|
|
35
40
|
this.addExtraction(record.extraction);
|
|
@@ -61,6 +66,7 @@ export class SurveyInputBuilder {
|
|
|
61
66
|
candidateSets: [...this.candidateSets.values()],
|
|
62
67
|
reviewOutcomes: [...this.reviewOutcomes.values()],
|
|
63
68
|
claims: [...this.claims.values()],
|
|
69
|
+
escalations: this.escalations.size > 0 ? [...this.escalations.values()] : undefined,
|
|
64
70
|
};
|
|
65
71
|
}
|
|
66
72
|
addRecordCandidateSet(candidateSet) {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { CandidateSetStatus, Candidate, CandidateSet, ClaimTarget, Extraction, LocatorScheme, RawSource, RawSourceKind, ReviewOutcome, ReviewStatus, SurveyInput, } from "./types.js";
|
|
1
|
+
export type { CandidateSetStatus, Candidate, CandidateSet, ClaimTarget, EscalationDimension, EscalationRecord, Extraction, LocatorScheme, RawSource, RawSourceKind, ReviewOutcome, ReviewStatus, SurveyInput, } from "./types.js";
|
|
2
2
|
export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
|
|
3
3
|
export type { CandidateReviewRecordInput, SurveyClaimRecord, SurveyInputBuilderArgs, SurveyObservationInput, } from "./builder.js";
|
|
4
4
|
export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js";
|
|
@@ -6,9 +6,14 @@ export type { ReviewedCandidateResolutionInput } from "./reviewed-candidate-reso
|
|
|
6
6
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
7
7
|
export type { CurrentProposedCandidateRole, ReviewedCurrentProposedResolutionInput, } from "./reviewed-current-proposed-resolution.js";
|
|
8
8
|
export { buildSurveyTrustInput } from "./to-surface.js";
|
|
9
|
+
export type { BuildSurveyTrustInputOptions } from "./to-surface.js";
|
|
10
|
+
export { buildCanonicalReviewProofPayload, buildReviewProofAnchor, canonicalReviewProofJson, hashCanonicalReviewProofPayload, } from "./review-proof.js";
|
|
11
|
+
export type { CanonicalReviewProofPayload, ReviewProofInput, } from "./review-proof.js";
|
|
9
12
|
export { fieldObservation } from "./field-observation.js";
|
|
10
13
|
export type { FieldObservationInput } from "./field-observation.js";
|
|
11
14
|
export { repeatedObservation } from "./repeated-observation.js";
|
|
12
15
|
export type { RepeatedObservationInput } from "./repeated-observation.js";
|
|
16
|
+
export { sourceOfAuthorityObservation } from "./source-of-authority-observation.js";
|
|
17
|
+
export type { SourceAuthorityClass, SourceAuthorityMetadata, SourceOfAuthorityObservationInput, } from "./source-of-authority-observation.js";
|
|
13
18
|
export { apiRecordSource, manualEntrySource, uploadedDocumentSource, webPageSource, } from "./raw-source.js";
|
|
14
19
|
export type { ApiRecordSourceInput, ChecksumInput, ManualEntrySourceInput, RawSourceInput, UploadedDocumentSourceInput, WebPageSourceInput, } from "./raw-source.js";
|
package/dist/src/index.js
CHANGED
|
@@ -2,6 +2,8 @@ export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
|
|
|
2
2
|
export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js";
|
|
3
3
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
4
4
|
export { buildSurveyTrustInput } from "./to-surface.js";
|
|
5
|
+
export { buildCanonicalReviewProofPayload, buildReviewProofAnchor, canonicalReviewProofJson, hashCanonicalReviewProofPayload, } from "./review-proof.js";
|
|
5
6
|
export { fieldObservation } from "./field-observation.js";
|
|
6
7
|
export { repeatedObservation } from "./repeated-observation.js";
|
|
8
|
+
export { sourceOfAuthorityObservation } from "./source-of-authority-observation.js";
|
|
7
9
|
export { apiRecordSource, manualEntrySource, uploadedDocumentSource, webPageSource, } from "./raw-source.js";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { IntegrityAnchor } from "@kontourai/surface";
|
|
2
|
+
import type { Candidate, CandidateSet, ClaimTarget, Extraction, RawSource, ReviewOutcome } from "./types.js";
|
|
3
|
+
export interface ReviewProofInput {
|
|
4
|
+
rawSource: RawSource;
|
|
5
|
+
extraction: Extraction;
|
|
6
|
+
candidate: Candidate;
|
|
7
|
+
candidateSet: CandidateSet;
|
|
8
|
+
reviewOutcome?: ReviewOutcome;
|
|
9
|
+
claim: ClaimTarget;
|
|
10
|
+
sourceRef?: string;
|
|
11
|
+
observedAt?: string;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface CanonicalReviewProofPayload {
|
|
15
|
+
schemaVersion: 1;
|
|
16
|
+
rawSource: {
|
|
17
|
+
id: string;
|
|
18
|
+
kind: RawSource["kind"];
|
|
19
|
+
sourceRef: string;
|
|
20
|
+
observedAt: string;
|
|
21
|
+
fetchedAt?: string;
|
|
22
|
+
checksum?: string;
|
|
23
|
+
locatorScheme: RawSource["locatorScheme"];
|
|
24
|
+
};
|
|
25
|
+
extraction: {
|
|
26
|
+
id: string;
|
|
27
|
+
sourceId: string;
|
|
28
|
+
target: string;
|
|
29
|
+
value: unknown;
|
|
30
|
+
confidence?: number;
|
|
31
|
+
locator?: string;
|
|
32
|
+
excerpt?: string;
|
|
33
|
+
extractor: string;
|
|
34
|
+
extractedAt: string;
|
|
35
|
+
};
|
|
36
|
+
candidate: {
|
|
37
|
+
id: string;
|
|
38
|
+
extractionId: string;
|
|
39
|
+
value: unknown;
|
|
40
|
+
confidence?: number;
|
|
41
|
+
sourceRank?: number;
|
|
42
|
+
};
|
|
43
|
+
candidateSet: {
|
|
44
|
+
id: string;
|
|
45
|
+
target: string;
|
|
46
|
+
candidateIds: string[];
|
|
47
|
+
selectedCandidateId?: string;
|
|
48
|
+
status: CandidateSet["status"];
|
|
49
|
+
rationale?: string;
|
|
50
|
+
};
|
|
51
|
+
reviewOutcome?: {
|
|
52
|
+
id: string;
|
|
53
|
+
candidateSetId: string;
|
|
54
|
+
candidateId?: string;
|
|
55
|
+
status: ReviewOutcome["status"];
|
|
56
|
+
actor?: string;
|
|
57
|
+
reviewedAt?: string;
|
|
58
|
+
rationale?: string;
|
|
59
|
+
evidenceIds?: string[];
|
|
60
|
+
};
|
|
61
|
+
claim: {
|
|
62
|
+
id: string;
|
|
63
|
+
candidateSetId: string;
|
|
64
|
+
candidateId?: string;
|
|
65
|
+
subjectType: string;
|
|
66
|
+
subjectId: string;
|
|
67
|
+
surface: string;
|
|
68
|
+
claimType: string;
|
|
69
|
+
fieldOrBehavior: string;
|
|
70
|
+
value?: unknown;
|
|
71
|
+
status?: ClaimTarget["status"];
|
|
72
|
+
impactLevel: ClaimTarget["impactLevel"];
|
|
73
|
+
createdAt?: string;
|
|
74
|
+
updatedAt?: string;
|
|
75
|
+
evidenceType?: ClaimTarget["evidenceType"];
|
|
76
|
+
evidenceMethod?: ClaimTarget["evidenceMethod"];
|
|
77
|
+
confidenceBasis?: ClaimTarget["confidenceBasis"];
|
|
78
|
+
derivedFrom?: string[];
|
|
79
|
+
derivationEdges?: Array<Omit<NonNullable<ClaimTarget["derivationEdges"]>[number], "metadata">>;
|
|
80
|
+
collectedBy: string;
|
|
81
|
+
actor?: string;
|
|
82
|
+
eventMethod?: string;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export declare function buildCanonicalReviewProofPayload(input: ReviewProofInput): CanonicalReviewProofPayload;
|
|
86
|
+
export declare function canonicalReviewProofJson(payload: CanonicalReviewProofPayload): string;
|
|
87
|
+
export declare function hashCanonicalReviewProofPayload(payload: CanonicalReviewProofPayload): string;
|
|
88
|
+
export declare function buildReviewProofAnchor(input: ReviewProofInput): IntegrityAnchor;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export function buildCanonicalReviewProofPayload(input) {
|
|
3
|
+
return {
|
|
4
|
+
schemaVersion: 1,
|
|
5
|
+
rawSource: {
|
|
6
|
+
id: input.rawSource.id,
|
|
7
|
+
kind: input.rawSource.kind,
|
|
8
|
+
sourceRef: input.rawSource.sourceRef,
|
|
9
|
+
observedAt: input.rawSource.observedAt,
|
|
10
|
+
fetchedAt: input.rawSource.fetchedAt,
|
|
11
|
+
checksum: input.rawSource.checksum,
|
|
12
|
+
locatorScheme: input.rawSource.locatorScheme,
|
|
13
|
+
},
|
|
14
|
+
extraction: {
|
|
15
|
+
id: input.extraction.id,
|
|
16
|
+
sourceId: input.extraction.sourceId,
|
|
17
|
+
target: input.extraction.target,
|
|
18
|
+
value: input.extraction.value,
|
|
19
|
+
confidence: input.extraction.confidence,
|
|
20
|
+
locator: input.extraction.locator,
|
|
21
|
+
excerpt: input.extraction.excerpt,
|
|
22
|
+
extractor: input.extraction.extractor,
|
|
23
|
+
extractedAt: input.extraction.extractedAt,
|
|
24
|
+
},
|
|
25
|
+
candidate: {
|
|
26
|
+
id: input.candidate.id,
|
|
27
|
+
extractionId: input.candidate.extractionId,
|
|
28
|
+
value: input.candidate.value,
|
|
29
|
+
confidence: input.candidate.confidence,
|
|
30
|
+
sourceRank: input.candidate.sourceRank,
|
|
31
|
+
},
|
|
32
|
+
candidateSet: {
|
|
33
|
+
id: input.candidateSet.id,
|
|
34
|
+
target: input.candidateSet.target,
|
|
35
|
+
candidateIds: input.candidateSet.candidates.map((candidate) => candidate.id).sort(),
|
|
36
|
+
selectedCandidateId: input.candidateSet.selectedCandidateId,
|
|
37
|
+
status: input.candidateSet.status,
|
|
38
|
+
rationale: input.candidateSet.rationale,
|
|
39
|
+
},
|
|
40
|
+
reviewOutcome: input.reviewOutcome
|
|
41
|
+
? {
|
|
42
|
+
id: input.reviewOutcome.id,
|
|
43
|
+
candidateSetId: input.reviewOutcome.candidateSetId,
|
|
44
|
+
candidateId: input.reviewOutcome.candidateId,
|
|
45
|
+
status: input.reviewOutcome.status,
|
|
46
|
+
actor: input.reviewOutcome.actor,
|
|
47
|
+
reviewedAt: input.reviewOutcome.reviewedAt,
|
|
48
|
+
rationale: input.reviewOutcome.rationale,
|
|
49
|
+
evidenceIds: input.reviewOutcome.evidenceIds ? [...input.reviewOutcome.evidenceIds].sort() : undefined,
|
|
50
|
+
}
|
|
51
|
+
: undefined,
|
|
52
|
+
claim: {
|
|
53
|
+
id: input.claim.id,
|
|
54
|
+
candidateSetId: input.claim.candidateSetId,
|
|
55
|
+
candidateId: input.claim.candidateId,
|
|
56
|
+
subjectType: input.claim.subjectType,
|
|
57
|
+
subjectId: input.claim.subjectId,
|
|
58
|
+
surface: input.claim.surface,
|
|
59
|
+
claimType: input.claim.claimType,
|
|
60
|
+
fieldOrBehavior: input.claim.fieldOrBehavior,
|
|
61
|
+
value: input.claim.value,
|
|
62
|
+
status: input.claim.status,
|
|
63
|
+
impactLevel: input.claim.impactLevel,
|
|
64
|
+
createdAt: input.claim.createdAt,
|
|
65
|
+
updatedAt: input.claim.updatedAt,
|
|
66
|
+
evidenceType: input.claim.evidenceType,
|
|
67
|
+
evidenceMethod: input.claim.evidenceMethod,
|
|
68
|
+
confidenceBasis: input.claim.confidenceBasis,
|
|
69
|
+
derivedFrom: input.claim.derivedFrom ? [...input.claim.derivedFrom].sort() : undefined,
|
|
70
|
+
derivationEdges: input.claim.derivationEdges?.map((edge) => ({
|
|
71
|
+
inputClaimId: edge.inputClaimId,
|
|
72
|
+
method: edge.method,
|
|
73
|
+
role: edge.role,
|
|
74
|
+
supportStrength: edge.supportStrength,
|
|
75
|
+
rationale: edge.rationale,
|
|
76
|
+
})),
|
|
77
|
+
collectedBy: input.claim.collectedBy,
|
|
78
|
+
actor: input.claim.actor,
|
|
79
|
+
eventMethod: input.claim.eventMethod,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function canonicalReviewProofJson(payload) {
|
|
84
|
+
return JSON.stringify(canonicalize(payload));
|
|
85
|
+
}
|
|
86
|
+
export function hashCanonicalReviewProofPayload(payload) {
|
|
87
|
+
return createHash("sha256").update(canonicalReviewProofJson(payload)).digest("hex");
|
|
88
|
+
}
|
|
89
|
+
export function buildReviewProofAnchor(input) {
|
|
90
|
+
const payload = buildCanonicalReviewProofPayload(input);
|
|
91
|
+
const hash = hashCanonicalReviewProofPayload(payload);
|
|
92
|
+
return {
|
|
93
|
+
id: `review-proof.${input.claim.id}.${hash.slice(0, 16)}`,
|
|
94
|
+
kind: "hash",
|
|
95
|
+
algorithm: "sha256",
|
|
96
|
+
value: hash,
|
|
97
|
+
sourceRef: input.sourceRef ?? input.rawSource.sourceRef,
|
|
98
|
+
observedAt: input.observedAt ?? input.reviewOutcome?.reviewedAt ?? input.rawSource.observedAt,
|
|
99
|
+
verificationStatus: "unverified",
|
|
100
|
+
metadata: input.metadata,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function canonicalize(value) {
|
|
104
|
+
if (Array.isArray(value))
|
|
105
|
+
return value.map((item) => canonicalize(item));
|
|
106
|
+
if (!isRecord(value))
|
|
107
|
+
return value;
|
|
108
|
+
const canonical = Object.create(null);
|
|
109
|
+
for (const key of Object.keys(value).sort()) {
|
|
110
|
+
const item = value[key];
|
|
111
|
+
if (item !== undefined) {
|
|
112
|
+
Object.defineProperty(canonical, key, {
|
|
113
|
+
value: canonicalize(item),
|
|
114
|
+
enumerable: true,
|
|
115
|
+
configurable: true,
|
|
116
|
+
writable: true,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return canonical;
|
|
121
|
+
}
|
|
122
|
+
function isRecord(value) {
|
|
123
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
124
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SurveyObservationInput } from "./builder.js";
|
|
2
|
+
export type SourceAuthorityClass = "official_publication" | "system_of_record" | "publisher_owned_page" | "contract_record" | "policy_document" | "other";
|
|
3
|
+
export interface SourceAuthorityMetadata {
|
|
4
|
+
authorityClass: SourceAuthorityClass;
|
|
5
|
+
scope: Record<string, unknown>;
|
|
6
|
+
effectiveFrom?: string;
|
|
7
|
+
effectiveUntil?: string;
|
|
8
|
+
sourceVersion?: string;
|
|
9
|
+
sourceOwner?: string;
|
|
10
|
+
declaredBy: string;
|
|
11
|
+
metadata?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
export interface SourceOfAuthorityObservationInput<TValue> {
|
|
14
|
+
id: string;
|
|
15
|
+
field: string;
|
|
16
|
+
value: TValue;
|
|
17
|
+
sourceAuthority: SourceAuthorityMetadata;
|
|
18
|
+
rawSource: SurveyObservationInput["rawSource"];
|
|
19
|
+
extraction: Omit<SurveyObservationInput["extraction"], "target" | "value" | "excerpt"> & {
|
|
20
|
+
target?: string;
|
|
21
|
+
excerpt?: string | null;
|
|
22
|
+
};
|
|
23
|
+
reviewOutcome?: SurveyObservationInput["reviewOutcome"];
|
|
24
|
+
claim: Omit<SurveyObservationInput["claim"], "fieldOrBehavior" | "value"> & {
|
|
25
|
+
fieldOrBehavior?: string;
|
|
26
|
+
};
|
|
27
|
+
candidate?: SurveyObservationInput["candidate"];
|
|
28
|
+
candidateSet?: SurveyObservationInput["candidateSet"];
|
|
29
|
+
metadata?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
export declare function sourceOfAuthorityObservation<TValue>(input: SourceOfAuthorityObservationInput<TValue>): SurveyObservationInput;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { buildObservation } from "./observation-helper.js";
|
|
2
|
+
export function sourceOfAuthorityObservation(input) {
|
|
3
|
+
assertSourceAuthority(input.sourceAuthority, input.id);
|
|
4
|
+
assertVerifiedPosture(input);
|
|
5
|
+
const sourceAuthority = {
|
|
6
|
+
...input.sourceAuthority,
|
|
7
|
+
scope: { ...input.sourceAuthority.scope },
|
|
8
|
+
metadata: input.sourceAuthority.metadata
|
|
9
|
+
? { ...input.sourceAuthority.metadata }
|
|
10
|
+
: undefined,
|
|
11
|
+
};
|
|
12
|
+
return buildObservation({
|
|
13
|
+
...input,
|
|
14
|
+
extraction: {
|
|
15
|
+
...input.extraction,
|
|
16
|
+
metadata: {
|
|
17
|
+
...input.extraction.metadata,
|
|
18
|
+
sourceAuthority,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
surveyMetadata: {
|
|
22
|
+
sourceOfAuthority: {
|
|
23
|
+
authorityClass: sourceAuthority.authorityClass,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultExcerpt: `${input.field}: ${valueSummary(input.value)}`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function assertSourceAuthority(sourceAuthority, observationId) {
|
|
30
|
+
if (!sourceAuthority.authorityClass) {
|
|
31
|
+
throw new Error(`Source-of-authority observation ${observationId} needs sourceAuthority.authorityClass`);
|
|
32
|
+
}
|
|
33
|
+
if (!sourceAuthority.declaredBy) {
|
|
34
|
+
throw new Error(`Source-of-authority observation ${observationId} needs sourceAuthority.declaredBy`);
|
|
35
|
+
}
|
|
36
|
+
if (!isRecord(sourceAuthority.scope) || Object.keys(sourceAuthority.scope).length === 0) {
|
|
37
|
+
throw new Error(`Source-of-authority observation ${observationId} needs sourceAuthority.scope`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function assertVerifiedPosture(input) {
|
|
41
|
+
const status = claimStatus(input.claim.status, input.reviewOutcome?.status);
|
|
42
|
+
if (status !== "verified" && status !== "assumed")
|
|
43
|
+
return;
|
|
44
|
+
if (!input.rawSource.sourceRef) {
|
|
45
|
+
throw new Error(`Source-of-authority observation ${input.id} cannot be ${status} without a source reference`);
|
|
46
|
+
}
|
|
47
|
+
if (!input.extraction.locator) {
|
|
48
|
+
throw new Error(`Source-of-authority observation ${input.id} cannot be ${status} without a source locator`);
|
|
49
|
+
}
|
|
50
|
+
if (!input.reviewOutcome) {
|
|
51
|
+
throw new Error(`Source-of-authority observation ${input.id} cannot be ${status} without a review outcome`);
|
|
52
|
+
}
|
|
53
|
+
if (!input.reviewOutcome.actor) {
|
|
54
|
+
throw new Error(`Source-of-authority observation ${input.id} cannot be ${status} without review actor authority`);
|
|
55
|
+
}
|
|
56
|
+
if (!input.reviewOutcome.reviewedAt) {
|
|
57
|
+
throw new Error(`Source-of-authority observation ${input.id} cannot be ${status} without reviewedAt`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function claimStatus(claimStatusValue, reviewStatus) {
|
|
61
|
+
return claimStatusValue ?? reviewStatus;
|
|
62
|
+
}
|
|
63
|
+
function valueSummary(value) {
|
|
64
|
+
if (value === null || value === undefined)
|
|
65
|
+
return "<empty>";
|
|
66
|
+
return String(value);
|
|
67
|
+
}
|
|
68
|
+
function isRecord(value) {
|
|
69
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70
|
+
}
|
package/dist/src/to-surface.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { TrustInput } from "@kontourai/surface";
|
|
2
2
|
import type { SurveyInput } from "./types.js";
|
|
3
|
-
export
|
|
3
|
+
export interface BuildSurveyTrustInputOptions {
|
|
4
|
+
reviewProofs?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function buildSurveyTrustInput(input: SurveyInput, options?: BuildSurveyTrustInputOptions): TrustInput;
|
package/dist/src/to-surface.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { buildReviewProofAnchor } from "./review-proof.js";
|
|
2
|
+
export function buildSurveyTrustInput(input, options = {}) {
|
|
2
3
|
const rawSources = indexById(input.rawSources, "raw source");
|
|
3
4
|
const extractions = indexById(input.extractions, "extraction");
|
|
4
5
|
const candidateSets = indexById(input.candidateSets, "candidate set");
|
|
@@ -18,7 +19,7 @@ export function buildSurveyTrustInput(input) {
|
|
|
18
19
|
const createdAt = projection.createdAt ?? extraction.extractedAt;
|
|
19
20
|
const updatedAt = projection.updatedAt ?? review?.reviewedAt ?? input.generatedAt;
|
|
20
21
|
const evidenceId = `${projection.id}.evidence.source`;
|
|
21
|
-
|
|
22
|
+
const claim = {
|
|
22
23
|
id: projection.id,
|
|
23
24
|
subjectType: projection.subjectType,
|
|
24
25
|
subjectId: projection.subjectId,
|
|
@@ -51,7 +52,22 @@ export function buildSurveyTrustInput(input) {
|
|
|
51
52
|
reviewOutcomeId: review?.id,
|
|
52
53
|
},
|
|
53
54
|
},
|
|
54
|
-
}
|
|
55
|
+
};
|
|
56
|
+
if (options.reviewProofs && review) {
|
|
57
|
+
claim.currentIntegrityAnchor = buildReviewProofAnchor({
|
|
58
|
+
rawSource,
|
|
59
|
+
extraction,
|
|
60
|
+
candidate,
|
|
61
|
+
candidateSet,
|
|
62
|
+
reviewOutcome: review,
|
|
63
|
+
claim: {
|
|
64
|
+
...projection,
|
|
65
|
+
value: claimValue,
|
|
66
|
+
status,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
claims.push(claim);
|
|
55
71
|
evidence.push({
|
|
56
72
|
id: evidenceId,
|
|
57
73
|
claimId: projection.id,
|
|
@@ -72,6 +88,10 @@ export function buildSurveyTrustInput(input) {
|
|
|
72
88
|
confidence: candidate.confidence ?? extraction.confidence,
|
|
73
89
|
},
|
|
74
90
|
});
|
|
91
|
+
const rationale = review?.rationale ?? candidateSet.rationale;
|
|
92
|
+
const comfortZoneNote = review?.withinComfortZone === false
|
|
93
|
+
? `[outside comfort zone] ${review.comfortZoneNote ?? "reviewer flagged this as outside their comfort zone"}`
|
|
94
|
+
: undefined;
|
|
75
95
|
events.push({
|
|
76
96
|
id: `${projection.id}.event.${status}`,
|
|
77
97
|
claimId: projection.id,
|
|
@@ -81,9 +101,31 @@ export function buildSurveyTrustInput(input) {
|
|
|
81
101
|
evidenceIds: review?.evidenceIds?.length ? review.evidenceIds : [evidenceId],
|
|
82
102
|
createdAt: review?.reviewedAt ?? input.generatedAt,
|
|
83
103
|
verifiedAt: status === "verified" || status === "assumed" ? review?.reviewedAt ?? input.generatedAt : undefined,
|
|
84
|
-
notes:
|
|
104
|
+
notes: [rationale, comfortZoneNote].filter(Boolean).join(" | ") || undefined,
|
|
85
105
|
});
|
|
86
106
|
}
|
|
107
|
+
if (input.escalations) {
|
|
108
|
+
const claimIds = new Set(claims.map((c) => c.id));
|
|
109
|
+
for (const escalation of input.escalations) {
|
|
110
|
+
if (escalation.resolvedBy)
|
|
111
|
+
continue;
|
|
112
|
+
if (!escalation.attachToClaimId)
|
|
113
|
+
continue;
|
|
114
|
+
if (!claimIds.has(escalation.attachToClaimId)) {
|
|
115
|
+
throw new Error(`Escalation ${escalation.id} references unknown claim ${escalation.attachToClaimId}`);
|
|
116
|
+
}
|
|
117
|
+
events.push({
|
|
118
|
+
id: `${escalation.id}.event`,
|
|
119
|
+
claimId: escalation.attachToClaimId,
|
|
120
|
+
status: "disputed",
|
|
121
|
+
actor: escalation.raisedBy,
|
|
122
|
+
method: "candidate-escalation",
|
|
123
|
+
evidenceIds: [],
|
|
124
|
+
createdAt: escalation.raisedAt,
|
|
125
|
+
notes: `[${escalation.dimension}] ${escalation.reason}`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
87
129
|
return {
|
|
88
130
|
schemaVersion: 3,
|
|
89
131
|
source: input.source,
|
|
@@ -101,6 +143,8 @@ function statusFor(input) {
|
|
|
101
143
|
}
|
|
102
144
|
if (input.candidateSet.status === "conflict")
|
|
103
145
|
return "disputed";
|
|
146
|
+
if (input.candidateSet.status === "escalated")
|
|
147
|
+
return "disputed";
|
|
104
148
|
return "proposed";
|
|
105
149
|
}
|
|
106
150
|
function assertProducerDiscipline(input) {
|
|
@@ -144,6 +188,8 @@ function eventMethodFor(status, candidateSet) {
|
|
|
144
188
|
return "survey-rejection";
|
|
145
189
|
if (candidateSet.status === "conflict")
|
|
146
190
|
return "candidate-conflict";
|
|
191
|
+
if (candidateSet.status === "escalated")
|
|
192
|
+
return "candidate-escalation";
|
|
147
193
|
return "candidate-proposal";
|
|
148
194
|
}
|
|
149
195
|
function indexById(items, label) {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -23,7 +23,8 @@ export interface Extraction {
|
|
|
23
23
|
extractedAt: string;
|
|
24
24
|
metadata?: Record<string, unknown>;
|
|
25
25
|
}
|
|
26
|
-
export type CandidateSetStatus = "resolved" | "needs-review" | "conflict";
|
|
26
|
+
export type CandidateSetStatus = "resolved" | "needs-review" | "conflict" | "escalated";
|
|
27
|
+
export type EscalationDimension = "framing" | "completeness" | "conclusion" | "citation";
|
|
27
28
|
export interface Candidate {
|
|
28
29
|
id: string;
|
|
29
30
|
extractionId: string;
|
|
@@ -51,6 +52,19 @@ export interface ReviewOutcome {
|
|
|
51
52
|
reviewedAt?: string;
|
|
52
53
|
rationale?: string;
|
|
53
54
|
evidenceIds?: string[];
|
|
55
|
+
withinComfortZone?: boolean;
|
|
56
|
+
comfortZoneNote?: string;
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
export interface EscalationRecord {
|
|
60
|
+
id: string;
|
|
61
|
+
target: string;
|
|
62
|
+
dimension: EscalationDimension;
|
|
63
|
+
reason: string;
|
|
64
|
+
raisedBy: string;
|
|
65
|
+
raisedAt: string;
|
|
66
|
+
attachToClaimId?: string;
|
|
67
|
+
resolvedBy?: string;
|
|
54
68
|
metadata?: Record<string, unknown>;
|
|
55
69
|
}
|
|
56
70
|
export interface ClaimTarget {
|
|
@@ -85,4 +99,5 @@ export interface SurveyInput {
|
|
|
85
99
|
candidateSets: CandidateSet[];
|
|
86
100
|
reviewOutcomes: ReviewOutcome[];
|
|
87
101
|
claims: ClaimTarget[];
|
|
102
|
+
escalations?: EscalationRecord[];
|
|
88
103
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/survey",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"check:content-boundary": "node scripts/check-content-boundary.cjs"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@kontourai/surface": "^0.5.
|
|
38
|
+
"@kontourai/surface": "^0.5.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.6.0",
|