@kontourai/survey 0.1.4 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # Kontour Survey
2
2
 
3
- Survey is the producer-side contract for turning raw observations into
4
- Surface-ready trust records.
3
+ Survey is the producer-side contract for turning producer observations into
4
+ Surface-ready `TrustInput`.
5
5
 
6
6
  This repo is intentionally small right now. It is a proof package, not an
7
7
  ingestion platform:
@@ -10,8 +10,8 @@ ingestion platform:
10
10
  - Survey owns source, extraction, candidate, and review record shapes;
11
11
  - `buildSurveyTrustInput` projects those records into `@kontourai/surface`
12
12
  `TrustInput`;
13
- - Surface owns trust reporting, derivation, console projections, and downstream
14
- transparency.
13
+ - Surface owns Claim, Subject, Claim Type, Evidence, Status, Claim Dependency,
14
+ TrustInput, trust reporting, console projections, and downstream transparency.
15
15
 
16
16
  The first success criterion is that generic corrected-document and public-field
17
17
  fixtures can pass through Survey and produce valid Surface reports without
@@ -198,6 +198,119 @@ Producer metadata is preserved. Producers still own item semantics,
198
198
  validation, candidate ranking, review policy, and whether a value should be
199
199
  verified, proposed, rejected, or assumed.
200
200
 
201
+ ## Candidate review records
202
+
203
+ Use `candidateReviewRecord` when a producer has multiple candidate observations
204
+ for the same target and wants Survey to assemble the shared candidate set,
205
+ candidate links, and optional review outcome.
206
+
207
+ ```ts
208
+ import {
209
+ candidateReviewRecord,
210
+ fieldObservation,
211
+ SurveyInputBuilder,
212
+ } from "@kontourai/survey";
213
+
214
+ const observations = [
215
+ fieldObservation({
216
+ id: "entity-123.status.registry",
217
+ field: "registrationStatus",
218
+ value: "ACTIVE",
219
+ rawSource: {
220
+ kind: "api-record",
221
+ sourceRef: "example-records://entity/entity-123",
222
+ observedAt: new Date().toISOString(),
223
+ locatorScheme: "structured-field",
224
+ },
225
+ extraction: {
226
+ confidence: 0.97,
227
+ locator: "json:$.registrationStatus",
228
+ extractor: "example-extractor",
229
+ extractedAt: new Date().toISOString(),
230
+ },
231
+ candidate: { id: "candidate.registry", confidence: 0.97 },
232
+ claim: {
233
+ id: "claim.entity-123.status.registry",
234
+ subjectType: "public-record.entity",
235
+ subjectId: "entity-123",
236
+ surface: "example.profile",
237
+ claimType: "public-data.field",
238
+ status: "verified",
239
+ impactLevel: "medium",
240
+ collectedBy: "example-extractor",
241
+ },
242
+ }),
243
+ fieldObservation({
244
+ id: "entity-123.status.archive",
245
+ field: "registrationStatus",
246
+ value: "INACTIVE",
247
+ rawSource: {
248
+ kind: "web-page",
249
+ sourceRef: "https://records.example.test/entity-123",
250
+ observedAt: new Date().toISOString(),
251
+ locatorScheme: "html",
252
+ },
253
+ extraction: {
254
+ confidence: 0.71,
255
+ locator: "css:#registration-status",
256
+ extractor: "example-crawler",
257
+ extractedAt: new Date().toISOString(),
258
+ },
259
+ candidate: { id: "candidate.archive", confidence: 0.71 },
260
+ claim: {
261
+ id: "claim.entity-123.status.archive",
262
+ subjectType: "public-record.entity",
263
+ subjectId: "entity-123",
264
+ surface: "example.profile",
265
+ claimType: "public-data.field",
266
+ status: "superseded",
267
+ impactLevel: "medium",
268
+ collectedBy: "example-crawler",
269
+ },
270
+ }),
271
+ ];
272
+
273
+ const surveyInput = new SurveyInputBuilder({ source: "example-producer:run-1" })
274
+ .addClaimRecords(candidateReviewRecord({
275
+ id: "candidate-set.entity-123.registration-status",
276
+ target: "registrationStatus",
277
+ selectedCandidateId: "candidate.registry",
278
+ status: "resolved",
279
+ rationale: "Registry source wins over archive source.",
280
+ reviewOutcome: {
281
+ status: "verified",
282
+ actor: "records-operator",
283
+ reviewedAt: new Date().toISOString(),
284
+ },
285
+ observations,
286
+ }))
287
+ .build();
288
+ ```
289
+
290
+ `candidateReviewRecord` does not choose the winning candidate or status. The
291
+ producer still supplies candidate ids, selected candidate id, claim ids, review
292
+ status, rationale, and all domain policy. Survey only assembles the generic
293
+ record graph and tolerates repeated references to identical raw sources or the
294
+ shared candidate set while rejecting conflicting duplicate ids. Duplicate
295
+ conflict checks assume Survey records are JSON-shaped data, which is the same
296
+ shape expected by Surface validation and reports.
297
+
298
+ A candidate set with status `"conflict"` represents a Survey-side Candidate
299
+ Conflict before review has resolved which candidate should win. When no review
300
+ outcome overrides it, `buildSurveyTrustInput` projects the claim to Surface
301
+ status `"disputed"` and records a `"candidate-conflict"` verification event.
302
+
303
+ ## Computed values
304
+
305
+ Computed values are normal `ClaimTarget` entries in `claims`. Producers should
306
+ link them to their inputs with Surface Claim Dependency fields:
307
+ `derivedFrom` for simple claim-id links, or `derivationEdges` when the link
308
+ needs method, role, support-strength, rationale, or metadata.
309
+
310
+ Survey passes those fields through to Surface while keeping the same
311
+ source -> extraction -> candidate -> review -> claim projection path. Surface
312
+ owns dependency semantics such as recompute pressure and status ceilings.
313
+
201
314
  ## Product Boundary
202
315
 
203
316
  Survey does not crawl pages, parse PDFs, rank candidates, decide review policy,
@@ -19,16 +19,22 @@ export const correctedDocumentCandidatesFixture = {
19
19
  checksum: "sha256:corrected",
20
20
  locatorScheme: "pdf",
21
21
  },
22
+ computationSource("current"),
23
+ computationSource("original"),
22
24
  ],
23
25
  extractions: [
24
26
  documentExtraction("original:amount", "document:source:original", "statement.totalAmount", 82000, "pdf:page=1;box=1"),
25
27
  documentExtraction("original:credit", "document:source:original", "statement.creditAmount", 8600, "pdf:page=1;box=2"),
26
28
  documentExtraction("corrected:amount", "document:source:corrected", "statement.totalAmount", 84000, "pdf:page=1;box=1"),
27
29
  documentExtraction("corrected:credit", "document:source:corrected", "statement.creditAmount", 9100, "pdf:page=1;box=2"),
30
+ computationExtraction("current", 84000, 9100),
31
+ computationExtraction("original", 82000, 8600),
28
32
  ],
29
33
  candidateSets: [
30
34
  documentCandidateSet("amount", "original:amount", 82000, "corrected:amount", 84000),
31
35
  documentCandidateSet("credit", "original:credit", 8600, "corrected:credit", 9100),
36
+ computationCandidateSet("current", 84000, 9100),
37
+ computationCandidateSet("original", 82000, 8600),
32
38
  ],
33
39
  reviewOutcomes: [],
34
40
  claims: [
@@ -36,58 +42,23 @@ export const correctedDocumentCandidatesFixture = {
36
42
  documentClaim("document.entity-1.statement.credit.original", "credit", "original:credit", "statement.creditAmount", "superseded"),
37
43
  documentClaim("document.entity-1.statement.amount.corrected", "amount", "corrected:amount", "statement.totalAmount", "proposed"),
38
44
  documentClaim("document.entity-1.statement.credit.corrected", "credit", "corrected:credit", "statement.creditAmount", "proposed"),
39
- ],
40
- derivedClaims: [
41
- {
42
- id: "document.entity-1.statement-position.current",
43
- subjectType: "verified-record.period",
44
- subjectId: "entity-1:2026",
45
- surface: "document-review",
46
- claimType: "derived-field",
47
- fieldOrBehavior: "statementPosition",
48
- value: {
49
- totalAmount: 84000,
50
- creditAmount: 9100,
51
- position: "credit-present",
52
- },
53
- status: "proposed",
54
- impactLevel: "high",
55
- inputClaimIds: [
56
- { claimId: "document.entity-1.statement.amount.corrected", role: "amount-input", supportStrength: "strong" },
57
- { claimId: "document.entity-1.statement.credit.corrected", role: "credit-input", supportStrength: "strong" },
58
- ],
59
- createdAt: generatedAt,
60
- updatedAt: generatedAt,
61
- evidenceSummary: "Derived statement position from corrected source fields.",
62
- sourceRef: "documents://entities/entity-1/periods/2026/resolved-fields/statement",
63
- collectedBy: "survey-document-fixture",
64
- },
65
- {
66
- id: "document.entity-1.statement-position.original",
67
- subjectType: "verified-record.period",
68
- subjectId: "entity-1:2026",
69
- surface: "document-review",
70
- claimType: "derived-field",
71
- fieldOrBehavior: "statementPosition",
72
- value: {
73
- totalAmount: 82000,
74
- creditAmount: 8600,
75
- position: "credit-present",
76
- },
77
- status: "stale",
78
- impactLevel: "high",
79
- inputClaimIds: [
80
- { claimId: "document.entity-1.statement.amount.original", role: "amount-input", supportStrength: "strong" },
81
- { claimId: "document.entity-1.statement.credit.original", role: "credit-input", supportStrength: "strong" },
82
- ],
83
- createdAt: generatedAt,
84
- updatedAt: generatedAt,
85
- evidenceSummary: "Prior statement position derived from original source fields.",
86
- sourceRef: "documents://entities/entity-1/periods/2026/resolved-fields/statement",
87
- collectedBy: "survey-document-fixture",
88
- },
45
+ computationClaim("current", "proposed", [
46
+ "document.entity-1.statement.amount.corrected",
47
+ "document.entity-1.statement.credit.corrected",
48
+ ]),
49
+ computationClaim("original", "stale", [
50
+ "document.entity-1.statement.amount.original",
51
+ "document.entity-1.statement.credit.original",
52
+ ]),
89
53
  ],
90
54
  };
55
+ function statementPosition(totalAmount, creditAmount) {
56
+ return {
57
+ totalAmount,
58
+ creditAmount,
59
+ position: "credit-present",
60
+ };
61
+ }
91
62
  function documentExtraction(id, sourceId, target, value, locator) {
92
63
  return {
93
64
  id: `document:extraction:${id}`,
@@ -126,6 +97,45 @@ function documentCandidateSet(target, originalId, originalValue, correctedId, co
126
97
  ],
127
98
  };
128
99
  }
100
+ function computationSource(id) {
101
+ return {
102
+ id: `document:source:statement-position:${id}`,
103
+ kind: "manual-entry",
104
+ sourceRef: "documents://entities/entity-1/periods/2026/resolved-fields/statement",
105
+ observedAt: generatedAt,
106
+ locatorScheme: "structured-field",
107
+ };
108
+ }
109
+ function computationExtraction(id, totalAmount, creditAmount) {
110
+ return {
111
+ id: `document:extraction:statement-position:${id}`,
112
+ sourceId: `document:source:statement-position:${id}`,
113
+ target: "statementPosition",
114
+ value: statementPosition(totalAmount, creditAmount),
115
+ confidence: 0.96,
116
+ extractor: "statement-position-rule",
117
+ extractedAt: generatedAt,
118
+ excerpt: `${id} statement position from source fields.`,
119
+ };
120
+ }
121
+ function computationCandidateSet(id, totalAmount, creditAmount) {
122
+ return {
123
+ id: `document:candidates:statement-position:${id}`,
124
+ target: "statementPosition",
125
+ selectedCandidateId: `document:candidate:statement-position:${id}`,
126
+ status: "needs-review",
127
+ rationale: "Statement position is computed from statement amount and credit fields.",
128
+ candidates: [
129
+ {
130
+ id: `document:candidate:statement-position:${id}`,
131
+ extractionId: `document:extraction:statement-position:${id}`,
132
+ value: statementPosition(totalAmount, creditAmount),
133
+ confidence: 0.96,
134
+ sourceRank: 1,
135
+ },
136
+ ],
137
+ };
138
+ }
129
139
  function documentClaim(id, target, candidateId, fieldOrBehavior, status) {
130
140
  return {
131
141
  id,
@@ -141,3 +151,29 @@ function documentClaim(id, target, candidateId, fieldOrBehavior, status) {
141
151
  collectedBy: "document-field-parser",
142
152
  };
143
153
  }
154
+ function computationClaim(id, status, inputClaimIds) {
155
+ const roles = ["amount-input", "credit-input"];
156
+ return {
157
+ id: `document.entity-1.statement-position.${id}`,
158
+ candidateSetId: `document:candidates:statement-position:${id}`,
159
+ candidateId: `document:candidate:statement-position:${id}`,
160
+ subjectType: "verified-record.period",
161
+ subjectId: "entity-1:2026",
162
+ surface: "document-review",
163
+ claimType: "computed-field",
164
+ fieldOrBehavior: "statementPosition",
165
+ status,
166
+ impactLevel: "high",
167
+ evidenceType: "calculation_trace",
168
+ evidenceMethod: "validation",
169
+ collectedBy: "survey-document-fixture",
170
+ eventMethod: "rule-application",
171
+ derivedFrom: inputClaimIds,
172
+ derivationEdges: inputClaimIds.map((inputClaimId, index) => ({
173
+ inputClaimId,
174
+ method: "rule-application",
175
+ role: roles[index],
176
+ supportStrength: "strong",
177
+ })),
178
+ };
179
+ }
@@ -1,4 +1,4 @@
1
- import type { CandidateSet, ClaimTarget, DerivedClaimTarget, Extraction, RawSource, ReviewOutcome, SurveyInput } from "./types.js";
1
+ import type { CandidateSet, ClaimTarget, Extraction, RawSource, ReviewOutcome, SurveyInput } from "./types.js";
2
2
  export interface SurveyInputBuilderArgs {
3
3
  source: string;
4
4
  generatedAt?: string;
@@ -37,6 +37,19 @@ export interface SurveyObservationInput {
37
37
  id?: string;
38
38
  };
39
39
  }
40
+ export interface CandidateReviewRecordInput {
41
+ id: string;
42
+ target: string;
43
+ observations: SurveyObservationInput[];
44
+ selectedCandidateId?: string;
45
+ status?: CandidateSet["status"];
46
+ rationale?: string;
47
+ metadata?: Record<string, unknown>;
48
+ reviewOutcome?: Omit<ReviewOutcome, "id" | "candidateSetId"> & {
49
+ id?: string;
50
+ candidateId?: string;
51
+ };
52
+ }
40
53
  export declare class SurveyInputBuilder {
41
54
  private readonly source;
42
55
  private readonly generatedAt;
@@ -45,17 +58,18 @@ export declare class SurveyInputBuilder {
45
58
  private readonly candidateSets;
46
59
  private readonly reviewOutcomes;
47
60
  private readonly claims;
48
- private readonly derivedClaims;
49
61
  constructor(args: SurveyInputBuilderArgs);
50
62
  addRawSource(rawSource: RawSource): this;
51
63
  addExtraction(extraction: Extraction): this;
52
64
  addCandidateSet(candidateSet: CandidateSet): this;
53
65
  addReviewOutcome(reviewOutcome: ReviewOutcome): this;
54
66
  addClaim(claim: ClaimTarget): this;
55
- addDerivedClaim(derivedClaim: DerivedClaimTarget): this;
56
67
  addClaimRecord(record: SurveyClaimRecord): this;
57
68
  addClaimRecords(records: SurveyClaimRecord[]): this;
58
69
  addObservation(observation: SurveyObservationInput): this;
59
70
  addObservations(observations: SurveyObservationInput[]): this;
60
71
  build(): SurveyInput;
72
+ private addRecordCandidateSet;
73
+ private addRecordRawSource;
61
74
  }
75
+ export declare function candidateReviewRecord(input: CandidateReviewRecordInput): SurveyClaimRecord[];
@@ -6,7 +6,6 @@ export class SurveyInputBuilder {
6
6
  candidateSets = new Map();
7
7
  reviewOutcomes = new Map();
8
8
  claims = new Map();
9
- derivedClaims = new Map();
10
9
  constructor(args) {
11
10
  this.source = args.source;
12
11
  this.generatedAt = args.generatedAt ?? new Date().toISOString();
@@ -31,14 +30,10 @@ export class SurveyInputBuilder {
31
30
  addUnique(this.claims, claim, "claim target");
32
31
  return this;
33
32
  }
34
- addDerivedClaim(derivedClaim) {
35
- addUnique(this.derivedClaims, derivedClaim, "derived claim target");
36
- return this;
37
- }
38
33
  addClaimRecord(record) {
39
- this.addRawSource(record.rawSource);
34
+ this.addRecordRawSource(record.rawSource);
40
35
  this.addExtraction(record.extraction);
41
- this.addCandidateSet(record.candidateSet);
36
+ this.addRecordCandidateSet(record.candidateSet);
42
37
  if (record.reviewOutcome)
43
38
  this.addReviewOutcome(record.reviewOutcome);
44
39
  this.addClaim(record.claim);
@@ -66,9 +61,79 @@ export class SurveyInputBuilder {
66
61
  candidateSets: [...this.candidateSets.values()],
67
62
  reviewOutcomes: [...this.reviewOutcomes.values()],
68
63
  claims: [...this.claims.values()],
69
- derivedClaims: [...this.derivedClaims.values()],
70
64
  };
71
65
  }
66
+ addRecordCandidateSet(candidateSet) {
67
+ addIdempotent(this.candidateSets, candidateSet, "candidate set");
68
+ }
69
+ addRecordRawSource(rawSource) {
70
+ addIdempotent(this.rawSources, rawSource, "raw source");
71
+ }
72
+ }
73
+ export function candidateReviewRecord(input) {
74
+ if (input.observations.length === 0) {
75
+ throw new Error(`Candidate review record ${input.id} needs at least one observation`);
76
+ }
77
+ const records = input.observations.map((observation) => observationToClaimRecord(observation));
78
+ const candidateSetId = input.id;
79
+ const selectedCandidateId = input.selectedCandidateId ?? input.reviewOutcome?.candidateId;
80
+ const candidates = records.map((record) => record.candidateSet.candidates[0]);
81
+ assertUniqueCandidateIds(candidates, candidateSetId);
82
+ assertCandidateIdExists(candidates, selectedCandidateId, candidateSetId, "selected");
83
+ if (input.selectedCandidateId && input.reviewOutcome?.candidateId && input.selectedCandidateId !== input.reviewOutcome.candidateId) {
84
+ throw new Error(`Candidate review record ${candidateSetId} has conflicting selected and review candidate ids`);
85
+ }
86
+ if (input.reviewOutcome && !selectedCandidateId) {
87
+ throw new Error(`Candidate review record ${candidateSetId} needs a selected candidate id for review outcome`);
88
+ }
89
+ const candidateSet = {
90
+ id: candidateSetId,
91
+ target: input.target,
92
+ candidates,
93
+ selectedCandidateId,
94
+ status: input.status ?? candidateSetStatusFor(input.reviewOutcome?.status),
95
+ rationale: input.rationale,
96
+ metadata: input.metadata,
97
+ };
98
+ const reviewCandidateId = input.reviewOutcome?.candidateId ?? selectedCandidateId;
99
+ const reviewOutcome = input.reviewOutcome
100
+ ? {
101
+ id: input.reviewOutcome.id ?? `${candidateSetId}.review`,
102
+ candidateSetId,
103
+ ...input.reviewOutcome,
104
+ candidateId: reviewCandidateId,
105
+ }
106
+ : undefined;
107
+ assertCandidateIdExists(candidates, reviewCandidateId, candidateSetId, "review");
108
+ return records.map((record) => {
109
+ const candidate = record.candidateSet.candidates[0];
110
+ return {
111
+ ...record,
112
+ candidateSet,
113
+ reviewOutcome: candidate.id === reviewCandidateId ? reviewOutcome : undefined,
114
+ claim: {
115
+ ...record.claim,
116
+ candidateSetId,
117
+ candidateId: candidate.id,
118
+ },
119
+ };
120
+ });
121
+ }
122
+ function assertUniqueCandidateIds(candidates, candidateSetId) {
123
+ const seen = new Set();
124
+ for (const candidate of candidates) {
125
+ if (seen.has(candidate.id)) {
126
+ throw new Error(`Candidate review record ${candidateSetId} has duplicate candidate id: ${candidate.id}`);
127
+ }
128
+ seen.add(candidate.id);
129
+ }
130
+ }
131
+ function assertCandidateIdExists(candidates, candidateId, candidateSetId, role) {
132
+ if (!candidateId)
133
+ return;
134
+ if (!candidates.some((candidate) => candidate.id === candidateId)) {
135
+ throw new Error(`Candidate review record ${candidateSetId} does not contain ${role} candidate ${candidateId}`);
136
+ }
72
137
  }
73
138
  function observationToClaimRecord(observation) {
74
139
  const ids = observationIds(observation.id, observation);
@@ -138,3 +203,25 @@ function addUnique(map, item, label) {
138
203
  throw new Error(`Duplicate ${label} id: ${item.id}`);
139
204
  map.set(item.id, item);
140
205
  }
206
+ function addIdempotent(map, item, label) {
207
+ const existing = map.get(item.id);
208
+ if (existing) {
209
+ if (stableStringify(existing) !== stableStringify(item)) {
210
+ throw new Error(`Conflicting ${label} id: ${item.id}`);
211
+ }
212
+ return;
213
+ }
214
+ map.set(item.id, item);
215
+ }
216
+ function stableStringify(value) {
217
+ return JSON.stringify(sortValue(value));
218
+ }
219
+ function sortValue(value) {
220
+ if (Array.isArray(value))
221
+ return value.map(sortValue);
222
+ if (!value || typeof value !== "object")
223
+ return value;
224
+ return Object.fromEntries(Object.entries(value)
225
+ .sort(([left], [right]) => left.localeCompare(right))
226
+ .map(([key, item]) => [key, sortValue(item)]));
227
+ }
@@ -1,6 +1,6 @@
1
- export type { CandidateSetStatus, Candidate, CandidateSet, ClaimTarget, DerivedClaimTarget, Extraction, LocatorScheme, RawSource, RawSourceKind, ReviewOutcome, ReviewStatus, SurveyInput, } from "./types.js";
2
- export { SurveyInputBuilder } from "./builder.js";
3
- export type { SurveyClaimRecord, SurveyInputBuilderArgs, SurveyObservationInput } from "./builder.js";
1
+ export type { CandidateSetStatus, Candidate, CandidateSet, ClaimTarget, Extraction, LocatorScheme, RawSource, RawSourceKind, ReviewOutcome, ReviewStatus, SurveyInput, } from "./types.js";
2
+ export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
3
+ export type { CandidateReviewRecordInput, SurveyClaimRecord, SurveyInputBuilderArgs, SurveyObservationInput, } from "./builder.js";
4
4
  export { buildSurveyTrustInput } from "./to-surface.js";
5
5
  export { fieldObservation } from "./field-observation.js";
6
6
  export type { FieldObservationInput } from "./field-observation.js";
package/dist/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { SurveyInputBuilder } from "./builder.js";
1
+ export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
2
2
  export { buildSurveyTrustInput } from "./to-surface.js";
3
3
  export { fieldObservation } from "./field-observation.js";
4
4
  export { repeatedObservation } from "./repeated-observation.js";
@@ -30,6 +30,8 @@ export function buildSurveyTrustInput(input) {
30
30
  createdAt,
31
31
  updatedAt,
32
32
  impactLevel: projection.impactLevel,
33
+ derivedFrom: projection.derivedFrom,
34
+ derivationEdges: projection.derivationEdges,
33
35
  confidenceBasis: {
34
36
  sourceQuality: "moderate",
35
37
  extractionConfidence: candidate.confidence ?? extraction.confidence,
@@ -82,9 +84,6 @@ export function buildSurveyTrustInput(input) {
82
84
  notes: review?.rationale ?? candidateSet.rationale,
83
85
  });
84
86
  }
85
- for (const derived of input.derivedClaims ?? []) {
86
- addDerivedClaim({ derived, claims, evidence, events });
87
- }
88
87
  return {
89
88
  schemaVersion: 3,
90
89
  source: input.source,
@@ -94,50 +93,6 @@ export function buildSurveyTrustInput(input) {
94
93
  events,
95
94
  };
96
95
  }
97
- function addDerivedClaim(input) {
98
- const { derived } = input;
99
- input.claims.push({
100
- id: derived.id,
101
- subjectType: derived.subjectType,
102
- subjectId: derived.subjectId,
103
- surface: derived.surface,
104
- claimType: derived.claimType,
105
- fieldOrBehavior: derived.fieldOrBehavior,
106
- value: derived.value,
107
- status: derived.status,
108
- createdAt: derived.createdAt,
109
- updatedAt: derived.updatedAt,
110
- impactLevel: derived.impactLevel,
111
- derivationEdges: derived.inputClaimIds.map((edge) => ({
112
- inputClaimId: edge.claimId,
113
- method: "rule-application",
114
- role: edge.role,
115
- supportStrength: edge.supportStrength,
116
- })),
117
- metadata: derived.metadata,
118
- });
119
- const evidenceId = `${derived.id}.evidence.calculation`;
120
- input.evidence.push({
121
- id: evidenceId,
122
- claimId: derived.id,
123
- evidenceType: "calculation_trace",
124
- method: "validation",
125
- sourceRef: derived.sourceRef,
126
- excerptOrSummary: derived.evidenceSummary,
127
- observedAt: derived.updatedAt,
128
- collectedBy: derived.collectedBy,
129
- });
130
- input.events.push({
131
- id: `${derived.id}.event.${derived.status}`,
132
- claimId: derived.id,
133
- status: derived.status,
134
- actor: derived.collectedBy,
135
- method: "rule-application",
136
- evidenceIds: [evidenceId],
137
- createdAt: derived.updatedAt,
138
- verifiedAt: derived.status === "verified" ? derived.updatedAt : undefined,
139
- });
140
- }
141
96
  function statusFor(input) {
142
97
  if (input.review)
143
98
  return input.review.status;
@@ -1,4 +1,4 @@
1
- import type { ConfidenceBasis, EvidenceMethod, EvidenceType, ImpactLevel, TrustStatus } from "@kontourai/surface";
1
+ import type { ConfidenceBasis, DerivationEdge, EvidenceMethod, EvidenceType, ImpactLevel, TrustStatus } from "@kontourai/surface";
2
2
  export type RawSourceKind = "uploaded-document" | "web-page" | "api-record" | "manual-entry";
3
3
  export type LocatorScheme = "pdf" | "text" | "html" | "structured-field";
4
4
  export interface RawSource {
@@ -70,33 +70,13 @@ export interface ClaimTarget {
70
70
  evidenceType?: EvidenceType;
71
71
  evidenceMethod?: EvidenceMethod;
72
72
  confidenceBasis?: ConfidenceBasis;
73
+ derivedFrom?: string[];
74
+ derivationEdges?: DerivationEdge[];
73
75
  collectedBy: string;
74
76
  actor?: string;
75
77
  eventMethod?: string;
76
78
  metadata?: Record<string, unknown>;
77
79
  }
78
- export interface DerivedClaimTarget {
79
- id: string;
80
- subjectType: string;
81
- subjectId: string;
82
- surface: string;
83
- claimType: string;
84
- fieldOrBehavior: string;
85
- value: unknown;
86
- status: TrustStatus;
87
- impactLevel: ImpactLevel;
88
- inputClaimIds: Array<{
89
- claimId: string;
90
- role?: string;
91
- supportStrength?: "weak" | "moderate" | "strong";
92
- }>;
93
- createdAt: string;
94
- updatedAt: string;
95
- evidenceSummary: string;
96
- sourceRef: string;
97
- collectedBy: string;
98
- metadata?: Record<string, unknown>;
99
- }
100
80
  export interface SurveyInput {
101
81
  source: string;
102
82
  generatedAt: string;
@@ -105,5 +85,4 @@ export interface SurveyInput {
105
85
  candidateSets: CandidateSet[];
106
86
  reviewOutcomes: ReviewOutcome[];
107
87
  claims: ClaimTarget[];
108
- derivedClaims?: DerivedClaimTarget[];
109
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontourai/survey",
3
- "version": "0.1.4",
3
+ "version": "0.2.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",