@kontourai/survey 0.4.23 → 0.4.24
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 +82 -868
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/to-flow-artifact.d.ts +41 -0
- package/dist/src/to-flow-artifact.js +31 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,24 +1,45 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# Kontour Survey
|
|
2
4
|
|
|
3
|
-
Survey
|
|
4
|
-
|
|
5
|
+
**The producer side of trust. Survey carries evidence from raw source to reviewed claim — without ever pretending to know what's true.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@kontourai/survey)
|
|
8
|
+
[](https://github.com/kontourai/survey/actions/workflows/ci.yml)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
[Documentation](https://kontourai.github.io/survey/) · [Record Contracts](docs/record-contracts.md) · [Consumer Guide](docs/consumer-integration-guide.md) · [kontourai.io/survey](https://kontourai.io/survey)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Every "verified" value in a data product has a story: where it was observed, what was extracted, what the alternatives were, who reviewed it, and what they decided. Most systems throw that story away the moment a human clicks approve — leaving a bare value nobody can re-inspect.
|
|
18
|
+
|
|
19
|
+
Survey is the contract that keeps the story. Producers own acquisition, parsing, ranking, review UX, and vertical policy; Survey owns the portable record shapes for the **source → extraction → candidate → review → claim** chain, and projects them into [Surface](https://kontourai.io/surface) `TrustInput` — so downstream trust reports, consoles, and [Flow](https://kontourai.github.io/flow/) gates can see not just the value, but the evidence and the review posture behind it.
|
|
20
|
+
|
|
21
|
+
Survey does not decide whether a real-world value is true. It preserves the producer's evidence and review discipline so something downstream can.
|
|
5
22
|
|
|
6
|
-
|
|
7
|
-
ingestion platform:
|
|
23
|
+
## What you get
|
|
8
24
|
|
|
9
|
-
-
|
|
10
|
-
- Survey owns
|
|
11
|
-
- `
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
TrustInput, trust reporting, and public reporting surfaces.
|
|
25
|
+
- **Typed record contracts** for raw sources, extractions, candidates, review outcomes, source-of-authority posture, repeated observations, resolutions, review proofs, and adversarial passes — every link in the evidence chain inspectable after the fact.
|
|
26
|
+
- **One projection to Surface.** `buildSurveyTrustInput` turns Survey records into Surface `TrustInput`; Surface owns claims, evidence, status, and trust reporting from there.
|
|
27
|
+
- **An embeddable Review Workbench** — a framework-neutral UI for working a `ReviewItem` queue: current vs proposed values, source refs and excerpts, decision controls, and a live Surface preview.
|
|
28
|
+
- **A server-owned apply boundary.** Review decisions are derived from pre-decision snapshots plus persisted events — never from browser-computed payloads — with freshness and replay checks built in.
|
|
29
|
+
- **Adversarial-pass records** for high-stakes review loops, designed to serve as per-round evidence for [Flow's adversarial route-back pattern](https://kontourai.github.io/flow/gates-and-route-back.html#pattern-adversarial-review-with-a-defect-budget).
|
|
15
30
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
## See it
|
|
32
|
+
|
|
33
|
+
The Review Workbench rendering a real fixture queue — current vs proposed values, source evidence, decision effect, and the Surface projection preview:
|
|
34
|
+
|
|
35
|
+

|
|
19
36
|
|
|
20
37
|
## Quickstart
|
|
21
38
|
|
|
39
|
+
```sh
|
|
40
|
+
npm install @kontourai/survey @kontourai/surface
|
|
41
|
+
```
|
|
42
|
+
|
|
22
43
|
```ts
|
|
23
44
|
import { buildTrustReport, validateTrustInput } from "@kontourai/surface";
|
|
24
45
|
import { buildSurveyTrustInput, SurveyInputBuilder } from "@kontourai/survey";
|
|
@@ -64,10 +85,20 @@ const trustInput = validateTrustInput(buildSurveyTrustInput(surveyInput));
|
|
|
64
85
|
const report = buildTrustReport(trustInput);
|
|
65
86
|
```
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
One observation, one chain: the page it came from, what the extractor read, who verified it, and the claim it supports — projected into a Surface trust report with the evidence attached.
|
|
89
|
+
|
|
90
|
+
## The producer validation path
|
|
68
91
|
|
|
69
|
-
Survey
|
|
70
|
-
|
|
92
|
+
1. Build Survey observations with source, extraction, candidate, review, and claim records.
|
|
93
|
+
2. Call `buildSurveyTrustInput` to project the Survey records into Surface `TrustInput`.
|
|
94
|
+
3. Call Surface `validateTrustInput` on the projected input.
|
|
95
|
+
4. Optionally call public Surface report APIs such as `buildTrustReport` to inspect claims, evidence, status, gaps, and metadata.
|
|
96
|
+
|
|
97
|
+
Keep producer operational state outside Survey. Queue status, reviewer form state, retries, source caches, and product policy decisions belong in the producer's own data model. Survey carries only the portable source, extraction, candidate, review, and claim projection records needed by Surface.
|
|
98
|
+
|
|
99
|
+
## Review Workbench embed
|
|
100
|
+
|
|
101
|
+
For downstream products that already produce `ReviewItem` queues:
|
|
71
102
|
|
|
72
103
|
```ts
|
|
73
104
|
import {
|
|
@@ -86,880 +117,63 @@ const presentationAdapter = {
|
|
|
86
117
|
mountReviewWorkbench(element, reviewQueueSession, { presentationAdapter });
|
|
87
118
|
```
|
|
88
119
|
|
|
89
|
-
The
|
|
90
|
-
Console Kit tokens it needs, so importing it should not rewrite the host
|
|
91
|
-
application's `body` or `:root` styles. Hosts should mount into an element like:
|
|
120
|
+
The stylesheet is scoped to `.survey-workbench-embed` and bundles the Console Kit tokens it needs, so it will not rewrite the host application's `body` or `:root` styles. Mount into:
|
|
92
121
|
|
|
93
122
|
```html
|
|
94
123
|
<div class="survey-workbench-embed theme-survey"></div>
|
|
95
124
|
```
|
|
96
125
|
|
|
97
|
-
|
|
98
|
-
the standalone demo page. Use that only when Survey owns the whole page.
|
|
99
|
-
|
|
100
|
-
For the full consumer path from `ReviewItem` construction through persisted
|
|
101
|
-
review events, exported results, and optional Surface projection, see
|
|
102
|
-
[`docs/consumer-integration-guide.md`](docs/consumer-integration-guide.md).
|
|
103
|
-
That guide also covers the server-side apply boundary: producers should derive
|
|
104
|
-
write results from pre-decision review snapshots plus persisted events, not from
|
|
105
|
-
browser-computed decisions or exported result payloads.
|
|
106
|
-
Use `persistReviewSessionEvents` when server code needs to save review events,
|
|
107
|
-
then pass the persisted event set to `deriveReviewSessionApplyResultForSnapshot`
|
|
108
|
-
before applying product policy. Survey derives selected review results and
|
|
109
|
-
structured replay/completion issues; the producer still owns current-record
|
|
110
|
-
validation and writes.
|
|
111
|
-
For browser-backed queues, server code can import
|
|
112
|
-
`@kontourai/survey/review-workbench/server-review-session` and use
|
|
113
|
-
`createServerReviewSessionRecord`, `hashReviewSessionSnapshot`,
|
|
114
|
-
`assertServerReviewSessionFreshness`, and `assertServerReviewSessionEvents` to
|
|
115
|
-
keep the review snapshot server-owned while accepting browser-submitted
|
|
116
|
-
`ReviewSessionEvent` resources. `deriveServerReviewSessionApplyResult` composes
|
|
117
|
-
those checks with Survey's apply-result derivation for server-side write paths.
|
|
118
|
-
For generic, test-covered consumer examples, see
|
|
119
|
-
[`examples/review-workbench/facility-credential-consumer.ts`](examples/review-workbench/facility-credential-consumer.ts)
|
|
120
|
-
for presentation and event persistence, and
|
|
121
|
-
[`examples/review-workbench/server-apply-consumer.ts`](examples/review-workbench/server-apply-consumer.ts)
|
|
122
|
-
for a compact server-side apply boundary.
|
|
123
|
-
For the current decision on why Survey is not adding a generic review adapter
|
|
124
|
-
builder yet, see
|
|
125
|
-
[`docs/consumer-adapter-abstraction-assessment.md`](docs/consumer-adapter-abstraction-assessment.md).
|
|
126
|
-
|
|
127
|
-
## Contributor checks
|
|
128
|
-
|
|
129
|
-
Install the repo-owned Git hooks once per clone:
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
npm run setup:repo-hooks
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
The setup command is idempotent. It sets this repo's local `core.hooksPath` to
|
|
136
|
-
`.githooks` and does not require global Git configuration.
|
|
137
|
-
|
|
138
|
-
Use the same checks directly when you want to validate hook drift or package
|
|
139
|
-
health before pushing:
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
npm run validate:repo-hooks
|
|
143
|
-
npm run verify
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
The committed pre-push hook runs both commands from the repo root.
|
|
147
|
-
|
|
148
|
-
## Producer validation path
|
|
149
|
-
|
|
150
|
-
Survey producers validate through public `@kontourai/survey` and
|
|
151
|
-
`@kontourai/surface` contracts:
|
|
152
|
-
|
|
153
|
-
1. Build Survey observations with source, extraction, candidate, review, and
|
|
154
|
-
claim records.
|
|
155
|
-
2. Call `buildSurveyTrustInput` to project the Survey records into Surface
|
|
156
|
-
`TrustInput`.
|
|
157
|
-
3. Call Surface `validateTrustInput` on the projected input.
|
|
158
|
-
4. Optionally call public Surface report APIs such as `buildTrustReport` to
|
|
159
|
-
inspect claims, evidence, status, gaps, and metadata.
|
|
160
|
-
|
|
161
|
-
Keep producer operational state outside Survey. Queue status, reviewer form
|
|
162
|
-
state, retries, source caches, and product policy decisions belong in the
|
|
163
|
-
producer's own data model. Survey carries only the portable source,
|
|
164
|
-
extraction, candidate, review, and claim projection records needed by Surface.
|
|
165
|
-
|
|
166
|
-
## Raw sources
|
|
167
|
-
|
|
168
|
-
Use raw-source helpers when a producer wants Survey to shape source identity
|
|
169
|
-
before building observations. The helpers do not fetch, crawl, parse, or judge
|
|
170
|
-
the source; they only produce stable `RawSource` records with explicit source
|
|
171
|
-
references, observed times, locator schemes, checksums, and producer metadata.
|
|
172
|
-
|
|
173
|
-
```ts
|
|
174
|
-
import {
|
|
175
|
-
apiRecordSource,
|
|
176
|
-
fieldObservation,
|
|
177
|
-
SurveyInputBuilder,
|
|
178
|
-
} from "@kontourai/survey";
|
|
179
|
-
|
|
180
|
-
const rawSource = apiRecordSource({
|
|
181
|
-
sourceRef: "example-records://entity/entity-123",
|
|
182
|
-
observedAt: new Date().toISOString(),
|
|
183
|
-
checksum: "abc123",
|
|
184
|
-
metadata: {
|
|
185
|
-
provider: "example-records",
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const surveyInput = new SurveyInputBuilder({ source: "example-producer:run-1" })
|
|
190
|
-
.addObservation(fieldObservation({
|
|
191
|
-
id: "entity-123.status.current",
|
|
192
|
-
field: "registrationStatus",
|
|
193
|
-
value: "ACTIVE",
|
|
194
|
-
rawSource,
|
|
195
|
-
extraction: {
|
|
196
|
-
confidence: 0.97,
|
|
197
|
-
locator: "json:$.registrationStatus",
|
|
198
|
-
extractor: "example-extractor",
|
|
199
|
-
extractedAt: new Date().toISOString(),
|
|
200
|
-
},
|
|
201
|
-
claim: {
|
|
202
|
-
subjectType: "public-record.entity",
|
|
203
|
-
subjectId: "entity-123",
|
|
204
|
-
surface: "example.profile",
|
|
205
|
-
claimType: "public-data.field",
|
|
206
|
-
status: "proposed",
|
|
207
|
-
impactLevel: "medium",
|
|
208
|
-
collectedBy: "example-extractor",
|
|
209
|
-
},
|
|
210
|
-
}))
|
|
211
|
-
.build();
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
Survey exports `uploadedDocumentSource`, `apiRecordSource`, `webPageSource`,
|
|
215
|
-
`manualEntrySource`, and `policyStandardSource`. Producer-provided `id` values
|
|
216
|
-
are preserved; otherwise Survey derives a stable id from source kind and
|
|
217
|
-
`sourceRef`. Bare checksum values are normalized to `sha256:<value>`, while
|
|
218
|
-
already-prefixed checksum values are preserved. Producer metadata is copied
|
|
219
|
-
through to Surface evidence.
|
|
220
|
-
|
|
221
|
-
Use `policyStandardSource` when the observed material is the applied standard
|
|
222
|
-
itself. It records `inlineText`, `standardVersion`, and optional `paragraphRef`
|
|
223
|
-
on the `RawSource` and projects to Surface `policy_rule` evidence by default.
|
|
224
|
-
Survey only preserves the producer-applied standard text/version; it does not
|
|
225
|
-
decide whether that standard is correct for the producer's domain.
|
|
226
|
-
|
|
227
|
-
## Interpretation records
|
|
228
|
-
|
|
229
|
-
Use `addInterpretation` when a producer records how an actor read a
|
|
230
|
-
`policy-standard` paragraph for one claim. Interpretations are flat provenance
|
|
231
|
-
records with an `appliesTo` edge to a claim and an `anchorsTo` edge to a
|
|
232
|
-
policy-standard raw source; they are not nested claim derivations or rejection
|
|
233
|
-
reasons.
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
import {
|
|
237
|
-
apiRecordSource,
|
|
238
|
-
buildSurveyTrustInput,
|
|
239
|
-
fieldObservation,
|
|
240
|
-
policyStandardSource,
|
|
241
|
-
SurveyInputBuilder,
|
|
242
|
-
} from "@kontourai/survey";
|
|
243
|
-
|
|
244
|
-
const observedAt = new Date().toISOString();
|
|
245
|
-
const standard = policyStandardSource({
|
|
246
|
-
id: "source.example.policy-standard.rule-1",
|
|
247
|
-
sourceRef: "policy-standard://example/rules/2026#rule-1",
|
|
248
|
-
observedAt,
|
|
249
|
-
inlineText: "A producer reading must cite the applied rule paragraph.",
|
|
250
|
-
standardVersion: "2026.1",
|
|
251
|
-
paragraphRef: "rule-1",
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
const surveyInput = new SurveyInputBuilder({ source: "example-producer:run-1" })
|
|
255
|
-
.addRawSource(standard)
|
|
256
|
-
.addObservation(fieldObservation({
|
|
257
|
-
id: "observation.example.policy-application",
|
|
258
|
-
field: "policyApplication.status",
|
|
259
|
-
value: "DOCUMENTED",
|
|
260
|
-
rawSource: apiRecordSource({
|
|
261
|
-
id: "source.example.application-record",
|
|
262
|
-
sourceRef: "example-records://application/application-1",
|
|
263
|
-
observedAt,
|
|
264
|
-
checksum: "application-1",
|
|
265
|
-
}),
|
|
266
|
-
extraction: {
|
|
267
|
-
target: "policyApplication.status",
|
|
268
|
-
locator: "json:$.policyApplication.status",
|
|
269
|
-
extractor: "example-extractor",
|
|
270
|
-
extractedAt: observedAt,
|
|
271
|
-
},
|
|
272
|
-
claim: {
|
|
273
|
-
id: "claim.example.policy-application",
|
|
274
|
-
subjectType: "example.application",
|
|
275
|
-
subjectId: "application-1",
|
|
276
|
-
surface: "example.review",
|
|
277
|
-
claimType: "policy-application.status",
|
|
278
|
-
impactLevel: "medium",
|
|
279
|
-
collectedBy: "example-extractor",
|
|
280
|
-
},
|
|
281
|
-
}))
|
|
282
|
-
.addInterpretation({
|
|
283
|
-
id: "interpretation.example.rule-1",
|
|
284
|
-
appliesToClaimId: "claim.example.policy-application",
|
|
285
|
-
anchorsToSourceId: standard.id,
|
|
286
|
-
ruleLocator: "text:paragraph=rule-1",
|
|
287
|
-
reading: "The producer read rule 1 as applying to the claim.",
|
|
288
|
-
actor: "producer-operator",
|
|
289
|
-
recordedAt: observedAt,
|
|
290
|
-
})
|
|
291
|
-
.build();
|
|
292
|
-
|
|
293
|
-
const trustInput = buildSurveyTrustInput(surveyInput);
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
Projection emits a normal Surface verification event with
|
|
297
|
-
`method: "survey-interpretation"`, the existing `claimId`, and anchor
|
|
298
|
-
`evidenceIds`. Because current Surface verification events reject unsupported
|
|
299
|
-
keys, typed edge details are preserved on the projected claim at
|
|
300
|
-
`metadata.survey.interpretations[]`. The anchor evidence uses
|
|
301
|
-
`evidenceType: "policy_rule"`, `method: "anchoring"`, the interpretation
|
|
302
|
-
`ruleLocator` as `sourceLocator`, and the policy-standard text/version metadata.
|
|
303
|
-
|
|
304
|
-
## Review resources
|
|
305
|
-
|
|
306
|
-
Survey also exports producer-neutral `ReviewItem`, `ReviewCandidate`, and
|
|
307
|
-
`ReviewDecision` TypeScript resource shapes for review UI and adapter fixtures.
|
|
308
|
-
They use `apiVersion`, `kind`, `metadata`, `spec`, and `status` fields while
|
|
309
|
-
mapping back to the existing Survey record layer. See
|
|
310
|
-
[`docs/review-resource-contract.md`](docs/review-resource-contract.md) for
|
|
311
|
-
field ownership, mapping hints, and the `ReviewSession` non-goal.
|
|
312
|
-
|
|
313
|
-
## Field observations
|
|
314
|
-
|
|
315
|
-
Use `fieldObservation` when a producer wants to describe one scalar field value
|
|
316
|
-
without hand-assembling the repeated source, extraction, candidate, review, and
|
|
317
|
-
claim defaults. The helper returns a normal `SurveyObservationInput`, so it
|
|
318
|
-
works with `SurveyInputBuilder.addObservation` and the same Surface projection
|
|
319
|
-
path.
|
|
320
|
-
|
|
321
|
-
```ts
|
|
322
|
-
import {
|
|
323
|
-
buildSurveyTrustInput,
|
|
324
|
-
fieldObservation,
|
|
325
|
-
SurveyInputBuilder,
|
|
326
|
-
} from "@kontourai/survey";
|
|
327
|
-
|
|
328
|
-
const surveyInput = new SurveyInputBuilder({
|
|
329
|
-
source: "example-producer:run-1",
|
|
330
|
-
})
|
|
331
|
-
.addObservation(fieldObservation({
|
|
332
|
-
id: "entity-123.status.current",
|
|
333
|
-
field: "registrationStatus",
|
|
334
|
-
value: "ACTIVE",
|
|
335
|
-
rawSource: {
|
|
336
|
-
kind: "api-record",
|
|
337
|
-
sourceRef: "example-records://entity/entity-123",
|
|
338
|
-
observedAt: new Date().toISOString(),
|
|
339
|
-
locatorScheme: "structured-field",
|
|
340
|
-
},
|
|
341
|
-
extraction: {
|
|
342
|
-
confidence: 0.97,
|
|
343
|
-
locator: "json:$.registrationStatus",
|
|
344
|
-
extractor: "example-extractor",
|
|
345
|
-
extractedAt: new Date().toISOString(),
|
|
346
|
-
},
|
|
347
|
-
reviewOutcome: {
|
|
348
|
-
status: "verified",
|
|
349
|
-
actor: "records-operator",
|
|
350
|
-
reviewedAt: new Date().toISOString(),
|
|
351
|
-
},
|
|
352
|
-
claim: {
|
|
353
|
-
subjectType: "public-record.entity",
|
|
354
|
-
subjectId: "entity-123",
|
|
355
|
-
surface: "example.profile",
|
|
356
|
-
claimType: "public-data.field",
|
|
357
|
-
status: "verified",
|
|
358
|
-
impactLevel: "medium",
|
|
359
|
-
collectedBy: "example-extractor",
|
|
360
|
-
},
|
|
361
|
-
metadata: {
|
|
362
|
-
producerField: "registration_status",
|
|
363
|
-
},
|
|
364
|
-
}))
|
|
365
|
-
.build();
|
|
366
|
-
|
|
367
|
-
const trustInput = buildSurveyTrustInput(surveyInput);
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
`fieldObservation` sets `extraction.target` and `claim.fieldOrBehavior` from
|
|
371
|
-
`field` when omitted, uses the scalar as both the extraction and claim value,
|
|
372
|
-
and adds neutral helper metadata at
|
|
373
|
-
`metadata.survey.field = { representation: "scalar" }`. Producer metadata is
|
|
374
|
-
preserved. Producers still own scalar semantics, validation, candidate ranking,
|
|
375
|
-
review policy, and whether a value should be verified, proposed, rejected, or
|
|
376
|
-
assumed.
|
|
377
|
-
|
|
378
|
-
## Source-of-authority observations
|
|
379
|
-
|
|
380
|
-
Use `sourceOfAuthorityObservationBuilder` when a producer treats the raw source
|
|
381
|
-
as authoritative for the extracted target: an official publication,
|
|
382
|
-
registration platform page, policy document, contract record, or
|
|
383
|
-
system-of-record response. The builder does not decide whether the source is
|
|
384
|
-
truly authoritative. It guides producers through the source, extraction,
|
|
385
|
-
source-authority posture, review outcome, and claim fields that make the
|
|
386
|
-
declared source posture auditable.
|
|
387
|
-
|
|
388
|
-
Verified or assumed source-of-authority observations require:
|
|
389
|
-
|
|
390
|
-
- source reference
|
|
391
|
-
- source locator
|
|
392
|
-
- source-authority class
|
|
393
|
-
- source-authority scope
|
|
394
|
-
- review actor
|
|
395
|
-
- reviewed time
|
|
396
|
-
|
|
397
|
-
Source-authority metadata projects through Surface Evidence metadata under
|
|
398
|
-
`sourceAuthority`. It does not project to Surface `authorityTrace`, which is
|
|
399
|
-
reserved for actor, credential, role, organization, policy, or system authority.
|
|
400
|
-
|
|
401
|
-
```ts
|
|
402
|
-
import {
|
|
403
|
-
buildSurveyTrustInput,
|
|
404
|
-
sourceOfAuthorityObservationBuilder,
|
|
405
|
-
SurveyInputBuilder,
|
|
406
|
-
uploadedDocumentSource,
|
|
407
|
-
} from "@kontourai/survey";
|
|
408
|
-
|
|
409
|
-
const observedAt = new Date().toISOString();
|
|
410
|
-
const rawSource = uploadedDocumentSource({
|
|
411
|
-
sourceRef: "https://rules.example.test/thresholds.pdf",
|
|
412
|
-
observedAt,
|
|
413
|
-
checksum: "abc123",
|
|
414
|
-
locatorScheme: "pdf",
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
const surveyInput = new SurveyInputBuilder({
|
|
418
|
-
source: "rule-producer:run-1",
|
|
419
|
-
})
|
|
420
|
-
.addObservation(sourceOfAuthorityObservationBuilder({
|
|
421
|
-
id: "rule.threshold.primary.2026",
|
|
422
|
-
field: "regulatedRule.threshold.primary.2026",
|
|
423
|
-
value: 1200,
|
|
424
|
-
})
|
|
425
|
-
.withSourceAuthority({
|
|
426
|
-
authorityClass: "official_publication",
|
|
427
|
-
scope: {
|
|
428
|
-
jurisdiction: "example",
|
|
429
|
-
productArea: "regulated-rule",
|
|
430
|
-
effectiveYear: 2026,
|
|
431
|
-
},
|
|
432
|
-
sourceVersion: "2026",
|
|
433
|
-
declaredBy: "rule-producer",
|
|
434
|
-
})
|
|
435
|
-
.fromSource(rawSource)
|
|
436
|
-
.withExtraction({
|
|
437
|
-
confidence: 0.94,
|
|
438
|
-
locator: "pdf:page=12;table=thresholds;row=primary",
|
|
439
|
-
extractor: "rule-producer",
|
|
440
|
-
extractedAt: observedAt,
|
|
441
|
-
})
|
|
442
|
-
.withReviewOutcome({
|
|
443
|
-
status: "verified",
|
|
444
|
-
actor: "rule-reviewer",
|
|
445
|
-
reviewedAt: new Date().toISOString(),
|
|
446
|
-
})
|
|
447
|
-
.forClaim({
|
|
448
|
-
subjectType: "regulated-rule",
|
|
449
|
-
subjectId: "example:threshold:primary:2026",
|
|
450
|
-
surface: "regulated.rules",
|
|
451
|
-
claimType: "regulated.rule-value",
|
|
452
|
-
status: "verified",
|
|
453
|
-
impactLevel: "high",
|
|
454
|
-
evidenceType: "policy_rule",
|
|
455
|
-
evidenceMethod: "extraction",
|
|
456
|
-
collectedBy: "rule-producer",
|
|
457
|
-
})
|
|
458
|
-
.build())
|
|
459
|
-
.build();
|
|
460
|
-
|
|
461
|
-
const trustInput = buildSurveyTrustInput(surveyInput);
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
`sourceOfAuthorityObservation` remains available as the lower-level object
|
|
465
|
-
factory when a producer already has the full observation input assembled.
|
|
466
|
-
|
|
467
|
-
Contextual claims such as "this submission is compliant" or "this record is
|
|
468
|
-
eligible for a specific requester" are not source-of-authority observations.
|
|
469
|
-
They are Surface claims with Claim Dependencies on source-of-authority claims
|
|
470
|
-
and other producer facts. The producer owns that domain logic.
|
|
471
|
-
|
|
472
|
-
For the reusable producer workflow, including manual confirmation state,
|
|
473
|
-
source references, Survey review outcomes, and Surface report boundaries, see
|
|
474
|
-
[Source-Authority Review Pattern](docs/source-authority-review-pattern.md).
|
|
475
|
-
|
|
476
|
-
## Reviewed candidate resolutions
|
|
477
|
-
|
|
478
|
-
Use `reviewedCandidateResolution` when a producer has multiple candidate
|
|
479
|
-
observations for the same target and a review outcome selects one candidate.
|
|
480
|
-
The helper wraps `candidateReviewRecord`, attaches the review outcome to the
|
|
481
|
-
selected candidate, defaults the candidate set to `resolved`, defaults the
|
|
482
|
-
selected claim status from the review outcome, and defaults unselected
|
|
483
|
-
candidates to `superseded`. Producers can override selected or unselected claim
|
|
484
|
-
statuses when their domain workflow needs a different posture.
|
|
485
|
-
|
|
486
|
-
This is useful for corrected documents, source-of-truth choices, and review
|
|
487
|
-
queues where losing candidates should remain visible for transparency rather
|
|
488
|
-
than disappearing from the trust trail.
|
|
489
|
-
|
|
490
|
-
Candidates may include an optional `rejectionReason` when a producer wants to
|
|
491
|
-
record why a non-selected alternative was superseded or rejected. Survey
|
|
492
|
-
preserves that producer-provided rationale on the candidate and projects it to
|
|
493
|
-
Surface claim `metadata.survey.candidate.rejectionReason` for that candidate
|
|
494
|
-
while preserving producer-provided `metadata.survey` keys. Survey does not rank
|
|
495
|
-
candidates, choose winners, or define rejection policy.
|
|
496
|
-
|
|
497
|
-
## Reviewed current/proposed resolutions
|
|
498
|
-
|
|
499
|
-
Use `reviewedCurrentProposedResolution` when a producer has exactly two
|
|
500
|
-
candidate roles for the same target: the current value the producer would keep
|
|
501
|
-
absent a change, and a proposed value introduced by new source material,
|
|
502
|
-
extraction, or review work. The helper consumes full observations, selects
|
|
503
|
-
either the current or proposed candidate through `selectedCandidateRole`, and
|
|
504
|
-
wraps the result with `reviewedCandidateResolution`.
|
|
505
|
-
|
|
506
|
-
The helper may promote the selected candidate to a caller-supplied
|
|
507
|
-
`selectedClaimId`. The unselected observation keeps its caller-authored claim
|
|
508
|
-
id, so producers can keep losing candidates as candidate-specific history.
|
|
509
|
-
Survey does not decide producer policy: callers still own review status,
|
|
510
|
-
selected and unselected claim statuses, source details, claim vocabulary, and
|
|
511
|
-
domain metadata.
|
|
512
|
-
|
|
513
|
-
## Repeated observations
|
|
514
|
-
|
|
515
|
-
Use `repeatedObservation` when a producer wants to describe a repeated field or
|
|
516
|
-
entity list as one aggregate observation. The helper returns a normal
|
|
517
|
-
`SurveyObservationInput`, so it works with `SurveyInputBuilder.addObservation`
|
|
518
|
-
and the same Surface projection path.
|
|
519
|
-
|
|
520
|
-
```ts
|
|
521
|
-
import {
|
|
522
|
-
buildSurveyTrustInput,
|
|
523
|
-
repeatedObservation,
|
|
524
|
-
SurveyInputBuilder,
|
|
525
|
-
} from "@kontourai/survey";
|
|
526
|
-
|
|
527
|
-
const aliases = [
|
|
528
|
-
{ name: "North Annex", sourceLabel: "record row 1" },
|
|
529
|
-
{ name: "East Annex", sourceLabel: "record row 2" },
|
|
530
|
-
];
|
|
531
|
-
|
|
532
|
-
const surveyInput = new SurveyInputBuilder({
|
|
533
|
-
source: "example-producer:run-1",
|
|
534
|
-
})
|
|
535
|
-
.addObservation(repeatedObservation({
|
|
536
|
-
id: "entity-123.aliases.current",
|
|
537
|
-
field: "knownAliases",
|
|
538
|
-
value: aliases,
|
|
539
|
-
rawSource: {
|
|
540
|
-
kind: "api-record",
|
|
541
|
-
sourceRef: "example-records://entity/entity-123",
|
|
542
|
-
observedAt: new Date().toISOString(),
|
|
543
|
-
locatorScheme: "structured-field",
|
|
544
|
-
},
|
|
545
|
-
extraction: {
|
|
546
|
-
confidence: 0.88,
|
|
547
|
-
locator: "json:$.aliases",
|
|
548
|
-
extractor: "example-extractor",
|
|
549
|
-
extractedAt: new Date().toISOString(),
|
|
550
|
-
},
|
|
551
|
-
reviewOutcome: {
|
|
552
|
-
status: "verified",
|
|
553
|
-
actor: "records-operator",
|
|
554
|
-
reviewedAt: new Date().toISOString(),
|
|
555
|
-
},
|
|
556
|
-
claim: {
|
|
557
|
-
subjectType: "public-record.entity",
|
|
558
|
-
subjectId: "entity-123",
|
|
559
|
-
surface: "example.profile",
|
|
560
|
-
claimType: "public-data.repeated-field",
|
|
561
|
-
status: "verified",
|
|
562
|
-
impactLevel: "medium",
|
|
563
|
-
collectedBy: "example-extractor",
|
|
564
|
-
},
|
|
565
|
-
metadata: {
|
|
566
|
-
producerField: "aliases",
|
|
567
|
-
},
|
|
568
|
-
}))
|
|
569
|
-
.build();
|
|
570
|
-
|
|
571
|
-
const trustInput = buildSurveyTrustInput(surveyInput);
|
|
572
|
-
```
|
|
126
|
+
`@kontourai/survey/review-workbench/standalone.css` exists for pages Survey owns entirely. Server code persisting browser-submitted review events should use `persistReviewSessionEvents`, then `deriveReviewSessionApplyResultForSnapshot` (or the composed `deriveServerReviewSessionApplyResult` with the freshness and event assertions from `@kontourai/survey/review-workbench/server-review-session`) before applying product policy — write results derive from pre-decision snapshots plus persisted events, never from browser-computed decisions.
|
|
573
127
|
|
|
574
|
-
`
|
|
575
|
-
`claim.fieldOrBehavior` from `field` when omitted, uses the array as both the
|
|
576
|
-
extraction and claim value, and adds neutral helper metadata at
|
|
577
|
-
`metadata.survey.repeated = { representation: "aggregate-array", itemCount }`.
|
|
578
|
-
Producer metadata is preserved. Producers still own item semantics,
|
|
579
|
-
validation, candidate ranking, review policy, and whether a value should be
|
|
580
|
-
verified, proposed, rejected, or assumed.
|
|
128
|
+
The [Consumer Integration Guide](docs/consumer-integration-guide.md) covers the full path from `ReviewItem` construction through persisted review events, exported results, and optional Surface projection, with test-covered examples under [`examples/review-workbench/`](examples/review-workbench/).
|
|
581
129
|
|
|
582
|
-
##
|
|
130
|
+
## Where Survey fits
|
|
583
131
|
|
|
584
|
-
|
|
585
|
-
for the same target and wants Survey to assemble the shared candidate set,
|
|
586
|
-
candidate links, and optional review outcome.
|
|
132
|
+
Kontour AI shows the work behind AI. Survey is the producer-side primitive:
|
|
587
133
|
|
|
588
|
-
|
|
589
|
-
import {
|
|
590
|
-
candidateReviewRecord,
|
|
591
|
-
fieldObservation,
|
|
592
|
-
SurveyInputBuilder,
|
|
593
|
-
} from "@kontourai/survey";
|
|
594
|
-
|
|
595
|
-
const observations = [
|
|
596
|
-
fieldObservation({
|
|
597
|
-
id: "entity-123.status.registry",
|
|
598
|
-
field: "registrationStatus",
|
|
599
|
-
value: "ACTIVE",
|
|
600
|
-
rawSource: {
|
|
601
|
-
kind: "api-record",
|
|
602
|
-
sourceRef: "example-records://entity/entity-123",
|
|
603
|
-
observedAt: new Date().toISOString(),
|
|
604
|
-
locatorScheme: "structured-field",
|
|
605
|
-
},
|
|
606
|
-
extraction: {
|
|
607
|
-
confidence: 0.97,
|
|
608
|
-
locator: "json:$.registrationStatus",
|
|
609
|
-
extractor: "example-extractor",
|
|
610
|
-
extractedAt: new Date().toISOString(),
|
|
611
|
-
},
|
|
612
|
-
candidate: { id: "candidate.registry", confidence: 0.97 },
|
|
613
|
-
claim: {
|
|
614
|
-
id: "claim.entity-123.status.registry",
|
|
615
|
-
subjectType: "public-record.entity",
|
|
616
|
-
subjectId: "entity-123",
|
|
617
|
-
surface: "example.profile",
|
|
618
|
-
claimType: "public-data.field",
|
|
619
|
-
status: "verified",
|
|
620
|
-
impactLevel: "medium",
|
|
621
|
-
collectedBy: "example-extractor",
|
|
622
|
-
},
|
|
623
|
-
}),
|
|
624
|
-
fieldObservation({
|
|
625
|
-
id: "entity-123.status.archive",
|
|
626
|
-
field: "registrationStatus",
|
|
627
|
-
value: "INACTIVE",
|
|
628
|
-
rawSource: {
|
|
629
|
-
kind: "web-page",
|
|
630
|
-
sourceRef: "https://records.example.test/entity-123",
|
|
631
|
-
observedAt: new Date().toISOString(),
|
|
632
|
-
locatorScheme: "html",
|
|
633
|
-
},
|
|
634
|
-
extraction: {
|
|
635
|
-
confidence: 0.71,
|
|
636
|
-
locator: "css:#registration-status",
|
|
637
|
-
extractor: "example-crawler",
|
|
638
|
-
extractedAt: new Date().toISOString(),
|
|
639
|
-
},
|
|
640
|
-
candidate: { id: "candidate.archive", confidence: 0.71 },
|
|
641
|
-
claim: {
|
|
642
|
-
id: "claim.entity-123.status.archive",
|
|
643
|
-
subjectType: "public-record.entity",
|
|
644
|
-
subjectId: "entity-123",
|
|
645
|
-
surface: "example.profile",
|
|
646
|
-
claimType: "public-data.field",
|
|
647
|
-
status: "superseded",
|
|
648
|
-
impactLevel: "medium",
|
|
649
|
-
collectedBy: "example-crawler",
|
|
650
|
-
},
|
|
651
|
-
}),
|
|
652
|
-
];
|
|
653
|
-
|
|
654
|
-
const surveyInput = new SurveyInputBuilder({ source: "example-producer:run-1" })
|
|
655
|
-
.addClaimRecords(candidateReviewRecord({
|
|
656
|
-
id: "candidate-set.entity-123.registration-status",
|
|
657
|
-
target: "registrationStatus",
|
|
658
|
-
selectedCandidateId: "candidate.registry",
|
|
659
|
-
status: "resolved",
|
|
660
|
-
rationale: "Registry source wins over archive source.",
|
|
661
|
-
reviewOutcome: {
|
|
662
|
-
status: "verified",
|
|
663
|
-
actor: "records-operator",
|
|
664
|
-
reviewedAt: new Date().toISOString(),
|
|
665
|
-
},
|
|
666
|
-
observations,
|
|
667
|
-
}))
|
|
668
|
-
.build();
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
`candidateReviewRecord` does not choose the winning candidate or status. The
|
|
672
|
-
producer still supplies candidate ids, selected candidate id, claim ids, review
|
|
673
|
-
status, rationale, and all domain policy. Survey only assembles the generic
|
|
674
|
-
record graph and tolerates repeated references to identical raw sources or the
|
|
675
|
-
shared candidate set while rejecting conflicting duplicate ids. Duplicate
|
|
676
|
-
conflict checks assume Survey records are JSON-shaped data, which is the same
|
|
677
|
-
shape expected by Surface validation and reports.
|
|
678
|
-
|
|
679
|
-
If an observation candidate includes `rejectionReason`, `candidateReviewRecord`
|
|
680
|
-
preserves it in the shared candidate set. Use this only for producer-authored
|
|
681
|
-
rationale about a candidate that the producer already treats as non-selected,
|
|
682
|
-
superseded, or rejected; it does not affect selected candidate behavior or
|
|
683
|
-
status projection.
|
|
684
|
-
|
|
685
|
-
A candidate set with status `"conflict"` represents a Survey-side Candidate
|
|
686
|
-
Conflict before review has resolved which candidate should win. When no review
|
|
687
|
-
outcome overrides it, `buildSurveyTrustInput` projects the claim to Surface
|
|
688
|
-
status `"disputed"` and records a `"candidate-conflict"` verification event.
|
|
689
|
-
|
|
690
|
-
## Review proofs
|
|
691
|
-
|
|
692
|
-
Use review proof helpers when a producer wants a Surface-compatible integrity
|
|
693
|
-
anchor for one reviewed Survey source -> extraction -> candidate -> review ->
|
|
694
|
-
claim path.
|
|
695
|
-
|
|
696
|
-
```ts
|
|
697
|
-
import {
|
|
698
|
-
buildCanonicalReviewProofPayload,
|
|
699
|
-
buildReviewProofAnchor,
|
|
700
|
-
canonicalReviewProofJson,
|
|
701
|
-
hashCanonicalReviewProofPayload,
|
|
702
|
-
} from "@kontourai/survey";
|
|
703
|
-
|
|
704
|
-
const proofInput = {
|
|
705
|
-
rawSource,
|
|
706
|
-
extraction,
|
|
707
|
-
candidate,
|
|
708
|
-
candidateSet,
|
|
709
|
-
reviewOutcome,
|
|
710
|
-
claim,
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
const payload = buildCanonicalReviewProofPayload(proofInput);
|
|
714
|
-
const canonicalJson = canonicalReviewProofJson(payload);
|
|
715
|
-
const hash = hashCanonicalReviewProofPayload(payload);
|
|
716
|
-
const anchor = buildReviewProofAnchor(proofInput);
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
`buildReviewProofAnchor` returns a hash-only Surface `IntegrityAnchor` for the
|
|
720
|
-
canonical payload. The lower-level payload, JSON, and hash helpers are exported
|
|
721
|
-
so producers can store or recompute the exact canonical proof material used for
|
|
722
|
-
the anchor. Producer metadata is not part of the canonical payload; any
|
|
723
|
-
non-portable context belongs outside the hash, such as anchor metadata.
|
|
724
|
-
|
|
725
|
-
The canonical payload is the portable review proof contract. It contains:
|
|
726
|
-
|
|
727
|
-
| Field | Purpose |
|
|
134
|
+
| Product | Owns |
|
|
728
135
|
| --- | --- |
|
|
729
|
-
|
|
|
730
|
-
|
|
|
731
|
-
|
|
|
732
|
-
|
|
|
733
|
-
|
|
|
734
|
-
| `proof.subject` | Claim identity: claim id, candidate set id, reviewed candidate id, subject, surface, claim type, and field/behavior. If the claim also names a candidate id, it must match the reviewed candidate id. |
|
|
735
|
-
| `proof.sourcePayload` / `rawSource.checksum` | Source payload identity, ref, and producer-supplied checksum when present. |
|
|
736
|
-
| `extraction` | Extracted target, value, locator, excerpt, extractor, confidence, and extraction time. |
|
|
737
|
-
| `candidate` / `candidateSet` | Candidate identity/value plus the ordered candidate set, selected candidate, status, and rationale. |
|
|
738
|
-
| `reviewOutcome` | Review decision/status, actor, review time, rationale, and evidence ids. |
|
|
739
|
-
| `claim` | Projected claim identity, status/value, impact, evidence method, derivation links, collector, actor, and event method. |
|
|
740
|
-
|
|
741
|
-
To recompute the anchor value, rebuild the same canonical payload from the
|
|
742
|
-
reviewed Survey records, call `canonicalReviewProofJson(payload)`, and compute
|
|
743
|
-
SHA-256 over that JSON. The result should equal
|
|
744
|
-
`claim.currentIntegrityAnchor.value`. The Surface anchor remains generic:
|
|
745
|
-
`kind: "hash"`, `algorithm: "sha256"`, `verificationStatus: "unverified"`, no
|
|
746
|
-
Survey-specific anchor metadata, and a source/time pointer for display. Claims
|
|
747
|
-
without a selected review outcome are not anchored by `{ reviewProofs: true }`.
|
|
748
|
-
|
|
749
|
-
When the Surface projection proof option is enabled, `buildSurveyTrustInput`
|
|
750
|
-
will attach the same kind of anchor to the projected reviewed claim:
|
|
751
|
-
|
|
752
|
-
```ts
|
|
753
|
-
const trustInput = buildSurveyTrustInput(surveyInput, { reviewProofs: true });
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
The proof provides hash-only tamper evidence for the Survey review/provenance
|
|
757
|
-
trail in the canonical payload. It does not authenticate an actor, sign the
|
|
758
|
-
payload, or prove the real-world truth of the claim. Non-goals include JWT/JWS
|
|
759
|
-
signing, key management, a transparency log, and any veracity guarantee.
|
|
760
|
-
|
|
761
|
-
JWT-adjacent words in the payload are process-envelope vocabulary, not a v0 JWT
|
|
762
|
-
implementation. `issuer` identifies the Survey producer for recomputation,
|
|
763
|
-
`subject` identifies the claim being reviewed, and `issuedAt` records the review
|
|
764
|
-
proof time. Audience restrictions, expiry, cryptographic signing, key discovery,
|
|
765
|
-
and legal non-repudiation are deferred concepts for a future signed envelope.
|
|
766
|
-
|
|
767
|
-
## Computed values
|
|
768
|
-
|
|
769
|
-
Computed values are normal `ClaimTarget` entries in `claims`. Producers should
|
|
770
|
-
link them to their inputs with Surface Claim Dependency fields:
|
|
771
|
-
`derivedFrom` for simple claim-id links, or `derivationEdges` when the link
|
|
772
|
-
needs method, role, support-strength, rationale, or metadata.
|
|
773
|
-
|
|
774
|
-
Survey passes those fields through to Surface while keeping the same
|
|
775
|
-
source -> extraction -> candidate -> review -> claim projection path. Surface
|
|
776
|
-
owns dependency semantics such as recompute pressure and status ceilings.
|
|
777
|
-
|
|
778
|
-
## Adversarial passes
|
|
779
|
-
|
|
780
|
-
Producers that run a second adversarial pass — whether an LLM judge, a rules
|
|
781
|
-
engine, or a second human reviewer — emit their output into Survey as a normal
|
|
782
|
-
producer pass with a distinct `extractor` id. Survey does not know or care that
|
|
783
|
-
a second pass ran; it sees two producers disagreeing on the same target, which
|
|
784
|
-
is exactly what `conflict` and escalation records are for.
|
|
136
|
+
| **Survey** | Producer evidence: source → extraction → candidate → review → claim |
|
|
137
|
+
| **[Surface](https://kontourai.io/surface)** | Portable trust state: claims, evidence, policies, trust snapshots |
|
|
138
|
+
| **[Flow](https://kontourai.io/flow)** | Process transparency: steps, gates, transitions, runs, exceptions |
|
|
139
|
+
| **[Veritas](https://kontourai.io/veritas)** | Code/change transparency: repo standards, merge readiness |
|
|
140
|
+
| **[Flow Agents](https://kontourai.io/flow-agents)** | Agent-facing distribution: skills, kits, runtime adapters, hooks |
|
|
785
141
|
|
|
786
|
-
|
|
142
|
+
Survey feeds Surface; Surface-shaped evidence feeds Flow gates; Flow's adversarial route-back pattern consumes Survey's per-round adversarial-pass records. Each product stands alone — Survey only requires `@kontourai/surface`.
|
|
787
143
|
|
|
788
|
-
|
|
789
|
-
value. Add the adversary's extraction as a second candidate to the same candidate
|
|
790
|
-
set using `candidateReviewRecord` with `status: "conflict"`. Survey projects the
|
|
791
|
-
conflict to a `disputed` claim in Surface.
|
|
144
|
+
## Documentation
|
|
792
145
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
fieldObservation({
|
|
803
|
-
id: "observation.entity-1.status.first-pass",
|
|
804
|
-
field: "registrationStatus",
|
|
805
|
-
value: "ACTIVE",
|
|
806
|
-
rawSource: {
|
|
807
|
-
kind: "api-record",
|
|
808
|
-
sourceRef: "records://entity-1/registry",
|
|
809
|
-
observedAt: new Date().toISOString(),
|
|
810
|
-
locatorScheme: "structured-field",
|
|
811
|
-
},
|
|
812
|
-
extraction: {
|
|
813
|
-
confidence: 0.91,
|
|
814
|
-
locator: "json:$.registrationStatus",
|
|
815
|
-
extractor: "agent-v1",
|
|
816
|
-
extractedAt: new Date().toISOString(),
|
|
817
|
-
},
|
|
818
|
-
candidate: { id: "candidate.first-pass", confidence: 0.91 },
|
|
819
|
-
claim: {
|
|
820
|
-
subjectType: "public-record.entity",
|
|
821
|
-
subjectId: "entity-1",
|
|
822
|
-
surface: "public-record.profile",
|
|
823
|
-
claimType: "public-data.field",
|
|
824
|
-
impactLevel: "high",
|
|
825
|
-
collectedBy: "agent-v1",
|
|
826
|
-
},
|
|
827
|
-
}),
|
|
828
|
-
fieldObservation({
|
|
829
|
-
id: "observation.entity-1.status.adversary",
|
|
830
|
-
field: "registrationStatus",
|
|
831
|
-
value: "INACTIVE",
|
|
832
|
-
rawSource: {
|
|
833
|
-
kind: "api-record",
|
|
834
|
-
sourceRef: "records://entity-1/registry",
|
|
835
|
-
observedAt: new Date().toISOString(),
|
|
836
|
-
locatorScheme: "structured-field",
|
|
837
|
-
},
|
|
838
|
-
extraction: {
|
|
839
|
-
confidence: 0.84,
|
|
840
|
-
locator: "json:$.registrationStatus",
|
|
841
|
-
extractor: "adversary-v1",
|
|
842
|
-
extractedAt: new Date().toISOString(),
|
|
843
|
-
},
|
|
844
|
-
candidate: { id: "candidate.adversary", confidence: 0.84 },
|
|
845
|
-
claim: {
|
|
846
|
-
subjectType: "public-record.entity",
|
|
847
|
-
subjectId: "entity-1",
|
|
848
|
-
surface: "public-record.profile",
|
|
849
|
-
claimType: "public-data.field",
|
|
850
|
-
impactLevel: "high",
|
|
851
|
-
collectedBy: "adversary-v1",
|
|
852
|
-
},
|
|
853
|
-
}),
|
|
854
|
-
],
|
|
855
|
-
});
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
**Framing challenge.** The adversary identifies a target that was not addressed
|
|
859
|
-
at all — a missed standard, an unconsidered alternative, or a misframed question.
|
|
860
|
-
Use `addEscalation` to record the challenge. Attach it to the closest relevant
|
|
861
|
-
claim with `attachToClaimId`; Survey projects it as an additional `disputed`
|
|
862
|
-
verification event on that claim so the reviewer sees it prominently.
|
|
863
|
-
|
|
864
|
-
```ts
|
|
865
|
-
import { SurveyInputBuilder, fieldObservation } from "@kontourai/survey";
|
|
866
|
-
|
|
867
|
-
const builder = new SurveyInputBuilder({ source: "example-producer:run-2" });
|
|
868
|
-
|
|
869
|
-
// First-pass observation
|
|
870
|
-
builder.addObservation(fieldObservation({ /* ... */ }));
|
|
871
|
-
|
|
872
|
-
// Adversary raises a framing challenge
|
|
873
|
-
builder.addEscalation({
|
|
874
|
-
id: "escalation.entity-1.fair-value.completeness",
|
|
875
|
-
target: "fairValue",
|
|
876
|
-
dimension: "completeness",
|
|
877
|
-
reason: "Measurement standard Level 3 inputs were not documented; sensitivity range and unobservable input assumptions are missing.",
|
|
878
|
-
raisedBy: "adversary-v1",
|
|
879
|
-
raisedAt: new Date().toISOString(),
|
|
880
|
-
attachToClaimId: "claim.entity-1.fair-value",
|
|
881
|
-
});
|
|
882
|
-
```
|
|
883
|
-
|
|
884
|
-
If a subsequent first-pass or human-review pass resolves the challenge, set
|
|
885
|
-
`resolvedBy` to the id of the observation that closes it. Survey will not project
|
|
886
|
-
a `disputed` event for resolved escalations.
|
|
146
|
+
| Guide | What it covers |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| [Record Contracts](docs/record-contracts.md) | every record shape in the chain: raw sources, extractions, candidates, reviews, proofs, resolutions, comfort-zone flags |
|
|
149
|
+
| [Adversarial Passes & Learning](docs/adversarial-and-learning.md) | per-round adversarial review records and learning projections, and the Flow boundary |
|
|
150
|
+
| [Consumer Integration Guide](docs/consumer-integration-guide.md) | the full consumer path: ReviewItem queues, the workbench, persisted events, the server apply boundary |
|
|
151
|
+
| [Review Resource Contract](docs/review-resource-contract.md) | the Kontour Resource shapes for review sessions and events |
|
|
152
|
+
| [Source-Authority Review Pattern](docs/source-authority-review-pattern.md) | record discipline for sources the producer treats as authoritative |
|
|
153
|
+
| [Review Workbench Prototype](docs/review-workbench-prototype.md) | running the fixture-backed standalone demo locally |
|
|
154
|
+
| [Releasing](docs/RELEASING.md) | release prep and publish flow |
|
|
887
155
|
|
|
888
|
-
|
|
889
|
-
question framed), `completeness` (missing standards, alternatives, or evidence),
|
|
890
|
-
`conclusion` (reasoning would not survive challenge), and `citation` (cited
|
|
891
|
-
sources do not support the claims attached to them).
|
|
156
|
+
## Product boundary
|
|
892
157
|
|
|
893
|
-
|
|
894
|
-
for producer tooling but are not projected to Surface. If the adversary cannot
|
|
895
|
-
identify a target claim to attach a framing challenge to, emit a candidate set
|
|
896
|
-
with `status: "escalated"` for the affected target — that projects to `disputed`
|
|
897
|
-
in Surface with a `candidate-escalation` event.
|
|
158
|
+
Survey does not crawl pages, parse PDFs, rank candidates, decide review policy, or claim a value is true. Producers own acquisition, extraction, ranking, review UX, materiality, and domain policy. Survey gives those producers a consistent source → extraction → candidate → review → claim contract before the records enter Surface.
|
|
898
159
|
|
|
899
|
-
##
|
|
160
|
+
## Contributor checks
|
|
900
161
|
|
|
901
|
-
|
|
902
|
-
recording a decision outside their domain expertise or is flagging that the
|
|
903
|
-
conclusion requires a different authority to confirm. The flag and optional
|
|
904
|
-
`comfortZoneNote` are carried forward as structured Survey metadata on the
|
|
905
|
-
projected Surface claim at `metadata.survey.comfortZone`. Verification event
|
|
906
|
-
`notes` carry the normal review or candidate-set rationale only.
|
|
162
|
+
Install the repo-owned Git hooks once per clone:
|
|
907
163
|
|
|
908
|
-
```
|
|
909
|
-
|
|
910
|
-
status: "assumed",
|
|
911
|
-
actor: "records-operator",
|
|
912
|
-
reviewedAt: new Date().toISOString(),
|
|
913
|
-
rationale: "Assumed from registry source pending specialist review.",
|
|
914
|
-
withinComfortZone: false,
|
|
915
|
-
comfortZoneNote: "Renewal clause interpretation requires specialist counsel.",
|
|
916
|
-
},
|
|
164
|
+
```bash
|
|
165
|
+
npm run setup:repo-hooks
|
|
917
166
|
```
|
|
918
167
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
Use `buildSurveyLearningProjections(input)` when producer or review tooling needs
|
|
922
|
-
workflow/evaluation signals without changing Surface `TrustInput`.
|
|
168
|
+
The setup command is idempotent. It sets this repo's local `core.hooksPath` to `.githooks` and does not require global Git configuration. Validate hook drift or package health directly:
|
|
923
169
|
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
buildSurveyTrustInput,
|
|
928
|
-
} from "@kontourai/survey";
|
|
929
|
-
|
|
930
|
-
const learning = buildSurveyLearningProjections(surveyInput);
|
|
931
|
-
const trustInput = buildSurveyTrustInput(surveyInput);
|
|
170
|
+
```bash
|
|
171
|
+
npm run validate:repo-hooks
|
|
172
|
+
npm run verify
|
|
932
173
|
```
|
|
933
174
|
|
|
934
|
-
|
|
935
|
-
`learning.rejected-candidate` from structured candidate rejection data such as
|
|
936
|
-
non-empty `Candidate.rejectionReason` values or a candidate-specific
|
|
937
|
-
`ReviewOutcome.status === "rejected"` outcome with rationale. When both exist,
|
|
938
|
-
Survey emits one rejected-candidate projection enriched with candidate and
|
|
939
|
-
review outcome context.
|
|
940
|
-
Ordinary rejected candidates do not emit `learning.comfort-zone`.
|
|
941
|
-
|
|
942
|
-
Survey also emits `learning.comfort-zone` from structured
|
|
943
|
-
`ReviewOutcome.withinComfortZone === false` data and `learning.escalation` from
|
|
944
|
-
unresolved `EscalationRecord`s, including unattached records that producer
|
|
945
|
-
tooling can route but Surface cannot attach to a claim event.
|
|
946
|
-
|
|
947
|
-
These projections are producer/review workflow and evaluation signals. They are
|
|
948
|
-
not claims about truth or veracity, not Surface claim status, not evidence, and
|
|
949
|
-
not verification events. Calling `buildSurveyLearningProjections` does not alter
|
|
950
|
-
`buildSurveyTrustInput`, trust status derivation, or escalation event projection.
|
|
951
|
-
|
|
952
|
-
## Product Boundary
|
|
953
|
-
|
|
954
|
-
Survey does not crawl pages, parse PDFs, rank candidates, decide review policy,
|
|
955
|
-
or claim a value is true. Producers own acquisition, extraction, ranking, review
|
|
956
|
-
UX, materiality, and domain policy. Survey gives those producers a consistent
|
|
957
|
-
source -> extraction -> candidate -> review -> claim contract before the records
|
|
958
|
-
enter Surface.
|
|
175
|
+
The committed pre-push hook runs both commands from the repo root.
|
|
959
176
|
|
|
960
|
-
##
|
|
177
|
+
## License
|
|
961
178
|
|
|
962
|
-
|
|
963
|
-
npm install
|
|
964
|
-
npm run verify
|
|
965
|
-
```
|
|
179
|
+
[Apache-2.0](LICENSE) © Kontour AI
|
package/dist/src/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js"
|
|
|
7
7
|
export type { ReviewedCandidateResolutionInput } from "./reviewed-candidate-resolution.js";
|
|
8
8
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
9
9
|
export type { CurrentProposedCandidateRole, ReviewedCurrentProposedResolutionInput, } from "./reviewed-current-proposed-resolution.js";
|
|
10
|
+
export { flowTrustArtifactFromReviewOutcome } from "./to-flow-artifact.js";
|
|
11
|
+
export type { FlowTrustArtifact, FlowTrustArtifactOptions } from "./to-flow-artifact.js";
|
|
10
12
|
export { buildSurveyTrustInput } from "./to-surface.js";
|
|
11
13
|
export type { BuildSurveyTrustInputOptions } from "./to-surface.js";
|
|
12
14
|
export { buildSurveyLearningProjections } from "./learning-projections.js";
|
package/dist/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { reviewResourceApiVersion } from "./review-resource.js";
|
|
|
2
2
|
export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
|
|
3
3
|
export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js";
|
|
4
4
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
5
|
+
export { flowTrustArtifactFromReviewOutcome } from "./to-flow-artifact.js";
|
|
5
6
|
export { buildSurveyTrustInput } from "./to-surface.js";
|
|
6
7
|
export { buildSurveyLearningProjections } from "./learning-projections.js";
|
|
7
8
|
export { buildCanonicalReviewProofPayload, buildReviewProofAnchor, canonicalReviewProofJson, hashCanonicalReviewProofPayload, REVIEW_PROOF_CONTRACT_VERSION, REVIEW_PROOF_PACKAGE_NAME, REVIEW_PROOF_SCHEMA, REVIEW_PROOF_SCHEMA_VERSION, } from "./review-proof.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ReviewOutcome, ReviewStatus } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The neutral trust-artifact shape Kontour Flow consumes through
|
|
4
|
+
* `flow attach-evidence --trust-artifact`. Flow evaluates only these fields
|
|
5
|
+
* plus its own definition and project config; Survey stays the producer-side
|
|
6
|
+
* authority for what the review actually decided.
|
|
7
|
+
*/
|
|
8
|
+
export interface FlowTrustArtifact {
|
|
9
|
+
schema_version: "0.1";
|
|
10
|
+
artifact_type: "trust-report";
|
|
11
|
+
subject: string;
|
|
12
|
+
producer: string;
|
|
13
|
+
status: string;
|
|
14
|
+
issued_at: string;
|
|
15
|
+
authority_traces: string[];
|
|
16
|
+
claims: Array<{
|
|
17
|
+
type: string;
|
|
18
|
+
subject: string;
|
|
19
|
+
status: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
export interface FlowTrustArtifactOptions {
|
|
23
|
+
/** Flow claim type the gate expects, e.g. "adversarial.review". */
|
|
24
|
+
claimType: string;
|
|
25
|
+
/** Flow claim subject the gate expects, e.g. "adversarial-pass.review". */
|
|
26
|
+
subject: string;
|
|
27
|
+
/** Producer identity recorded on the artifact, e.g. "survey/adversarial-workbench". */
|
|
28
|
+
producer: string;
|
|
29
|
+
/** Defaults to the review outcome's reviewedAt, then the current time. */
|
|
30
|
+
issuedAt?: string;
|
|
31
|
+
/** Defaults to ["survey:review-outcome/<outcome id>"]. */
|
|
32
|
+
authorityTraces?: string[];
|
|
33
|
+
/** Override the ReviewStatus -> artifact status projection per entry. */
|
|
34
|
+
statusMap?: Partial<Record<ReviewStatus, string>>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Projects a Survey ReviewOutcome into the neutral trust artifact Flow
|
|
38
|
+
* consumes, so a per-round review (including an adversarial pass) can satisfy
|
|
39
|
+
* or fail a Flow gate without Flow learning Survey vocabulary.
|
|
40
|
+
*/
|
|
41
|
+
export declare function flowTrustArtifactFromReviewOutcome(outcome: ReviewOutcome, options: FlowTrustArtifactOptions): FlowTrustArtifact;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const defaultStatusMap = {
|
|
2
|
+
verified: "trusted",
|
|
3
|
+
assumed: "assumed",
|
|
4
|
+
proposed: "proposed",
|
|
5
|
+
rejected: "rejected",
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Projects a Survey ReviewOutcome into the neutral trust artifact Flow
|
|
9
|
+
* consumes, so a per-round review (including an adversarial pass) can satisfy
|
|
10
|
+
* or fail a Flow gate without Flow learning Survey vocabulary.
|
|
11
|
+
*/
|
|
12
|
+
export function flowTrustArtifactFromReviewOutcome(outcome, options) {
|
|
13
|
+
if (!outcome.id) {
|
|
14
|
+
throw new Error("flowTrustArtifactFromReviewOutcome requires a review outcome id");
|
|
15
|
+
}
|
|
16
|
+
const statusMap = { ...defaultStatusMap, ...options.statusMap };
|
|
17
|
+
const status = statusMap[outcome.status];
|
|
18
|
+
if (!status) {
|
|
19
|
+
throw new Error(`flowTrustArtifactFromReviewOutcome: unmapped review status '${outcome.status}'`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
schema_version: "0.1",
|
|
23
|
+
artifact_type: "trust-report",
|
|
24
|
+
subject: options.subject,
|
|
25
|
+
producer: options.producer,
|
|
26
|
+
status,
|
|
27
|
+
issued_at: options.issuedAt ?? outcome.reviewedAt ?? new Date().toISOString(),
|
|
28
|
+
authority_traces: options.authorityTraces ?? [`survey:review-outcome/${outcome.id}`],
|
|
29
|
+
claims: [{ type: options.claimType, subject: options.subject, status }],
|
|
30
|
+
};
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/survey",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.24",
|
|
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",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/kontourai/survey.git"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://kontourai.io/survey",
|
|
12
|
+
"homepage": "https://kontourai.github.io/survey/",
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/kontourai/survey/issues"
|
|
15
15
|
},
|
|
@@ -52,7 +52,9 @@
|
|
|
52
52
|
"check:review-workbench": "npm run check:review-workbench:static",
|
|
53
53
|
"check:review-workbench:static": "npm run build && node scripts/check-review-workbench.cjs",
|
|
54
54
|
"setup:repo-hooks": "node scripts/setup-repo-hooks.cjs",
|
|
55
|
-
"validate:repo-hooks": "node scripts/validate-repo-hooks.cjs"
|
|
55
|
+
"validate:repo-hooks": "node scripts/validate-repo-hooks.cjs",
|
|
56
|
+
"docs:build": "npm run build --silent && node scripts/docs-site/build.ts",
|
|
57
|
+
"docs:check": "npm run docs:build && node scripts/docs-site/check-links.ts"
|
|
56
58
|
},
|
|
57
59
|
"dependencies": {
|
|
58
60
|
"@kontourai/surface": "^0.5.1"
|
|
@@ -61,6 +63,7 @@
|
|
|
61
63
|
"@kontourai/console-kit": "^0.1.0",
|
|
62
64
|
"@playwright/test": "^1.60.0",
|
|
63
65
|
"@types/node": "^25.6.0",
|
|
66
|
+
"marked": "^16.4.2",
|
|
64
67
|
"typescript": "^5.8.0"
|
|
65
68
|
},
|
|
66
69
|
"engines": {
|