@kontourai/survey 6.1.0 → 7.0.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/dist/example-data/public-directory-review-resource.d.ts +3 -3
- package/dist/src/canonical-reviewed-trust-input.js +30 -1
- package/dist/src/console/review-console-server.js +22 -4
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +1 -1
- package/dist/src/mcp/review-mcp.js +139 -36
- package/dist/src/review-resource.d.ts +7 -0
- package/dist/src/review-session-file.d.ts +7 -0
- package/dist/src/review-workbench/audit-rows.d.ts +1 -1
- package/dist/src/review-workbench/audit-rows.js +1 -0
- package/dist/src/review-workbench/queue-binding.d.ts +45 -2
- package/dist/src/review-workbench/queue-binding.js +45 -0
- package/dist/src/review-workbench/review-presentation.d.ts +23 -0
- package/dist/src/review-workbench/review-presentation.js +51 -10
- package/dist/src/review-workbench/review-queue-session.d.ts +47 -4
- package/dist/src/review-workbench/review-queue-session.js +73 -8
- package/dist/src/review-workbench/review-session-replay.d.ts +11 -1
- package/dist/src/review-workbench/review-session-replay.js +35 -1
- package/dist/src/review-workbench/review-workbench-css.generated.js +94 -0
- package/dist/src/review-workbench/review-workbench-element.d.ts +10 -0
- package/dist/src/review-workbench/review-workbench-element.js +21 -3
- package/dist/src/review-workbench/review-workbench.css +94 -0
- package/dist/src/review-workbench/review-workbench.d.ts +28 -8
- package/dist/src/review-workbench/review-workbench.js +157 -62
- package/dist/src/review-workbench/review-workbench.standalone.css +94 -0
- package/dist/src/review-workbench/server-review-session.d.ts +18 -0
- package/dist/src/review-workbench/server-review-session.js +20 -2
- package/dist/src/surface-reviewed-extraction.d.ts +23 -0
- package/dist/src/surface-reviewed-extraction.js +64 -1
- package/dist/src/to-surface.js +32 -3
- package/package.json +1 -1
|
@@ -3,7 +3,9 @@ import { type ReviewDecisionModeIssue } from "./producer-decision-mode.js";
|
|
|
3
3
|
import { type ReviewSessionReplayIssue, type ReviewSessionReplayWarning } from "./review-session-replay.js";
|
|
4
4
|
import type { ReviewDecision, ReviewSessionEvent } from "../review-resource.js";
|
|
5
5
|
import { type ReviewQueueBinding } from "./queue-binding.js";
|
|
6
|
+
import type { ExtractionEnvelopeImport, ExtractionEnvelopeImportResult } from "../extraction-envelope.js";
|
|
6
7
|
import { type ReviewQueueSessionState } from "./review-queue-session.js";
|
|
8
|
+
import type { ReviewPresentationAdapter } from "./review-presentation.js";
|
|
7
9
|
export interface ServerReviewSessionRecord {
|
|
8
10
|
readonly sessionName: string;
|
|
9
11
|
readonly snapshot: ReviewQueueSessionState;
|
|
@@ -57,6 +59,8 @@ export interface DeriveServerReviewSessionApplyResultOptions {
|
|
|
57
59
|
readonly currentSnapshot?: ReviewQueueSessionState;
|
|
58
60
|
readonly currentEventCount?: number;
|
|
59
61
|
readonly requiredResolvedItems?: ReviewSessionApplyResolutionRequirement;
|
|
62
|
+
/** The adapter the reviewer's card used; the recorded decision prompts render the target label and values with it. */
|
|
63
|
+
readonly presentationAdapter?: ReviewPresentationAdapter;
|
|
60
64
|
/**
|
|
61
65
|
* The queue binding taken when this session opened (see
|
|
62
66
|
* ./queue-binding.ts). When present, the apply derivation refuses unless the
|
|
@@ -70,6 +74,15 @@ export interface DeriveServerReviewSessionApplyResultOptions {
|
|
|
70
74
|
* consumer's storage, not be recomputed at call time.
|
|
71
75
|
*/
|
|
72
76
|
readonly binding?: ReviewQueueBinding;
|
|
77
|
+
/**
|
|
78
|
+
* The extraction import record the snapshot's items were built from, as
|
|
79
|
+
* stored beside the session. When present, the derivation refuses (throws
|
|
80
|
+
* {@link UnattestedExtractionQueueError}) unless the record's snapshot items
|
|
81
|
+
* match it (see `validateReviewQueueAgainstExtractionImport`). When absent and
|
|
82
|
+
* the snapshot's items carry the extraction-envelope binding, the result
|
|
83
|
+
* carries an `unverified-extraction-queue` warning: nothing checked them.
|
|
84
|
+
*/
|
|
85
|
+
readonly extractionImport?: ExtractionEnvelopeImport | ExtractionEnvelopeImportResult;
|
|
73
86
|
}
|
|
74
87
|
export declare function createServerReviewSessionRecord(options: CreateServerReviewSessionRecordOptions): ServerReviewSessionRecord;
|
|
75
88
|
export declare function hashReviewSessionSnapshot(snapshot: ReviewQueueSessionState): string;
|
|
@@ -104,6 +117,8 @@ export interface ApplyReviewSessionOptions<TAction = never> {
|
|
|
104
117
|
readonly currentSnapshot?: ReviewQueueSessionState;
|
|
105
118
|
readonly currentEventCount?: number;
|
|
106
119
|
readonly requiredResolvedItems?: ReviewSessionApplyResolutionRequirement;
|
|
120
|
+
/** See {@link DeriveServerReviewSessionApplyResultOptions.extractionImport}. */
|
|
121
|
+
readonly extractionImport?: ExtractionEnvelopeImport | ExtractionEnvelopeImportResult;
|
|
107
122
|
/** When true, each result is checked against its item's producerPolicy.decisionMode. */
|
|
108
123
|
readonly enforceProducerPolicy?: boolean;
|
|
109
124
|
/** When provided, results are mapped to product apply actions in the same call. */
|
|
@@ -115,6 +130,9 @@ export type ApplyReviewSessionIssue = ReviewSessionApplyIssue | {
|
|
|
115
130
|
} | {
|
|
116
131
|
readonly code: "invalid-events";
|
|
117
132
|
readonly message: string;
|
|
133
|
+
} | {
|
|
134
|
+
readonly code: "unattested-extraction-queue";
|
|
135
|
+
readonly message: string;
|
|
118
136
|
} | {
|
|
119
137
|
readonly code: "decision-mode-violation";
|
|
120
138
|
readonly reviewItemName: string;
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { deriveReviewSessionApplyResultForSnapshot, mapReviewWorkbenchResultsToApplyActions, ReviewApplyActionMappingError, } from "./review-workbench.js";
|
|
3
3
|
import { validateReviewDecisionMode, } from "./producer-decision-mode.js";
|
|
4
4
|
import { validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
5
|
-
import { assertReviewQueueBinding } from "./queue-binding.js";
|
|
5
|
+
import { assertReviewQueueBinding, attestReviewQueueExtraction, UnattestedExtractionQueueError, } from "./queue-binding.js";
|
|
6
6
|
import { replayReviewSessionEvents } from "./review-queue-session.js";
|
|
7
7
|
import { canonicalJson } from "./canonical.js";
|
|
8
8
|
export class StaleServerReviewSessionError extends Error {
|
|
@@ -104,11 +104,25 @@ export function deriveServerReviewSessionApplyResult(options) {
|
|
|
104
104
|
assertServerReviewSessionFreshness(options.record, options.currentSnapshot, options.currentEventCount);
|
|
105
105
|
}
|
|
106
106
|
assertServerReviewSessionEvents(options.record, options.events);
|
|
107
|
-
|
|
107
|
+
const attestation = attestReviewQueueExtraction(options.record.snapshot.items, options.extractionImport);
|
|
108
|
+
if (attestation.state === "diverges") {
|
|
109
|
+
throw new UnattestedExtractionQueueError(attestation.issues);
|
|
110
|
+
}
|
|
111
|
+
const derived = deriveReviewSessionApplyResultForSnapshot({
|
|
108
112
|
snapshot: options.record.snapshot,
|
|
109
113
|
events: options.events,
|
|
110
114
|
requiredResolvedItems: options.requiredResolvedItems,
|
|
115
|
+
presentationAdapter: options.presentationAdapter,
|
|
111
116
|
});
|
|
117
|
+
return attestation.state === "unverified"
|
|
118
|
+
? {
|
|
119
|
+
...derived,
|
|
120
|
+
warnings: [
|
|
121
|
+
...(derived.warnings ?? []),
|
|
122
|
+
{ code: "unverified-extraction-queue", itemNames: attestation.itemNames, message: attestation.message },
|
|
123
|
+
],
|
|
124
|
+
}
|
|
125
|
+
: derived;
|
|
112
126
|
}
|
|
113
127
|
function staleIssuesForComparison(comparison) {
|
|
114
128
|
const issues = [];
|
|
@@ -164,9 +178,13 @@ export function applyReviewSession(options) {
|
|
|
164
178
|
currentSnapshot: options.currentSnapshot,
|
|
165
179
|
currentEventCount: options.currentEventCount,
|
|
166
180
|
requiredResolvedItems: options.requiredResolvedItems,
|
|
181
|
+
extractionImport: options.extractionImport,
|
|
167
182
|
});
|
|
168
183
|
}
|
|
169
184
|
catch (error) {
|
|
185
|
+
if (error instanceof UnattestedExtractionQueueError) {
|
|
186
|
+
return { ok: false, issues: [{ code: "unattested-extraction-queue", message: error.message }], decisions: [], results: [], actions: [] };
|
|
187
|
+
}
|
|
170
188
|
if (error instanceof StaleServerReviewSessionError) {
|
|
171
189
|
return { ok: false, issues: [{ code: "stale-session", message: error.message }], decisions: [], results: [], actions: [] };
|
|
172
190
|
}
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import type { SurveyExtractionEnvelopeImport, SurveyExtractionReviewDecision, SurveyExtractionReviewItem } from "@kontourai/surface";
|
|
2
2
|
import type { ExtractionEnvelopeImport } from "./extraction-envelope.js";
|
|
3
3
|
import type { ReviewDecision, ReviewItem } from "./review-resource.js";
|
|
4
|
+
/**
|
|
5
|
+
* The version of the `@kontourai/surface` package whose entry point
|
|
6
|
+
* `resolveEntry` returns. Surface does not export its package.json, so this
|
|
7
|
+
* walks up from the entry to the nearest package.json named
|
|
8
|
+
* `@kontourai/surface`; other package.json files on the way (a nested
|
|
9
|
+
* `{ "type": "module" }`, say) are skipped. Throws a Survey error when the
|
|
10
|
+
* entry cannot be resolved, as in a bundle without `import.meta.resolve`, or
|
|
11
|
+
* no such package.json exists.
|
|
12
|
+
*/
|
|
13
|
+
export declare function surfaceVersionFromEntry(resolveEntry: () => string): string;
|
|
14
|
+
/** Whether a Surface version accepts proposals without confidence. An unparseable version does not. */
|
|
15
|
+
export declare function surfaceAcceptsAbsentConfidence(version: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The record, typed for Surface's reviewed-extraction profile. A proposal
|
|
18
|
+
* without confidence passes through without one; no number is substituted.
|
|
19
|
+
* On a Surface older than 4.1.0, which rejects such a proposal, the record is
|
|
20
|
+
* refused by name instead.
|
|
21
|
+
*
|
|
22
|
+
* The version checked is that of the Surface copy Survey itself resolves. A
|
|
23
|
+
* consumer that projects with a different copy (for example Surface 4.0.0 of
|
|
24
|
+
* its own next to Survey's 4.2.0) gets Surface's own refusal instead of this
|
|
25
|
+
* one; dedupe Surface so that one copy is installed.
|
|
26
|
+
*/
|
|
4
27
|
export declare function toSurfaceReviewedExtractionImport(record: ExtractionEnvelopeImport): SurveyExtractionEnvelopeImport;
|
|
5
28
|
export declare function toSurfaceReviewedExtractionItem(item: ReviewItem): SurveyExtractionReviewItem;
|
|
6
29
|
export declare function toSurfaceReviewedExtractionDecision(decision: ReviewDecision): SurveyExtractionReviewDecision;
|
|
@@ -1,7 +1,70 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/** The first Surface release whose reviewed-extraction profile accepts a proposal without confidence. */
|
|
5
|
+
const ABSENT_CONFIDENCE_SINCE = [4, 1];
|
|
6
|
+
let resolvedSurfaceVersion;
|
|
7
|
+
const UNKNOWN_SURFACE_VERSION = "Cannot determine the installed @kontourai/surface version; refusing to export proposals without confidence.";
|
|
8
|
+
/**
|
|
9
|
+
* The version of the `@kontourai/surface` package whose entry point
|
|
10
|
+
* `resolveEntry` returns. Surface does not export its package.json, so this
|
|
11
|
+
* walks up from the entry to the nearest package.json named
|
|
12
|
+
* `@kontourai/surface`; other package.json files on the way (a nested
|
|
13
|
+
* `{ "type": "module" }`, say) are skipped. Throws a Survey error when the
|
|
14
|
+
* entry cannot be resolved, as in a bundle without `import.meta.resolve`, or
|
|
15
|
+
* no such package.json exists.
|
|
16
|
+
*/
|
|
17
|
+
export function surfaceVersionFromEntry(resolveEntry) {
|
|
18
|
+
let dir;
|
|
19
|
+
try {
|
|
20
|
+
dir = dirname(fileURLToPath(resolveEntry()));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw new Error(UNKNOWN_SURFACE_VERSION, { cause: error });
|
|
24
|
+
}
|
|
25
|
+
for (;;) {
|
|
26
|
+
try {
|
|
27
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
28
|
+
if (pkg.name === "@kontourai/surface" && typeof pkg.version === "string")
|
|
29
|
+
return pkg.version;
|
|
30
|
+
}
|
|
31
|
+
catch { /* no readable package.json here; keep walking up */ }
|
|
32
|
+
const parent = dirname(dir);
|
|
33
|
+
if (parent === dir)
|
|
34
|
+
throw new Error(UNKNOWN_SURFACE_VERSION);
|
|
35
|
+
dir = parent;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** The version of the `@kontourai/surface` copy this module resolves, cached once found. */
|
|
39
|
+
function installedSurfaceVersion() {
|
|
40
|
+
return resolvedSurfaceVersion ??= surfaceVersionFromEntry(() => import.meta.resolve("@kontourai/surface"));
|
|
41
|
+
}
|
|
42
|
+
/** Whether a Surface version accepts proposals without confidence. An unparseable version does not. */
|
|
43
|
+
export function surfaceAcceptsAbsentConfidence(version) {
|
|
44
|
+
const match = /^(\d+)\.(\d+)\.\d+/.exec(version);
|
|
45
|
+
if (!match)
|
|
46
|
+
return false;
|
|
47
|
+
const [major, minor] = [Number(match[1]), Number(match[2])];
|
|
48
|
+
return major > ABSENT_CONFIDENCE_SINCE[0] || (major === ABSENT_CONFIDENCE_SINCE[0] && minor >= ABSENT_CONFIDENCE_SINCE[1]);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The record, typed for Surface's reviewed-extraction profile. A proposal
|
|
52
|
+
* without confidence passes through without one; no number is substituted.
|
|
53
|
+
* On a Surface older than 4.1.0, which rejects such a proposal, the record is
|
|
54
|
+
* refused by name instead.
|
|
55
|
+
*
|
|
56
|
+
* The version checked is that of the Surface copy Survey itself resolves. A
|
|
57
|
+
* consumer that projects with a different copy (for example Surface 4.0.0 of
|
|
58
|
+
* its own next to Survey's 4.2.0) gets Surface's own refusal instead of this
|
|
59
|
+
* one; dedupe Surface so that one copy is installed.
|
|
60
|
+
*/
|
|
1
61
|
export function toSurfaceReviewedExtractionImport(record) {
|
|
2
62
|
const unreported = record.spec.envelope.result.proposals.findIndex((proposal) => proposal.confidence === undefined);
|
|
3
63
|
if (unreported !== -1) {
|
|
4
|
-
|
|
64
|
+
const version = installedSurfaceVersion();
|
|
65
|
+
if (!surfaceAcceptsAbsentConfidence(version)) {
|
|
66
|
+
throw new Error(`@kontourai/surface ${version} requires a proposer confidence on every proposal (4.1.0 and later do not); proposal ${unreported} of ${record.metadata.name} reports none.`);
|
|
67
|
+
}
|
|
5
68
|
}
|
|
6
69
|
return record;
|
|
7
70
|
}
|
package/dist/src/to-surface.js
CHANGED
|
@@ -7,6 +7,16 @@ import { canonicalJson } from "./review-workbench/canonical.js";
|
|
|
7
7
|
/** Minimum labeled samples a calibration group needs before its empirical
|
|
8
8
|
* accuracy is emitted as a `conclusionConfidence.value`. */
|
|
9
9
|
const DEFAULT_CALIBRATION_MIN_SAMPLES = 20;
|
|
10
|
+
/**
|
|
11
|
+
* The evidence's own `confidence` replaces any a metadata spread carried. When
|
|
12
|
+
* the extraction reported none, the key is removed rather than set to
|
|
13
|
+
* undefined, so absence is absence in memory as well as on the wire.
|
|
14
|
+
*/
|
|
15
|
+
function withoutUndefinedConfidence(metadata) {
|
|
16
|
+
if (metadata.confidence === undefined)
|
|
17
|
+
delete metadata.confidence;
|
|
18
|
+
return metadata;
|
|
19
|
+
}
|
|
10
20
|
/**
|
|
11
21
|
* Thrown when a claim's trusted status or value does not agree with the review
|
|
12
22
|
* outcome it cites, or when the governing review cannot be chosen.
|
|
@@ -80,7 +90,8 @@ export function buildSurveyTrustBundle(input, options = {}) {
|
|
|
80
90
|
derivationEdges: projection.derivationEdges,
|
|
81
91
|
confidenceBasis: {
|
|
82
92
|
sourceQuality: "moderate",
|
|
83
|
-
|
|
93
|
+
// An absent confidence stays absent; no key, no default.
|
|
94
|
+
...((candidate.confidence ?? extraction.confidence) !== undefined ? { extractionConfidence: candidate.confidence ?? extraction.confidence } : {}),
|
|
84
95
|
// An auto-accept policy checked nothing but the proposer's own
|
|
85
96
|
// confidence, so its claims carry system authority and weak evidence;
|
|
86
97
|
// only a human review earns operator authority.
|
|
@@ -168,7 +179,7 @@ export function buildSurveyTrustBundle(input, options = {}) {
|
|
|
168
179
|
observedAt: rawSource.observedAt,
|
|
169
180
|
collectedBy: projection.collectedBy,
|
|
170
181
|
integrityRef: rawSource.checksum,
|
|
171
|
-
metadata: {
|
|
182
|
+
metadata: withoutUndefinedConfidence({
|
|
172
183
|
...rawSource.metadata,
|
|
173
184
|
...extraction.metadata,
|
|
174
185
|
...candidate.metadata,
|
|
@@ -177,7 +188,7 @@ export function buildSurveyTrustBundle(input, options = {}) {
|
|
|
177
188
|
locatorScheme: rawSource.locatorScheme,
|
|
178
189
|
...(rawSource.resolution ? { provenanceResolution: rawSource.resolution } : {}),
|
|
179
190
|
confidence: candidate.confidence ?? extraction.confidence,
|
|
180
|
-
},
|
|
191
|
+
}),
|
|
181
192
|
});
|
|
182
193
|
const rationale = projectionReview?.rationale ?? candidateSet.rationale;
|
|
183
194
|
events.push({
|
|
@@ -606,8 +617,26 @@ function buildSurveyMetadata(input) {
|
|
|
606
617
|
},
|
|
607
618
|
}
|
|
608
619
|
: {}),
|
|
620
|
+
// A claim whose value was chosen from conflicting proposed values lists
|
|
621
|
+
// every value and marks the chosen one, so the claim never reads as if its
|
|
622
|
+
// value had been the only one proposed.
|
|
623
|
+
...(proposedRoleCount(input.candidateSet) > 1
|
|
624
|
+
? {
|
|
625
|
+
candidateSetStatus: input.candidateSet.status,
|
|
626
|
+
candidates: input.candidateSet.candidates.map((candidate) => ({
|
|
627
|
+
candidateId: candidate.id,
|
|
628
|
+
value: candidate.value,
|
|
629
|
+
...(candidate.id === input.candidate.id ? { selected: true } : {}),
|
|
630
|
+
...(candidate.rejectionReason !== undefined ? { rejectionReason: candidate.rejectionReason } : {}),
|
|
631
|
+
})),
|
|
632
|
+
}
|
|
633
|
+
: {}),
|
|
609
634
|
};
|
|
610
635
|
}
|
|
636
|
+
/** How many of a set's candidates carry the `proposed` role (Survey writes it to `metadata.role`). */
|
|
637
|
+
function proposedRoleCount(candidateSet) {
|
|
638
|
+
return candidateSet.candidates.filter((candidate) => candidate.metadata?.role === "proposed").length;
|
|
639
|
+
}
|
|
611
640
|
function statusFor(input) {
|
|
612
641
|
if (input.review)
|
|
613
642
|
return input.review.status;
|
package/package.json
CHANGED