<div align="center">

# Kontour Survey

**The producer side of trust. Survey carries evidence from raw source to reviewed claim — without ever pretending to know what's true.**

[![npm version](https://img.shields.io/npm/v/%40kontourai%2Fsurvey)](https://www.npmjs.com/package/@kontourai/survey)
[![CI](https://github.com/kontourai/survey/actions/workflows/ci.yml/badge.svg)](https://github.com/kontourai/survey/actions/workflows/ci.yml)
[![Docs](https://img.shields.io/badge/docs-kontourai.github.io-blue)](https://kontourai.github.io/survey/)
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)

[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)

</div>

---

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.

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) Trust Bundles — 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.

Survey does not decide whether a real-world value is true. It preserves the producer's evidence and review discipline so something downstream can.

## What you get

- **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.
- **One projection to Surface.** `buildSurveyTrustBundle` turns Survey records into a Surface Trust Bundle; Surface owns claims, evidence, status, and trust reporting from there.
- **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.
- **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.
- **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).

## See it

The Review Workbench rendering a real example queue — current vs proposed values, source evidence, decision effect, and the Surface projection preview. [Open the live demo](https://kontourai.github.io/survey/demo/).

![Survey Review Workbench showing a review queue, current versus proposed values, decision controls, and a Surface preview](docs/assets/review-workbench-desktop.png)

## Quickstart

```sh
npm install @kontourai/survey @kontourai/surface@^4
```

Survey supports `@kontourai/surface` 2.13 and later 2.x, 3.x, and 4.x. CI tests
2.13.0 and the newest 3.x and 4.x. Install a Surface version in that range so
your project and Survey share one Surface copy (`npm ls @kontourai/surface`
shows one entry). Without a version, npm installs the latest Surface, which
can fall outside that range; npm then installs a separate copy for Survey.

Requires Node.js >=22. TypeScript >=5.0 is required to compile against
Survey's published type declarations (`defineProductVocabulary`'s `const`
type parameters are TS 5.0+ syntax) — JavaScript consumers are unaffected;
TypeScript consumers on an older compiler should stay on `1.2.x`. See the
[Upgrade Guide](docs/upgrade-guide.md#consuming-decisioneffects-safely) for
details.

```ts
import { buildTrustReport, validateTrustBundle } from "@kontourai/surface";
import { buildSurveyTrustBundle, SurveyInputBuilder } from "@kontourai/survey";

const surveyInput = new SurveyInputBuilder({
  source: "example-producer:run-1",
})
  .addObservation({
    id: "listing-123.availability.current",
    rawSource: {
      kind: "web-page",
      sourceRef: "https://example.test/listings/123",
      observedAt: new Date().toISOString(),
      locatorScheme: "html",
    },
    extraction: {
      target: "availabilityStatus",
      value: "AVAILABLE",
      confidence: 0.92,
      locator: "html:field=availabilityStatus",
      excerpt: "Availability is open.",
      extractor: "example-crawler",
      extractedAt: new Date().toISOString(),
    },
    reviewOutcome: {
      status: "verified",
      actor: "example-operator",
      reviewedAt: new Date().toISOString(),
    },
    claim: {
      subjectType: "public-record.entity",
      subjectId: "listing-123",
      facet: "public-record.profile",
      claimType: "public-data.field",
      fieldOrBehavior: "availabilityStatus",
      impactLevel: "medium",
      collectedBy: "example-crawler",
    },
  })
  .build();

const trustBundle = validateTrustBundle(buildSurveyTrustBundle(surveyInput));
const report = buildTrustReport(trustBundle);
```

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.

## The producer validation path

1. Build Survey observations covering each link in the evidence chain.
2. Call `buildSurveyTrustBundle` to project the Survey records into a Surface Trust Bundle.
3. Call Surface `validateTrustBundle` on the Trust Bundle.
4. Optionally call public Surface report APIs such as `buildTrustReport` to inspect claims, evidence, status, gaps, and metadata.

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 evidence chain records needed by Surface.

Model-backed producers implement `MappingProposer` or `UtteranceClaimExtractor` in the product that owns the prompt, parsing, runtime policy, and domain mapping. Survey accepts their normalized proposals through the same framework-neutral interfaces and review path; it does not acquire credentials or depend on an AI runtime.

When you build an `authorized-action` authorizing block outside the workbench, pair `buildAuthorizedActionAuthorizing` with `buildPromptRef({ module, component, version?, scheme? })` — `buildPromptRef` formats a well-formed, versioned `promptRef` (bare `"review-workbench/decision-card@v1"` or scheme-prefixed `"survey://<module>/<component>@v1"`) that `buildAuthorizedActionAuthorizing` accepts directly, instead of hand-formatting the string.

## Review lifecycle

`ReviewOutcome.resolution` optionally records the terminal result of a review
round while preserving compatibility with outcomes that infer their result from
`status`. The explicit `could_not_confirm` resolution requires a non-empty
`resolutionReason`; `attemptEvidenceIds` may record evidence of what the reviewer
tried. It is valid only with an unchanged `proposed` or `assumed` status.
Other explicit resolutions must also agree with status: accepted is
verified/assumed, rejected is rejected, and held is any non-rejected retained
posture.

Could-not-confirm is terminal for the review round, but it is not rejection,
verification, or escalation. Surface projection stays quiet: the claim keeps its
pre-review status, no attempt evidence is projected, and no review-derived
freshness, validity, gap, or verification posture is added. The outcome remains
available in Survey review records, canonical review proof v3, and the distinct
`learning.could-not-confirm` producer-learning signal.

## Review Workbench embed

**Web component** (shadow DOM, no framework required):

```html
<link rel="stylesheet" href="@kontourai/survey/review-workbench/standalone.css">
<survey-review-workbench theme="survey" color-scheme="dark"></survey-review-workbench>
<script type="module">
  import "@kontourai/survey/review-workbench/element";
  const el = document.querySelector("survey-review-workbench");
  el.session = reviewQueueSession;
  el.presentationAdapter = myAdapter;
</script>
```

**Direct mount** into an existing element:

```ts
import {
  mountReviewWorkbench,
  type ReviewPresentationAdapter,
} from "@kontourai/survey/review-workbench";
import "@kontourai/survey/review-workbench.css";

const presentationAdapter = {
  labelForTarget: (target) => target === "registrationStatus"
    ? "Registration status"
    : undefined,
  linkForReviewItem: (item) => ({ href: `/review/${item.metadata.name}` }),
} satisfies ReviewPresentationAdapter;

mountReviewWorkbench(element, reviewQueueSession, { presentationAdapter });
```

The embedded stylesheet is scoped to `.survey-workbench-embed` and bundles Console Kit tokens, so it will not rewrite the host application's `body` or `:root` styles. Mount into:

```html
<div class="survey-workbench-embed theme-survey"></div>
```

`@kontourai/survey/review-workbench/standalone.css` exists for pages Survey owns entirely.

### Theme it as your own brand

The bundled stylesheet carries a literal default for every `--k-*` token on the
embed element itself, so the workbench looks right in a host that declares no
tokens at all. To re-brand it, declare the tokens you want **on the embed
element** — a rule matching `.survey-workbench-embed`, or an inline `style`
attribute. Tokens you leave alone keep their default, so you only declare what
you re-brand.

Setting `--k-*` on an *ancestor* of the embed does not reach it: the embed
declares those tokens on its own root, and a declaration on an element always
beats a value inherited from an ancestor. If you want the embed to inherit a
token layer your page already publishes at `:root`, use the web component
(`<survey-review-workbench>`) — its defaults sit on the shadow `:host`, so host
tokens and inline styles propagate through — or restate the tokens on the embed
element as below.

```css
/* Your app's own palette drives the workbench — no Kontour branding. */
.survey-workbench-embed {
  --k-brand: var(--brand-accent);
  --k-bg: var(--paper);
  --k-panel: var(--paper-2);
  --k-text: var(--ink);
  --k-line: var(--hairline);
  --k-font-ui: var(--font-body);
  --k-radius-md: 2px;
}
```

The full token list is in the [Consumer Integration Guide](docs/consumer-integration-guide.md).
Prefer this over the built-in `theme-survey`/`theme-console`/… presets when the
host app should read as itself.

## Review MCP

Drive review-queue decisions from any MCP host. The official server runtime
automatically supports MCP 2026-07-28 discovery and existing legacy clients:

```sh
npx survey-review-mcp --session path/to/session.json
```

Three tools: `survey_review_queue` (queue state), `survey_review_item` (item detail), and `survey_review_decide` (record a decision). Each queue and item call includes an embedded, fully self-contained review card with Accept / Hold / Reject / Could not confirm actions. See [docs/review-mcp.md](docs/review-mcp.md).

The embedded card is a fixed-size resource (a preferred frame of 420×560) that follows the host's light/dark `prefers-color-scheme`; it does not resize its layout at other breakpoints. For a card that adapts its layout to available width — stacking the current → proposed diff vertically and growing tap targets via CSS container queries below 620px/480px, with a viewport `@media` fallback — embed the [Review Workbench](#review-workbench-embed) directly instead.

Server code persisting browser-submitted review events should use `persistReviewSessionEvents`, then `deriveReviewSessionApplyResultForSnapshot` (or the composed `deriveServerReviewSessionApplyResult` 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.

The [Consumer Integration Guide](docs/consumer-integration-guide.md) covers the full path from `ReviewItem` construction through persisted review events, exported results, Surface projection, and the full `--k-*` theming token list. Test-covered examples are under [`examples/review-workbench/`](examples/review-workbench/). To run the standalone demo locally, see [Review Workbench Prototype](docs/review-workbench-prototype.md).

Portable extraction imports can also attach a source-linked, read-only inspector
to the same workbench. It verifies resolved artifact identity before showing
exact highlights, keeps unresolved material visibly non-grounded, and exports
with source text and excerpts redacted by default. Review queues and inspector
candidates use bounded, searchable paging so large validated result sets remain
navigable without changing canonical review events or exports. See
[Extraction Envelope Import](docs/extraction-envelope-import.md#source-linked-inspection).


## Standalone review console

Spawn a loopback browser dashboard backed directly by a session file:

```sh
npx survey-review-console --session path/to/session.json [--port 4243]
```

Opens the full Review Workbench in your browser. Every decision you make is persisted back to the session file atomically via the same `deriveServerReviewSessionApplyResult` validation the MCP server uses. An SSE stream watches the file for changes, so the browser and any concurrent MCP agent converge live on the same event queue — no page refresh required. See [docs/review-console.md](docs/review-console.md).

## Where Survey fits

Kontour AI shows the work behind AI. Survey is the producer-side primitive:

| Product | Owns |
| --- | --- |
| **Survey** | Producer evidence: source → extraction → candidate → review → claim |
| **[Surface](https://kontourai.io/surface)** | Portable trust state: claims, evidence, policies, trust snapshots |
| **[Flow](https://kontourai.io/flow)** | Process transparency: steps, gates, transitions, runs, exceptions |
| **[Veritas](https://kontourai.io/veritas)** | Code/change transparency: repo standards, merge readiness |
| **[Flow Agents](https://kontourai.io/flow-agents)** | Agent-facing distribution: skills, kits, runtime adapters, hooks |

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`.

## Documentation

| Guide | What it covers |
| --- | --- |
| [Record Contracts](docs/record-contracts.md) | every record shape in the chain: raw sources, extractions, candidates, reviews, proofs, resolutions, comfort-zone flags |
| [Adversarial Passes & Learning](docs/adversarial-and-learning.md) | per-round adversarial review records and learning projections, and the Flow boundary |
| [Consumer Integration Guide](docs/consumer-integration-guide.md) | the full consumer path: ReviewItem queues, the workbench, persisted events, the server apply boundary |
| [Review Resource Contract](docs/review-resource-contract.md) | the Kontour Resource shapes for review sessions and events |
| [Source-Authority Review Pattern](docs/source-authority-review-pattern.md) | record discipline for sources the producer treats as authoritative |
| [Review Workbench Prototype](docs/review-workbench-prototype.md) | running the example-backed standalone demo locally |
| [Review Console](docs/review-console.md) | standalone local dashboard: browser + MCP agent share the session file |
| [Review MCP](docs/review-mcp.md) | MCP server for agent-driven review-queue decisions |
| [Releasing](docs/RELEASING.md) | release prep and publish flow |

## Product boundary

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 evidence chain contract before the records enter Surface.

## Contributor checks

Install the repo-owned Git hooks once per clone:

```bash
npm run setup:repo-hooks
```

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:

```bash
npm run validate:repo-hooks
npm run verify
```

The committed pre-push hook runs both commands from the repo root.

## License

[Apache-2.0](LICENSE) © Kontour AI
