@kontourai/survey 0.4.8 → 0.4.9

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
@@ -87,6 +87,16 @@ application's `body` or `:root` styles. Hosts should mount into an element like:
87
87
  The package also exposes `@kontourai/survey/review-workbench/standalone.css` for
88
88
  the standalone demo page. Use that only when Survey owns the whole page.
89
89
 
90
+ For the full consumer path from `ReviewItem` construction through persisted
91
+ review events, exported results, and optional Surface projection, see
92
+ [`docs/consumer-integration-guide.md`](docs/consumer-integration-guide.md).
93
+ That guide also covers the server-side apply boundary: producers should derive
94
+ write results from reviewed snapshots plus persisted events, not from
95
+ browser-computed decisions or exported result payloads.
96
+ For the current decision on why Survey is not adding a generic review adapter
97
+ builder yet, see
98
+ [`docs/consumer-adapter-abstraction-assessment.md`](docs/consumer-adapter-abstraction-assessment.md).
99
+
90
100
  ## Contributor checks
91
101
 
92
102
  Install the repo-owned Git hooks once per clone:
@@ -9,6 +9,15 @@ export interface ReviewWorkbenchSessionExport {
9
9
  readonly decisions: readonly ReviewDecision[];
10
10
  readonly results: readonly ReviewWorkbenchResult[];
11
11
  }
12
+ export type ReviewSessionReplayIssueCode = "unknown-active-item" | "unknown-review-item" | "unknown-candidate";
13
+ export interface ReviewSessionReplayIssue {
14
+ readonly code: ReviewSessionReplayIssueCode;
15
+ readonly eventName: string;
16
+ readonly sequence: number;
17
+ readonly reviewItemName?: string;
18
+ readonly candidateId?: string;
19
+ readonly message: string;
20
+ }
12
21
  export interface ReviewSessionEventStore {
13
22
  load(session: ReviewQueueSessionState): readonly ReviewSessionEvent[] | undefined;
14
23
  save(session: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): void;
@@ -54,6 +63,9 @@ export interface ReviewWorkbenchResult {
54
63
  export declare function buildReviewDecisionsFromSession(session: ReviewQueueSessionState): ReviewDecision[];
55
64
  export declare function buildReviewWorkbenchResultsFromSession(session: ReviewQueueSessionState): ReviewWorkbenchResult[];
56
65
  export declare function buildReviewWorkbenchSessionExport(session: ReviewQueueSessionState, events?: readonly ReviewSessionEvent[]): ReviewWorkbenchSessionExport;
66
+ export declare function validateReviewSessionEventsForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewSessionReplayIssue[];
67
+ export declare function replayReviewSessionEventsForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewQueueSessionState;
68
+ export declare function buildReviewWorkbenchSessionExportForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewWorkbenchSessionExport;
57
69
  export declare function createInMemoryReviewSessionEventStore(initialEvents?: readonly ReviewSessionEvent[]): ReviewSessionEventStore & {
58
70
  events(): readonly ReviewSessionEvent[];
59
71
  };
@@ -89,6 +89,60 @@ export function buildReviewWorkbenchSessionExport(session, events = buildReviewS
89
89
  results: buildReviewWorkbenchResultsFromSession(session),
90
90
  };
91
91
  }
92
+ export function validateReviewSessionEventsForSnapshot(snapshot, events) {
93
+ const itemsByName = new Map(snapshot.items.map((item) => [item.metadata.name, item]));
94
+ return events.flatMap((event) => {
95
+ const issues = [];
96
+ const activeItemName = event.spec.activeItemName;
97
+ const reviewItemName = event.spec.reviewItemName;
98
+ const itemName = reviewItemName ?? activeItemName;
99
+ const eventRef = {
100
+ eventName: event.metadata.name,
101
+ sequence: event.spec.sequence,
102
+ };
103
+ if (activeItemName && !itemsByName.has(activeItemName)) {
104
+ issues.push({
105
+ ...eventRef,
106
+ code: "unknown-active-item",
107
+ reviewItemName: activeItemName,
108
+ message: `ReviewSessionEvent ${event.metadata.name} references active item ${activeItemName}, but the supplied session snapshot does not contain that ReviewItem.`,
109
+ });
110
+ }
111
+ if (reviewItemName && !itemsByName.has(reviewItemName)) {
112
+ issues.push({
113
+ ...eventRef,
114
+ code: "unknown-review-item",
115
+ reviewItemName,
116
+ message: `ReviewSessionEvent ${event.metadata.name} references review item ${reviewItemName}, but the supplied session snapshot does not contain that ReviewItem.`,
117
+ });
118
+ }
119
+ if (event.spec.candidateId && itemName && itemsByName.has(itemName)) {
120
+ const item = itemsByName.get(itemName);
121
+ const hasCandidate = item?.spec.candidates.some((candidate) => candidate.id === event.spec.candidateId);
122
+ if (!hasCandidate) {
123
+ issues.push({
124
+ ...eventRef,
125
+ code: "unknown-candidate",
126
+ reviewItemName: itemName,
127
+ candidateId: event.spec.candidateId,
128
+ message: `ReviewSessionEvent ${event.metadata.name} references candidate ${event.spec.candidateId}, but ReviewItem ${itemName} in the supplied session snapshot does not contain that candidate.`,
129
+ });
130
+ }
131
+ }
132
+ return issues;
133
+ });
134
+ }
135
+ export function replayReviewSessionEventsForSnapshot(snapshot, events) {
136
+ const issues = validateReviewSessionEventsForSnapshot(snapshot, events);
137
+ if (issues.length > 0) {
138
+ throw new Error(`Review session events do not match the supplied session snapshot: ${issues.map((issue) => issue.message).join(" ")}`);
139
+ }
140
+ return replayReviewSessionEvents(snapshot, events);
141
+ }
142
+ export function buildReviewWorkbenchSessionExportForSnapshot(snapshot, events) {
143
+ const replayedSession = replayReviewSessionEventsForSnapshot(snapshot, events);
144
+ return buildReviewWorkbenchSessionExport(replayedSession, events);
145
+ }
92
146
  export function createInMemoryReviewSessionEventStore(initialEvents = []) {
93
147
  let savedEvents = [...initialEvents];
94
148
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontourai/survey",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
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",