v0.4.19 npm install -D @kontourai/survey

Survey

review workflow for producing trustworthy Surface-ready claims

Before a claim reaches Surface, someone has to form it — from a source, an extraction, competing candidates, and a review. Survey is the small contract for that producer side: it makes those observations inspectable, gives reviewers a durable workflow, and projects the reviewed records into Surface-ready claims with provenance attached, without crawling, parsing, ranking, or absorbing your domain policy.

quickstart.ts
// proposed claim changes in → auditable review results out
import { buildReviewWorkbenchSessionExportForSnapshot,
         createPersistentReviewSessionEventStore,
         mountReviewWorkbench } from "@kontourai/survey/review-workbench";

const session = {
  items: [registrationStatusReviewItem],
  activeItemName: "entity-123.registrationStatus",
  notesByItemName: {},
  decisionsByItemName: {},
  actorId: "[email protected]",
  reviewedAt: new Date().toISOString(),
};

mountReviewWorkbench(document.querySelector("#review")!, session, {
  eventStore: createPersistentReviewSessionEventStore({
    initialEvents,
    persist: ({ events, expectedEventCount }) =>
      saveReviewEvents({ events, expectedEventCount }),
  }),
});

const exported = buildReviewWorkbenchSessionExportForSnapshot(
  reviewedSnapshot,
  persistedEvents,
);

const results = exported.results;

The producer pipeline

One path from raw source to a claim Surface can inspect.

01 Source

Where the value came from — a page, a document, an API record, a manual entry — with its reference, time, and checksum.

02 Extraction

The value pulled from that source, with a locator, an excerpt, the extractor, and a confidence.

03 Candidate

Competing values for the same target, kept side by side instead of silently dropped.

04 Review

The outcome a human or policy assigns — verified, assumed, rejected, or needs-review.

05 Claim

A Surface-ready claim with its full provenance trail attached, ready to be reported on.

A deliberately narrow boundary

Survey does not crawl pages, parse PDFs, rank candidates, decide review policy, or claim a value is true. It gives producers a consistent contract so the same provenance shape reaches Surface every time.

Survey owns

  • Raw source identity & retrieval context
  • Extraction observations & locators
  • Candidate sets and resolutions
  • Review outcomes
  • Projection into Surface TrustInput

Producers own

  • Acquisition, crawling, and parsing
  • Candidate ranking and scoring
  • Review UX and policy
  • Materiality and domain rules
  • Whether a value is verified or assumed

Surface owns

  • Claim status vocabulary
  • Evidence display & support strength
  • Claim dependencies & derived edges
  • Freshness and stale cascades
  • Trust reports and console views

Consumer adapter contract

Products keep their policy. Survey standardizes the review trail.

A producer builds ReviewItem records from its own queue, supplies a ReviewPresentationAdapter for readable labels and links, persists ReviewSessionEvent resources, and replays those events on the server before applying a decision.

That gives future producers a clear checklist for trustworthy claims: source identity, extraction locator, candidate history, reviewer action, actor/time, and the Surface projection are all explicit before the claim is published.

Read the integration guide →
consumer-adapter.ts
import { buildReviewItemPresentation,
         buildReviewWorkbenchSessionExportForSnapshot,
         type ReviewPresentationAdapter } from "@kontourai/survey/review-workbench";

const adapter = {
  labelForTarget: (target) =>
    target === "registrationStatus" ? "Registration status" : undefined,
  summarizeValue: (value) => humanizeProductValue(value),
  linkForReviewItem: (item) => ({
    label: item.metadata.producer?.displayName,
    href: "/review/" + encodeURIComponent(item.metadata.name),
  }),
  linkForTraceRef: (ref) =>
    ref.kind === "claim" ? { href: "/claims/" + ref.value } : undefined,
} satisfies ReviewPresentationAdapter;

const readable = buildReviewItemPresentation(reviewItem, adapter);
const exported = buildReviewWorkbenchSessionExportForSnapshot(
  reviewedSnapshot,
  persistedEvents,
);

Authoring helpers

Describe the common cases without hand-assembling every record.

fieldObservation() One scalar field value with its source, extraction, review, and claim defaults filled in.
repeatedObservation() A repeated field or entity list described as one aggregate observation.
candidateReviewRecord() Multiple candidates for one target, assembled into a shared candidate set with links.
reviewedCandidateResolution() Attaches a review outcome to the selected candidate and supersedes the rest.
ReviewPresentationAdapter Product-owned labels, value summaries, and source/trace links for generic Survey review records.
buildReviewItemPresentation() Turns a ReviewItem plus adapter hooks into human-readable review copy without changing canonical data.
buildReviewResultPresentation() Explains a derived review result and selected value while keeping write authority on the product server.
buildReviewWorkbenchSessionExportForSnapshot() Replays persisted review events against the reviewed snapshot before deriving selected candidates.
validateReviewSessionEventsForSnapshot() Reports stale item or candidate references before a producer applies review results.
uploadedDocumentSource() A stable RawSource for a document a producer already retrieved.
apiRecordSource() A stable RawSource for a structured record pulled from an API.
webPageSource() A stable RawSource for a crawled web page, with locator scheme and checksum.
manualEntrySource() A stable RawSource for a value entered by hand, kept inspectable like the rest.

Review apply contract

Survey can derive the review result. Your product still owns the write.

The workbench gives downstream products the same session, event, decision, and selected-candidate mechanics. That removes boilerplate from review flows without turning Survey into a product-specific approval engine.

A server apply path should load the current product record, replay persisted review events against the reviewed snapshot, derive results with Survey, validate that the target is still current, and stamp the mutating actor from authenticated product context. Browser exports are display and audit data, not write authority.

server-apply.ts
import { buildReviewWorkbenchSessionExportForSnapshot } from "@kontourai/survey/review-workbench";

const current = await loadProductRecord(recordId);
const snapshot = await loadReviewedSnapshot(reviewId);
const events = await loadPersistedReviewEvents(reviewId);

const exported = buildReviewWorkbenchSessionExportForSnapshot(
  snapshot,
  events,
);

for (const result of exported.results) {
  assertTargetStillMatches(current, result);
  await applyProductPolicy({
    selectedCandidateId: result.selectedCandidateId,
    selectedValue: result.selectedValue,
    actor: auth.user.id,
  });
}

Example use case

One field. Full provenance.

A crawler reads a registration status off a public record and needs to preserve the extraction before it becomes a claim. With fieldObservation, the source, extraction, review outcome, and claim travel together — so Surface can report not just the value, but where it came from and how fresh it is.

Conflicting candidates don't disappear. A losing source stays on the trail as superseded, and an unresolved conflict projects to Surface as disputed.

field-observation.ts
import { fieldObservation,
         SurveyInputBuilder } from "@kontourai/survey";

const input = new SurveyInputBuilder({ source: "crawler:run-1" })
  .addObservation(fieldObservation({
    id: "entity-123.status.current",
    field: "registrationStatus",
    value: "ACTIVE",
    rawSource: {
      kind: "web-page",
      sourceRef: "https://records.test/123",
      observedAt: now(),
      locatorScheme: "html",
    },
    extraction: {
      confidence: 0.97,
      locator: "css:#registration-status",
      extractor: "example-crawler",
      extractedAt: now(),
    },
    reviewOutcome: { status: "verified" },
    claim: {
      subjectType: "public-record.entity",
      subjectId: "entity-123",
      claimType: "public-data.field",
      impactLevel: "medium",
      collectedBy: "example-crawler",
    },
  }))
  .build();

Built for Surface

Survey produces.
Surface makes it inspectable.

buildSurveyTrustInput projects Survey records into a Surface TrustInput. From there, Surface owns claim status, evidence, dependencies, freshness, and the trust reports humans and agents inspect.

Explore Surface →
survey → surface

Survey records

source → extraction → candidate → review → claim


buildSurveyTrustInput()

───────────────────────────────

Surface TrustInput

verified registrationStatus = ACTIVE

evidence web-page · css:#registration-status

freshness owned by Surface policy