@kontourai/survey 0.4.2 → 0.4.4

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.
Files changed (34) hide show
  1. package/README.md +247 -35
  2. package/dist/examples/review-workbench/downstream-public-directory-adapter.d.ts +3 -0
  3. package/dist/examples/review-workbench/downstream-public-directory-adapter.js +262 -0
  4. package/dist/examples/review-workbench/review-queue-session.d.ts +54 -0
  5. package/dist/examples/review-workbench/review-queue-session.js +122 -0
  6. package/dist/examples/review-workbench/review-surface-preview.d.ts +55 -0
  7. package/dist/examples/review-workbench/review-surface-preview.js +104 -0
  8. package/dist/examples/review-workbench/review-workbench-data.d.ts +261 -0
  9. package/dist/examples/review-workbench/review-workbench-data.js +199 -0
  10. package/dist/examples/review-workbench/review-workbench.d.ts +7 -0
  11. package/dist/examples/review-workbench/review-workbench.js +488 -0
  12. package/dist/fixtures/downstream-public-directory-proposal.d.ts +100 -0
  13. package/dist/fixtures/downstream-public-directory-proposal.js +59 -0
  14. package/dist/fixtures/public-directory-review-resource.d.ts +156 -0
  15. package/dist/fixtures/public-directory-review-resource.js +157 -0
  16. package/dist/fixtures/regulated-document-review-resource.d.ts +121 -0
  17. package/dist/fixtures/regulated-document-review-resource.js +131 -0
  18. package/dist/src/builder.d.ts +4 -1
  19. package/dist/src/builder.js +7 -0
  20. package/dist/src/index.d.ts +10 -6
  21. package/dist/src/index.js +5 -3
  22. package/dist/src/learning-projections.d.ts +19 -0
  23. package/dist/src/learning-projections.js +77 -0
  24. package/dist/src/raw-source.d.ts +15 -0
  25. package/dist/src/raw-source.js +22 -0
  26. package/dist/src/review-proof.d.ts +28 -0
  27. package/dist/src/review-proof.js +58 -1
  28. package/dist/src/review-resource.d.ts +111 -0
  29. package/dist/src/review-resource.js +1 -0
  30. package/dist/src/source-of-authority-observation.d.ts +20 -0
  31. package/dist/src/source-of-authority-observation.js +68 -0
  32. package/dist/src/to-surface.js +226 -13
  33. package/dist/src/types.d.ts +17 -1
  34. package/package.json +10 -4
@@ -0,0 +1,19 @@
1
+ import type { SurveyInput } from "./types.js";
2
+ export type LearningProjectionKind = "learning.comfort-zone" | "learning.escalation";
3
+ export type LearningProjectionSignal = "comfort-zone.outside" | "escalation.unresolved";
4
+ export type LearningProjectionSeverity = "info" | "attention";
5
+ export interface LearningProjection {
6
+ id: string;
7
+ kind: LearningProjectionKind;
8
+ source: string;
9
+ createdAt: string;
10
+ target?: string;
11
+ claimId?: string;
12
+ reviewOutcomeId?: string;
13
+ escalationId?: string;
14
+ signal: LearningProjectionSignal;
15
+ severity?: LearningProjectionSeverity;
16
+ summary: string;
17
+ metadata?: Record<string, unknown>;
18
+ }
19
+ export declare function buildSurveyLearningProjections(input: SurveyInput): LearningProjection[];
@@ -0,0 +1,77 @@
1
+ export function buildSurveyLearningProjections(input) {
2
+ const claimsByCandidateSet = groupBy(input.claims, (claim) => claim.candidateSetId);
3
+ const candidateSetTargets = new Map(input.candidateSets.map((candidateSet) => [candidateSet.id, candidateSet.target]));
4
+ const projections = [];
5
+ for (const reviewOutcome of input.reviewOutcomes) {
6
+ if (reviewOutcome.withinComfortZone !== false)
7
+ continue;
8
+ const target = candidateSetTargets.get(reviewOutcome.candidateSetId);
9
+ const claim = findClaimForReview(claimsByCandidateSet.get(reviewOutcome.candidateSetId) ?? [], reviewOutcome);
10
+ const note = reviewOutcome.comfortZoneNote;
11
+ projections.push({
12
+ id: `${reviewOutcome.id}.learning.comfort-zone`,
13
+ kind: "learning.comfort-zone",
14
+ source: input.source,
15
+ createdAt: reviewOutcome.reviewedAt ?? input.generatedAt,
16
+ target,
17
+ claimId: claim?.id,
18
+ reviewOutcomeId: reviewOutcome.id,
19
+ signal: "comfort-zone.outside",
20
+ severity: "attention",
21
+ summary: note ? `Review outcome is outside comfort zone: ${note}` : "Review outcome is outside comfort zone.",
22
+ metadata: {
23
+ comfortZone: {
24
+ withinComfortZone: false,
25
+ ...(note ? { note } : {}),
26
+ },
27
+ },
28
+ });
29
+ }
30
+ for (const escalation of input.escalations ?? []) {
31
+ if (escalation.resolvedBy)
32
+ continue;
33
+ projections.push({
34
+ id: `${escalation.id}.learning.escalation`,
35
+ kind: "learning.escalation",
36
+ source: input.source,
37
+ createdAt: escalation.raisedAt,
38
+ target: escalation.target,
39
+ claimId: escalation.attachToClaimId,
40
+ escalationId: escalation.id,
41
+ signal: "escalation.unresolved",
42
+ severity: "attention",
43
+ summary: `Unresolved ${escalation.dimension} escalation: ${escalation.reason}`,
44
+ metadata: {
45
+ escalation: unresolvedEscalationMetadata(escalation),
46
+ },
47
+ });
48
+ }
49
+ return projections;
50
+ }
51
+ function findClaimForReview(claims, reviewOutcome) {
52
+ if (reviewOutcome.candidateId) {
53
+ return claims.find((claim) => claim.candidateId === reviewOutcome.candidateId);
54
+ }
55
+ return claims[0];
56
+ }
57
+ function unresolvedEscalationMetadata(escalation) {
58
+ return {
59
+ id: escalation.id,
60
+ target: escalation.target,
61
+ dimension: escalation.dimension,
62
+ reason: escalation.reason,
63
+ raisedBy: escalation.raisedBy,
64
+ raisedAt: escalation.raisedAt,
65
+ ...(escalation.attachToClaimId ? { attachToClaimId: escalation.attachToClaimId } : {}),
66
+ resolved: false,
67
+ ...(escalation.metadata ? { metadata: escalation.metadata } : {}),
68
+ };
69
+ }
70
+ function groupBy(items, getKey) {
71
+ const map = new Map();
72
+ for (const item of items) {
73
+ const key = getKey(item);
74
+ map.set(key, [...(map.get(key) ?? []), item]);
75
+ }
76
+ return map;
77
+ }
@@ -25,7 +25,22 @@ export interface ManualEntrySourceInput extends Omit<RawSourceInput, "checksum"
25
25
  checksum?: ChecksumInput;
26
26
  locatorScheme?: LocatorScheme;
27
27
  }
28
+ export interface PolicyStandardMetadata {
29
+ inlineText: string;
30
+ standardVersion: string;
31
+ paragraphRef?: string;
32
+ reference?: string;
33
+ }
34
+ export interface PolicyStandardSourceInput extends Omit<RawSourceInput, "locatorScheme" | "metadata"> {
35
+ inlineText: string;
36
+ standardVersion: string;
37
+ paragraphRef?: string;
38
+ reference?: string;
39
+ locatorScheme?: LocatorScheme;
40
+ metadata?: Record<string, unknown>;
41
+ }
28
42
  export declare function uploadedDocumentSource(input: UploadedDocumentSourceInput): RawSource;
29
43
  export declare function apiRecordSource(input: ApiRecordSourceInput): RawSource;
30
44
  export declare function webPageSource(input: WebPageSourceInput): RawSource;
31
45
  export declare function manualEntrySource(input: ManualEntrySourceInput): RawSource;
46
+ export declare function policyStandardSource(input: PolicyStandardSourceInput): RawSource;
@@ -19,6 +19,28 @@ export function manualEntrySource(input) {
19
19
  ...input,
20
20
  });
21
21
  }
22
+ export function policyStandardSource(input) {
23
+ const policyStandard = {
24
+ inlineText: input.inlineText,
25
+ standardVersion: input.standardVersion,
26
+ paragraphRef: input.paragraphRef,
27
+ reference: input.reference,
28
+ };
29
+ const source = rawSource("policy-standard", {
30
+ locatorScheme: "text",
31
+ ...input,
32
+ metadata: {
33
+ ...input.metadata,
34
+ policyStandard,
35
+ },
36
+ });
37
+ return {
38
+ ...source,
39
+ inlineText: input.inlineText,
40
+ standardVersion: input.standardVersion,
41
+ paragraphRef: input.paragraphRef,
42
+ };
43
+ }
22
44
  function rawSource(kind, input) {
23
45
  return {
24
46
  id: input.id ?? sourceId(kind, input.sourceRef),
@@ -1,5 +1,9 @@
1
1
  import type { IntegrityAnchor } from "@kontourai/surface";
2
2
  import type { Candidate, CandidateSet, ClaimTarget, Extraction, RawSource, ReviewOutcome } from "./types.js";
3
+ export declare const REVIEW_PROOF_SCHEMA = "survey.review-proof";
4
+ export declare const REVIEW_PROOF_SCHEMA_VERSION = 1;
5
+ export declare const REVIEW_PROOF_PACKAGE_NAME = "@kontourai/survey";
6
+ export declare const REVIEW_PROOF_CONTRACT_VERSION = "1";
3
7
  export interface ReviewProofInput {
4
8
  rawSource: RawSource;
5
9
  extraction: Extraction;
@@ -13,6 +17,30 @@ export interface ReviewProofInput {
13
17
  }
14
18
  export interface CanonicalReviewProofPayload {
15
19
  schemaVersion: 1;
20
+ proof: {
21
+ schema: typeof REVIEW_PROOF_SCHEMA;
22
+ schemaVersion: typeof REVIEW_PROOF_SCHEMA_VERSION;
23
+ packageName: typeof REVIEW_PROOF_PACKAGE_NAME;
24
+ packageVersion: typeof REVIEW_PROOF_CONTRACT_VERSION;
25
+ issuer: string;
26
+ producer: string;
27
+ issuedAt: string;
28
+ subject: {
29
+ claimId: string;
30
+ candidateSetId: string;
31
+ candidateId?: string;
32
+ subjectType: string;
33
+ subjectId: string;
34
+ surface: string;
35
+ claimType: string;
36
+ fieldOrBehavior: string;
37
+ };
38
+ sourcePayload: {
39
+ id: string;
40
+ sourceRef: string;
41
+ checksum?: string;
42
+ };
43
+ };
16
44
  rawSource: {
17
45
  id: string;
18
46
  kind: RawSource["kind"];
@@ -1,7 +1,38 @@
1
1
  import { createHash } from "node:crypto";
2
+ export const REVIEW_PROOF_SCHEMA = "survey.review-proof";
3
+ export const REVIEW_PROOF_SCHEMA_VERSION = 1;
4
+ export const REVIEW_PROOF_PACKAGE_NAME = "@kontourai/survey";
5
+ // Version of the review proof contract emitted by this helper. This is intentionally
6
+ // independent from the npm package release version because it participates in hashes.
7
+ export const REVIEW_PROOF_CONTRACT_VERSION = "1";
2
8
  export function buildCanonicalReviewProofPayload(input) {
9
+ assertCandidateConsistency(input);
3
10
  return {
4
- schemaVersion: 1,
11
+ schemaVersion: REVIEW_PROOF_SCHEMA_VERSION,
12
+ proof: {
13
+ schema: REVIEW_PROOF_SCHEMA,
14
+ schemaVersion: REVIEW_PROOF_SCHEMA_VERSION,
15
+ packageName: REVIEW_PROOF_PACKAGE_NAME,
16
+ packageVersion: REVIEW_PROOF_CONTRACT_VERSION,
17
+ issuer: input.claim.collectedBy,
18
+ producer: input.extraction.extractor,
19
+ issuedAt: input.reviewOutcome?.reviewedAt ?? input.claim.updatedAt ?? input.extraction.extractedAt,
20
+ subject: {
21
+ claimId: input.claim.id,
22
+ candidateSetId: input.claim.candidateSetId,
23
+ candidateId: input.candidate.id,
24
+ subjectType: input.claim.subjectType,
25
+ subjectId: input.claim.subjectId,
26
+ surface: input.claim.surface,
27
+ claimType: input.claim.claimType,
28
+ fieldOrBehavior: input.claim.fieldOrBehavior,
29
+ },
30
+ sourcePayload: {
31
+ id: input.rawSource.id,
32
+ sourceRef: input.rawSource.sourceRef,
33
+ checksum: input.rawSource.checksum,
34
+ },
35
+ },
5
36
  rawSource: {
6
37
  id: input.rawSource.id,
7
38
  kind: input.rawSource.kind,
@@ -80,6 +111,32 @@ export function buildCanonicalReviewProofPayload(input) {
80
111
  },
81
112
  };
82
113
  }
114
+ function assertCandidateConsistency(input) {
115
+ if (input.extraction.sourceId !== input.rawSource.id) {
116
+ throw new Error(`Review proof extraction sourceId "${input.extraction.sourceId}" does not match raw source id "${input.rawSource.id}".`);
117
+ }
118
+ if (input.candidate.extractionId !== input.extraction.id) {
119
+ throw new Error(`Review proof candidate extractionId "${input.candidate.extractionId}" does not match extraction id "${input.extraction.id}".`);
120
+ }
121
+ if (!input.candidateSet.candidates.some((candidate) => candidate.id === input.candidate.id)) {
122
+ throw new Error(`Review proof candidate id "${input.candidate.id}" is not present in candidate set "${input.candidateSet.id}".`);
123
+ }
124
+ if (input.candidateSet.selectedCandidateId && input.candidateSet.selectedCandidateId !== input.candidate.id) {
125
+ throw new Error(`Review proof candidate set selectedCandidateId "${input.candidateSet.selectedCandidateId}" does not match reviewed candidate id "${input.candidate.id}".`);
126
+ }
127
+ if (input.claim.candidateSetId !== input.candidateSet.id) {
128
+ throw new Error(`Review proof claim candidateSetId "${input.claim.candidateSetId}" does not match candidate set id "${input.candidateSet.id}".`);
129
+ }
130
+ if (input.claim.candidateId && input.claim.candidateId !== input.candidate.id) {
131
+ throw new Error(`Review proof claim candidateId "${input.claim.candidateId}" does not match reviewed candidate id "${input.candidate.id}".`);
132
+ }
133
+ if (input.reviewOutcome && input.reviewOutcome.candidateSetId !== input.candidateSet.id) {
134
+ throw new Error(`Review proof review outcome candidateSetId "${input.reviewOutcome.candidateSetId}" does not match candidate set id "${input.candidateSet.id}".`);
135
+ }
136
+ if (input.reviewOutcome?.candidateId && input.reviewOutcome.candidateId !== input.candidate.id) {
137
+ throw new Error(`Review proof review outcome candidateId "${input.reviewOutcome.candidateId}" does not match reviewed candidate id "${input.candidate.id}".`);
138
+ }
139
+ }
83
140
  export function canonicalReviewProofJson(payload) {
84
141
  return JSON.stringify(canonicalize(payload));
85
142
  }
@@ -0,0 +1,111 @@
1
+ import type { CandidateSetStatus, ClaimTarget, Extraction, RawSource, ReviewOutcome } from "./types.js";
2
+ export declare const reviewResourceApiVersion = "survey.kontourai.io/v1alpha1";
3
+ export type ReviewResourceApiVersion = typeof reviewResourceApiVersion;
4
+ export type ReviewResourceKind = "ReviewItem" | "ReviewDecision";
5
+ export interface ResourceMetadata {
6
+ name: string;
7
+ uid?: string;
8
+ labels?: Record<string, string>;
9
+ annotations?: Record<string, string>;
10
+ producer?: Record<string, unknown>;
11
+ }
12
+ export interface ResourceEnvelope<K extends ReviewResourceKind, Spec, Status = Record<string, never>> {
13
+ apiVersion: ReviewResourceApiVersion;
14
+ kind: K;
15
+ metadata: ResourceMetadata;
16
+ spec: Spec;
17
+ status?: Status;
18
+ }
19
+ export type CandidateRole = "current" | "proposed" | "alternative" | "source-version" | "computed";
20
+ export interface SourceReference {
21
+ sourceRef: string;
22
+ sourceId?: string;
23
+ kind?: RawSource["kind"];
24
+ observedAt?: string;
25
+ fetchedAt?: string;
26
+ checksum?: string;
27
+ locatorScheme?: RawSource["locatorScheme"];
28
+ }
29
+ export interface ReviewLocator {
30
+ scheme: RawSource["locatorScheme"];
31
+ locator?: string;
32
+ excerpt?: string;
33
+ }
34
+ export interface ExtractionReference {
35
+ extractionId?: string;
36
+ target: string;
37
+ confidence?: number;
38
+ extractor?: string;
39
+ extractedAt?: string;
40
+ }
41
+ export interface ClaimTargetHint {
42
+ claimId?: string;
43
+ subjectType: string;
44
+ subjectId: string;
45
+ surface: string;
46
+ claimType: string;
47
+ fieldOrBehavior: string;
48
+ impactLevel: ClaimTarget["impactLevel"];
49
+ evidenceType?: ClaimTarget["evidenceType"];
50
+ evidenceMethod?: ClaimTarget["evidenceMethod"];
51
+ collectedBy?: string;
52
+ derivedFrom?: string[];
53
+ }
54
+ export interface SurveyRecordProjectionHint {
55
+ rawSourceId?: RawSource["id"];
56
+ extractionId?: Extraction["id"];
57
+ candidateSetId?: string;
58
+ candidateId?: string;
59
+ reviewOutcomeId?: ReviewOutcome["id"];
60
+ claimId?: ClaimTarget["id"];
61
+ }
62
+ export interface ReviewCandidate {
63
+ id: string;
64
+ role?: CandidateRole;
65
+ value: unknown;
66
+ confidence?: number;
67
+ sourceRank?: number;
68
+ rejectionReason?: string;
69
+ source: SourceReference;
70
+ locator?: ReviewLocator;
71
+ extraction: ExtractionReference;
72
+ claimTarget: ClaimTargetHint;
73
+ projection?: SurveyRecordProjectionHint;
74
+ producer?: Record<string, unknown>;
75
+ }
76
+ export interface ReviewItemSpec {
77
+ target: string;
78
+ candidates: ReviewCandidate[];
79
+ candidateSetStatus?: CandidateSetStatus;
80
+ selectedCandidateId?: string;
81
+ rationale?: string;
82
+ producerPolicy?: Record<string, unknown>;
83
+ projection?: SurveyRecordProjectionHint;
84
+ }
85
+ export interface ReviewItemStatus {
86
+ observedCandidateCount?: number;
87
+ selectedCandidateId?: string;
88
+ reviewDecisionName?: string;
89
+ }
90
+ export type ReviewItem = ResourceEnvelope<"ReviewItem", ReviewItemSpec, ReviewItemStatus>;
91
+ export interface ReviewActor {
92
+ id: string;
93
+ displayName?: string;
94
+ }
95
+ export interface ReviewDecisionSpec {
96
+ reviewItemName: string;
97
+ candidateId?: string;
98
+ status: ReviewOutcome["status"];
99
+ actor?: ReviewActor;
100
+ reviewedAt?: string;
101
+ rationale?: string;
102
+ evidenceIds?: string[];
103
+ withinComfortZone?: boolean;
104
+ comfortZoneNote?: string;
105
+ projection?: SurveyRecordProjectionHint;
106
+ }
107
+ export interface ReviewDecisionStatus {
108
+ appliedToClaimIds?: string[];
109
+ }
110
+ export type ReviewDecision = ResourceEnvelope<"ReviewDecision", ReviewDecisionSpec, ReviewDecisionStatus>;
111
+ export type ReviewResource = ReviewItem | ReviewDecision;
@@ -0,0 +1 @@
1
+ export const reviewResourceApiVersion = "survey.kontourai.io/v1alpha1";
@@ -28,4 +28,24 @@ export interface SourceOfAuthorityObservationInput<TValue> {
28
28
  candidateSet?: SurveyObservationInput["candidateSet"];
29
29
  metadata?: Record<string, unknown>;
30
30
  }
31
+ export interface SourceOfAuthorityObservationBuilderArgs<TValue> {
32
+ id: string;
33
+ field: string;
34
+ value: TValue;
35
+ }
36
+ export declare class SourceOfAuthorityObservationBuilder<TValue> {
37
+ private readonly state;
38
+ constructor(args: SourceOfAuthorityObservationBuilderArgs<TValue>);
39
+ static create<TValue>(args: SourceOfAuthorityObservationBuilderArgs<TValue>): SourceOfAuthorityObservationBuilder<TValue>;
40
+ withSourceAuthority(sourceAuthority: SourceAuthorityMetadata): this;
41
+ fromSource(rawSource: SourceOfAuthorityObservationInput<TValue>["rawSource"]): this;
42
+ withExtraction(extraction: SourceOfAuthorityObservationInput<TValue>["extraction"]): this;
43
+ withReviewOutcome(reviewOutcome: SourceOfAuthorityObservationInput<TValue>["reviewOutcome"]): this;
44
+ forClaim(claim: SourceOfAuthorityObservationInput<TValue>["claim"]): this;
45
+ withCandidate(candidate: SourceOfAuthorityObservationInput<TValue>["candidate"]): this;
46
+ withCandidateSet(candidateSet: SourceOfAuthorityObservationInput<TValue>["candidateSet"]): this;
47
+ withMetadata(metadata: Record<string, unknown>): this;
48
+ build(): SurveyObservationInput;
49
+ }
50
+ export declare function sourceOfAuthorityObservationBuilder<TValue>(args: SourceOfAuthorityObservationBuilderArgs<TValue>): SourceOfAuthorityObservationBuilder<TValue>;
31
51
  export declare function sourceOfAuthorityObservation<TValue>(input: SourceOfAuthorityObservationInput<TValue>): SurveyObservationInput;
@@ -1,4 +1,51 @@
1
1
  import { buildObservation } from "./observation-helper.js";
2
+ export class SourceOfAuthorityObservationBuilder {
3
+ state;
4
+ constructor(args) {
5
+ this.state = { ...args };
6
+ }
7
+ static create(args) {
8
+ return new SourceOfAuthorityObservationBuilder(args);
9
+ }
10
+ withSourceAuthority(sourceAuthority) {
11
+ this.state.sourceAuthority = sourceAuthority;
12
+ return this;
13
+ }
14
+ fromSource(rawSource) {
15
+ this.state.rawSource = rawSource;
16
+ return this;
17
+ }
18
+ withExtraction(extraction) {
19
+ this.state.extraction = extraction;
20
+ return this;
21
+ }
22
+ withReviewOutcome(reviewOutcome) {
23
+ this.state.reviewOutcome = reviewOutcome;
24
+ return this;
25
+ }
26
+ forClaim(claim) {
27
+ this.state.claim = claim;
28
+ return this;
29
+ }
30
+ withCandidate(candidate) {
31
+ this.state.candidate = candidate;
32
+ return this;
33
+ }
34
+ withCandidateSet(candidateSet) {
35
+ this.state.candidateSet = candidateSet;
36
+ return this;
37
+ }
38
+ withMetadata(metadata) {
39
+ this.state.metadata = metadata;
40
+ return this;
41
+ }
42
+ build() {
43
+ return sourceOfAuthorityObservation(completeBuilderState(this.state));
44
+ }
45
+ }
46
+ export function sourceOfAuthorityObservationBuilder(args) {
47
+ return SourceOfAuthorityObservationBuilder.create(args);
48
+ }
2
49
  export function sourceOfAuthorityObservation(input) {
3
50
  assertSourceAuthority(input.sourceAuthority, input.id);
4
51
  assertVerifiedPosture(input);
@@ -26,6 +73,27 @@ export function sourceOfAuthorityObservation(input) {
26
73
  defaultExcerpt: `${input.field}: ${valueSummary(input.value)}`,
27
74
  });
28
75
  }
76
+ function completeBuilderState(state) {
77
+ return {
78
+ id: state.id,
79
+ field: state.field,
80
+ value: state.value,
81
+ sourceAuthority: requireBuilderField(state.sourceAuthority, state.id, "sourceAuthority"),
82
+ rawSource: requireBuilderField(state.rawSource, state.id, "rawSource"),
83
+ extraction: requireBuilderField(state.extraction, state.id, "extraction"),
84
+ reviewOutcome: state.reviewOutcome,
85
+ claim: requireBuilderField(state.claim, state.id, "claim"),
86
+ candidate: state.candidate,
87
+ candidateSet: state.candidateSet,
88
+ metadata: state.metadata,
89
+ };
90
+ }
91
+ function requireBuilderField(value, observationId, field) {
92
+ if (value === undefined) {
93
+ throw new Error(`Source-of-authority observation ${observationId} builder needs ${field}`);
94
+ }
95
+ return value;
96
+ }
29
97
  function assertSourceAuthority(sourceAuthority, observationId) {
30
98
  if (!sourceAuthority.authorityClass) {
31
99
  throw new Error(`Source-of-authority observation ${observationId} needs sourceAuthority.authorityClass`);