@kontourai/survey 2.5.0 → 4.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/README.md +6 -1
- package/dist/examples/calibrated-auto-accept.d.ts +22 -15
- package/dist/examples/calibrated-auto-accept.js +40 -36
- package/dist/src/agent-utterance.d.ts +87 -11
- package/dist/src/agent-utterance.js +135 -44
- package/dist/src/calibration.d.ts +48 -21
- package/dist/src/calibration.js +72 -33
- package/dist/src/console/review-console-server.d.ts +3 -1
- package/dist/src/console/review-console-server.js +203 -50
- package/dist/src/extraction-envelope.d.ts +22 -0
- package/dist/src/extraction-envelope.js +25 -4
- package/dist/src/index.d.ts +9 -8
- package/dist/src/index.js +2 -2
- package/dist/src/inquiry-mapping.d.ts +15 -1
- package/dist/src/inquiry-mapping.js +10 -2
- package/dist/src/mcp/review-mcp.js +219 -279
- package/dist/src/producer-profile.d.ts +41 -2
- package/dist/src/producer-profile.js +29 -2
- package/dist/src/review-session-file.d.ts +64 -0
- package/dist/src/review-session-file.js +320 -0
- package/dist/src/review-workbench/edited-value.d.ts +70 -0
- package/dist/src/review-workbench/edited-value.js +147 -0
- package/dist/src/review-workbench/review-presentation.d.ts +44 -0
- package/dist/src/review-workbench/review-presentation.js +49 -0
- package/dist/src/review-workbench/review-queue-session.js +8 -1
- package/dist/src/review-workbench/review-session-replay.d.ts +35 -1
- package/dist/src/review-workbench/review-session-replay.js +77 -0
- package/dist/src/review-workbench/review-workbench.d.ts +8 -4
- package/dist/src/review-workbench/review-workbench.js +19 -7
- package/dist/src/review-workbench/server-review-session.d.ts +3 -1
- package/dist/src/review-workbench/server-review-session.js +1 -0
- package/dist/src/reviewed-candidate-resolution.js +13 -7
- package/dist/src/schema-mapping.d.ts +23 -0
- package/dist/src/schema-mapping.js +30 -20
- package/dist/src/to-surface.d.ts +30 -6
- package/dist/src/to-surface.js +196 -18
- package/dist/src/types.d.ts +39 -0
- package/package.json +8 -4
|
@@ -5,18 +5,20 @@
|
|
|
5
5
|
* Routes:
|
|
6
6
|
* GET / HTML shell that mounts the workbench
|
|
7
7
|
* GET /api/session Current session state (snapshot + replayed events)
|
|
8
|
-
* POST /api/events Append review session events
|
|
8
|
+
* POST /api/events Append review session events to the stored log (same
|
|
9
|
+
* validation as MCP server), compare-and-swap on the
|
|
10
|
+
* revision the client last read
|
|
9
11
|
* GET /api/stream SSE stream: emits "update" events when the session file changes
|
|
10
12
|
* GET /api/health Health check
|
|
11
13
|
* GET /dist/* Compiled assets served from the dist tree (traversal-safe)
|
|
12
14
|
*/
|
|
13
15
|
import { createServer } from "node:http";
|
|
14
16
|
import { watch } from "node:fs";
|
|
15
|
-
import { readFile as readFileAsync
|
|
17
|
+
import { readFile as readFileAsync } from "node:fs/promises";
|
|
16
18
|
import { resolve, join, dirname, extname, normalize, sep } from "node:path";
|
|
17
19
|
import { fileURLToPath } from "node:url";
|
|
18
|
-
import { defaultReviewSessionName, } from "../review-workbench/review-workbench.js";
|
|
19
20
|
import { createServerReviewSessionRecord, currentSessionState, deriveServerReviewSessionApplyResult, } from "../review-workbench/server-review-session.js";
|
|
21
|
+
import { appendReviewSessionEvents, readReviewSessionFile, reviewSessionRevision, storedReviewSessionName, updateReviewSessionFile, } from "../review-session-file.js";
|
|
20
22
|
// ---------------------------------------------------------------------------
|
|
21
23
|
// Constants
|
|
22
24
|
// ---------------------------------------------------------------------------
|
|
@@ -55,13 +57,7 @@ function distRoot() {
|
|
|
55
57
|
// Session file helpers
|
|
56
58
|
// ---------------------------------------------------------------------------
|
|
57
59
|
async function readSession(path) {
|
|
58
|
-
|
|
59
|
-
return JSON.parse(raw);
|
|
60
|
-
}
|
|
61
|
-
async function writeSessionAtomic(path, content) {
|
|
62
|
-
const tmp = `${path}.tmp`;
|
|
63
|
-
await writeFileAsync(tmp, JSON.stringify(content, null, 2), "utf8");
|
|
64
|
-
await renameAsync(tmp, path);
|
|
60
|
+
return readReviewSessionFile(path);
|
|
65
61
|
}
|
|
66
62
|
// ---------------------------------------------------------------------------
|
|
67
63
|
// SSE broadcaster
|
|
@@ -271,6 +267,14 @@ body {
|
|
|
271
267
|
.connection-dot.disconnected {
|
|
272
268
|
background: var(--k-negative, #ff6f6f);
|
|
273
269
|
}
|
|
270
|
+
.console-save-status {
|
|
271
|
+
padding: 8px 16px;
|
|
272
|
+
border-bottom: 1px solid var(--k-negative, #ff6f6f);
|
|
273
|
+
background: var(--k-panel-raised, #16202d);
|
|
274
|
+
color: var(--k-text, #eef3f8);
|
|
275
|
+
font-size: 13px;
|
|
276
|
+
}
|
|
277
|
+
.console-save-status[hidden] { display: none; }
|
|
274
278
|
#review-workbench {
|
|
275
279
|
flex: 1;
|
|
276
280
|
min-height: 0;
|
|
@@ -309,31 +313,92 @@ body {
|
|
|
309
313
|
</button>
|
|
310
314
|
</div>
|
|
311
315
|
</header>
|
|
316
|
+
<div id="console-save-status" class="console-save-status" role="alert" data-testid="console-save-status" hidden></div>
|
|
312
317
|
<main id="review-workbench" class="workbench survey-workbench-embed" data-testid="review-workbench"></main>
|
|
313
318
|
<script type="module">
|
|
314
319
|
import { mountReviewWorkbench, replayReviewSessionEvents, defaultReviewSessionName, buildReviewSessionEvents } from "${workbenchJsPath}";
|
|
315
320
|
|
|
316
321
|
// ---- persistence adapter: POST events to /api/events ----
|
|
317
|
-
|
|
318
|
-
|
|
322
|
+
// One store per mount, bound to the server revision the mount was built from.
|
|
323
|
+
// Each save sends only the workbench events of this mount that are not stored
|
|
324
|
+
// yet; the server appends them to the stored log, so reversals and note
|
|
325
|
+
// changes stay on record and decisions another writer stored (for example the
|
|
326
|
+
// MCP server) are kept. Saves are serialized so each one carries the revision
|
|
327
|
+
// the previous save produced. A save the server refuses (409 conflict or any
|
|
328
|
+
// other failure) is never kept as local truth: the console re-fetches the
|
|
329
|
+
// stored session, re-mounts from it and tells the reviewer once.
|
|
330
|
+
let saveChain = Promise.resolve();
|
|
331
|
+
|
|
332
|
+
function showSaveStatus(message) {
|
|
333
|
+
const el = document.getElementById("console-save-status");
|
|
334
|
+
if (!el) return;
|
|
335
|
+
if (message) {
|
|
336
|
+
el.textContent = message;
|
|
337
|
+
el.hidden = false;
|
|
338
|
+
} else {
|
|
339
|
+
el.textContent = "";
|
|
340
|
+
el.hidden = true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function createConsoleEventStore(baseRevision) {
|
|
345
|
+
let revision = baseRevision;
|
|
346
|
+
// Local events of this mount already appended to the stored log.
|
|
347
|
+
let persistedCount = 0;
|
|
348
|
+
// Set once a save from this mount is refused. Saves still queued from this
|
|
349
|
+
// mount were built on the view the server just refused, and the page has
|
|
350
|
+
// re-mounted from the stored session and told the reviewer: drop them
|
|
351
|
+
// instead of reporting the same conflict again.
|
|
352
|
+
let abandoned = false;
|
|
353
|
+
|
|
354
|
+
async function refused(message) {
|
|
355
|
+
abandoned = true;
|
|
356
|
+
showSaveStatus(message);
|
|
357
|
+
await fetchAndMount();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function persist(localEvents) {
|
|
361
|
+
if (abandoned) return;
|
|
362
|
+
const pending = localEvents.slice(persistedCount);
|
|
363
|
+
if (pending.length === 0) return;
|
|
364
|
+
let res;
|
|
365
|
+
try {
|
|
366
|
+
res = await fetch("/api/events", {
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers: { "Content-Type": "application/json" },
|
|
369
|
+
body: JSON.stringify({ events: pending, baseRevision: revision }),
|
|
370
|
+
});
|
|
371
|
+
} catch (err) {
|
|
372
|
+
console.error("[console] Failed to persist events:", err);
|
|
373
|
+
await refused("Your last change was not saved: the console could not reach the server.");
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (res.ok) {
|
|
377
|
+
const body = await res.json().catch(() => ({}));
|
|
378
|
+
if (typeof body.revision === "string") revision = body.revision;
|
|
379
|
+
persistedCount = localEvents.length;
|
|
380
|
+
showSaveStatus("");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (res.status === 409) {
|
|
384
|
+
await refused("Your last change was not saved because the session changed (another tab, reviewer or agent wrote to it). The console reloaded the current session.");
|
|
385
|
+
} else {
|
|
386
|
+
const body = await res.json().catch(() => ({}));
|
|
387
|
+
await refused("Your last change was not saved (HTTP " + res.status + (body.error ? ": " + body.error : "") + "). The console reloaded the stored session.");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
319
391
|
return {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
headers: { "Content-Type": "application/json" },
|
|
327
|
-
body: JSON.stringify({ events }),
|
|
328
|
-
});
|
|
329
|
-
} catch (err) {
|
|
330
|
-
console.error("[console] Failed to persist events:", err);
|
|
331
|
-
}
|
|
392
|
+
// The mount state is already replayed from the stored log.
|
|
393
|
+
load: () => undefined,
|
|
394
|
+
save: (_session, events) => {
|
|
395
|
+
const localEvents = [...events];
|
|
396
|
+
saveChain = saveChain.then(() => persist(localEvents));
|
|
397
|
+
return saveChain;
|
|
332
398
|
},
|
|
333
399
|
};
|
|
334
400
|
}
|
|
335
401
|
|
|
336
|
-
const eventStore = createConsoleEventStore();
|
|
337
402
|
let activeItemName = null;
|
|
338
403
|
|
|
339
404
|
async function fetchAndMount() {
|
|
@@ -341,7 +406,7 @@ async function fetchAndMount() {
|
|
|
341
406
|
const res = await fetch("/api/session");
|
|
342
407
|
if (!res.ok) throw new Error("Session fetch failed: " + res.status);
|
|
343
408
|
const data = await res.json();
|
|
344
|
-
const { snapshot, events } = data;
|
|
409
|
+
const { snapshot, events, revision } = data;
|
|
345
410
|
const state = events && events.length > 0
|
|
346
411
|
? replayReviewSessionEvents(snapshot, events)
|
|
347
412
|
: snapshot;
|
|
@@ -354,7 +419,7 @@ async function fetchAndMount() {
|
|
|
354
419
|
const root = document.getElementById("review-workbench");
|
|
355
420
|
if (!root) return;
|
|
356
421
|
|
|
357
|
-
mountReviewWorkbench(root, state, { eventStore });
|
|
422
|
+
mountReviewWorkbench(root, state, { eventStore: createConsoleEventStore(revision) });
|
|
358
423
|
} catch (err) {
|
|
359
424
|
console.error("[console] Mount error:", err);
|
|
360
425
|
}
|
|
@@ -451,6 +516,49 @@ async function readJsonBody(req) {
|
|
|
451
516
|
return parsed;
|
|
452
517
|
}
|
|
453
518
|
// ---------------------------------------------------------------------------
|
|
519
|
+
// Request guards (kontourai/survey#281)
|
|
520
|
+
// ---------------------------------------------------------------------------
|
|
521
|
+
function hostnameOf(hostHeader) {
|
|
522
|
+
try {
|
|
523
|
+
return new URL(`http://${hostHeader}`).hostname.replace(/^\[|\]$/g, "");
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* The server binds to loopback only, but a browser page on another site can
|
|
531
|
+
* still reach it through DNS rebinding (Host is the attacker's name) or a
|
|
532
|
+
* cross-origin "simple request" (text/plain body, foreign Origin). Each guard
|
|
533
|
+
* returns an error message, or undefined when the request passes.
|
|
534
|
+
*/
|
|
535
|
+
function hostGuard(req) {
|
|
536
|
+
const host = req.headers.host;
|
|
537
|
+
const hostname = host ? hostnameOf(host) : undefined;
|
|
538
|
+
return hostname && LOOPBACK_HOSTS.has(hostname) ? undefined : "Host must be a loopback address";
|
|
539
|
+
}
|
|
540
|
+
function originGuard(req, port) {
|
|
541
|
+
const origin = req.headers.origin;
|
|
542
|
+
if (origin === undefined)
|
|
543
|
+
return undefined;
|
|
544
|
+
try {
|
|
545
|
+
const parsed = new URL(origin);
|
|
546
|
+
const originPort = parsed.port === "" ? "80" : parsed.port;
|
|
547
|
+
const hostname = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
548
|
+
if (parsed.protocol === "http:" && LOOPBACK_HOSTS.has(hostname) && originPort === String(port)) {
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
// Unparseable (for example "null"): refuse below.
|
|
554
|
+
}
|
|
555
|
+
return "Cross-origin writes are not allowed";
|
|
556
|
+
}
|
|
557
|
+
function contentTypeGuard(req) {
|
|
558
|
+
const mediaType = (req.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
|
|
559
|
+
return mediaType === "application/json" ? undefined : "Content-Type must be application/json";
|
|
560
|
+
}
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
454
562
|
// Public API
|
|
455
563
|
// ---------------------------------------------------------------------------
|
|
456
564
|
export async function startReviewConsoleServer(options) {
|
|
@@ -464,8 +572,14 @@ export async function startReviewConsoleServer(options) {
|
|
|
464
572
|
const stopWatcher = watchSessionFile(sessionPath, () => {
|
|
465
573
|
broadcaster.broadcast("update", JSON.stringify({ ts: Date.now() }));
|
|
466
574
|
});
|
|
575
|
+
let listenPort = 0;
|
|
467
576
|
const server = createServer(async (req, res) => {
|
|
468
577
|
try {
|
|
578
|
+
const hostError = hostGuard(req);
|
|
579
|
+
if (hostError) {
|
|
580
|
+
sendJson(res, 403, { error: hostError });
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
469
583
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
470
584
|
const pathname = url.pathname;
|
|
471
585
|
// ---- Static dist assets ----
|
|
@@ -492,42 +606,80 @@ export async function startReviewConsoleServer(options) {
|
|
|
492
606
|
session: content.session,
|
|
493
607
|
snapshot: content.snapshot,
|
|
494
608
|
events: content.events,
|
|
609
|
+
revision: reviewSessionRevision(content.events),
|
|
495
610
|
state: currentSessionState(content.snapshot, content.events),
|
|
496
611
|
});
|
|
497
612
|
return;
|
|
498
613
|
}
|
|
499
614
|
// ---- Events write (append) ----
|
|
500
615
|
if (pathname === "/api/events" && req.method === "POST") {
|
|
616
|
+
const originError = originGuard(req, listenPort);
|
|
617
|
+
if (originError) {
|
|
618
|
+
sendJson(res, 403, { error: originError });
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const contentTypeError = contentTypeGuard(req);
|
|
622
|
+
if (contentTypeError) {
|
|
623
|
+
sendJson(res, 415, { error: contentTypeError });
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
501
626
|
const body = await readJsonBody(req);
|
|
502
627
|
const incomingEvents = body.events;
|
|
628
|
+
const baseRevision = body.baseRevision;
|
|
503
629
|
if (!Array.isArray(incomingEvents)) {
|
|
504
630
|
sendJson(res, 400, { error: "events must be an array" });
|
|
505
631
|
return;
|
|
506
632
|
}
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
633
|
+
const outcome = await updateReviewSessionFile(sessionPath, (content) => {
|
|
634
|
+
const { snapshot, events: existingEvents } = content;
|
|
635
|
+
const revision = reviewSessionRevision(existingEvents);
|
|
636
|
+
// Compare-and-swap: the appended events express decisions made on
|
|
637
|
+
// the reviewer's view of the log, so they are only safe to append
|
|
638
|
+
// when that view is the log that is stored right now.
|
|
639
|
+
if (typeof baseRevision !== "string") {
|
|
640
|
+
return { result: { status: 428, body: { error: "baseRevision is required: send the revision from GET /api/session", revision, eventCount: existingEvents.length } } };
|
|
641
|
+
}
|
|
642
|
+
if (baseRevision !== revision) {
|
|
643
|
+
return { result: { status: 409, body: { error: "Session changed since it was read; reload and retry", revision, eventCount: existingEvents.length } } };
|
|
644
|
+
}
|
|
645
|
+
if (incomingEvents.length === 0) {
|
|
646
|
+
return { result: { status: 422, body: { error: "events must contain at least one event to append", revision, eventCount: existingEvents.length } } };
|
|
647
|
+
}
|
|
648
|
+
// Append, never replace: the stored log stays a complete record of
|
|
649
|
+
// reviewer intent (reversals and note changes included).
|
|
650
|
+
const nextEvents = appendReviewSessionEvents(content, incomingEvents);
|
|
651
|
+
const record = createServerReviewSessionRecord({
|
|
652
|
+
sessionName: storedReviewSessionName(content),
|
|
653
|
+
snapshot,
|
|
654
|
+
eventCount: existingEvents.length,
|
|
655
|
+
updatedAt: new Date(),
|
|
656
|
+
});
|
|
657
|
+
let issueMessages;
|
|
658
|
+
try {
|
|
659
|
+
const applyResult = deriveServerReviewSessionApplyResult({
|
|
660
|
+
record,
|
|
661
|
+
events: nextEvents,
|
|
662
|
+
requiredResolvedItems: "none",
|
|
663
|
+
});
|
|
664
|
+
issueMessages = applyResult.ok
|
|
665
|
+
? []
|
|
666
|
+
: applyResult.issues.map((issue) => ("message" in issue ? issue.message : String(issue)));
|
|
667
|
+
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
issueMessages = [error instanceof Error ? error.message : String(error)];
|
|
670
|
+
}
|
|
671
|
+
if (issueMessages.length > 0) {
|
|
672
|
+
return { result: { status: 422, body: { error: `Validation failed: ${issueMessages.join("; ")}` } } };
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
next: { session: content.session, snapshot, events: nextEvents },
|
|
676
|
+
result: {
|
|
677
|
+
status: 200,
|
|
678
|
+
body: { ok: true, eventCount: nextEvents.length, revision: reviewSessionRevision(nextEvents) },
|
|
679
|
+
},
|
|
680
|
+
};
|
|
529
681
|
});
|
|
530
|
-
sendJson(res,
|
|
682
|
+
sendJson(res, outcome.status, outcome.body);
|
|
531
683
|
return;
|
|
532
684
|
}
|
|
533
685
|
// ---- Health ----
|
|
@@ -558,6 +710,7 @@ export async function startReviewConsoleServer(options) {
|
|
|
558
710
|
if (!address || typeof address === "string") {
|
|
559
711
|
throw new Error("Unable to determine server address");
|
|
560
712
|
}
|
|
713
|
+
listenPort = address.port;
|
|
561
714
|
const normalizedHost = host === "::1" ? "[::1]" : host;
|
|
562
715
|
const url = `http://${normalizedHost}:${address.port}/`;
|
|
563
716
|
return {
|
|
@@ -17,6 +17,24 @@ export interface PortableExtractionOccurrence {
|
|
|
17
17
|
hintUsed: boolean;
|
|
18
18
|
ambiguous: boolean;
|
|
19
19
|
}
|
|
20
|
+
/** Which model produced one proposal, as the extractor recorded it for that proposal's own request. */
|
|
21
|
+
export interface PortableExtractionProducedBy {
|
|
22
|
+
model: string;
|
|
23
|
+
modelSource: "provider-reported" | "configured";
|
|
24
|
+
/** Content-free digest of the provider request (`sha256:<hex>`). */
|
|
25
|
+
requestDigest: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Deterministic, versioned facts about how a proposal's value relates to the
|
|
29
|
+
* declared schema and to its own excerpt. Annotations only: Survey carries
|
|
30
|
+
* them to review and does not route or block on them.
|
|
31
|
+
*/
|
|
32
|
+
export interface PortableExtractionEvidenceMatch {
|
|
33
|
+
checkerVersion: string;
|
|
34
|
+
schema: "ok" | "type-mismatch" | "enum-mismatch" | "format-invalid";
|
|
35
|
+
valueInExcerpt: "match" | "mismatch" | "not-evaluated" | "not-applicable";
|
|
36
|
+
tokenBoundary?: boolean;
|
|
37
|
+
}
|
|
20
38
|
export interface PortableExtractionProposal {
|
|
21
39
|
fieldPath: string;
|
|
22
40
|
candidateValue: unknown;
|
|
@@ -31,6 +49,8 @@ export interface PortableExtractionProposal {
|
|
|
31
49
|
inferenceType?: "explicit" | "inferred";
|
|
32
50
|
valueType?: ReviewValueType;
|
|
33
51
|
enumValues?: string[];
|
|
52
|
+
producedBy?: PortableExtractionProducedBy;
|
|
53
|
+
evidenceMatch?: PortableExtractionEvidenceMatch;
|
|
34
54
|
}
|
|
35
55
|
export type PortablePreparedArtifactState = {
|
|
36
56
|
status: "available" | "unavailable" | "storage-error";
|
|
@@ -89,10 +109,12 @@ export interface PortableExtractionResultEnvelope {
|
|
|
89
109
|
remainingChunks: number;
|
|
90
110
|
tokenOvershoot?: number;
|
|
91
111
|
};
|
|
112
|
+
/** `code` is the upstream error code, informational only; `kind` stays authoritative. */
|
|
92
113
|
providerFailures?: Array<{
|
|
93
114
|
provider: string;
|
|
94
115
|
kind: "authentication" | "rate-limit" | "timeout" | "invalid-request" | "unavailable" | "unknown";
|
|
95
116
|
retryable: boolean;
|
|
117
|
+
code?: string;
|
|
96
118
|
}>;
|
|
97
119
|
taskDigest?: string;
|
|
98
120
|
exampleDigests?: string[];
|
|
@@ -89,7 +89,9 @@ function buildReviewItem(record, proposal, index) {
|
|
|
89
89
|
target: proposal.fieldPath,
|
|
90
90
|
confidence: proposal.confidence,
|
|
91
91
|
extractor: proposal.extractor,
|
|
92
|
-
|
|
92
|
+
// The proposal's own served model when recorded; in a multi-chunk run
|
|
93
|
+
// `result.model` names only the last chunk's model.
|
|
94
|
+
...(proposal.producedBy ? { model: proposal.producedBy.model } : envelope.result.model ? { model: envelope.result.model } : {}),
|
|
93
95
|
},
|
|
94
96
|
claimTarget: target,
|
|
95
97
|
producer: { "survey.kontourai.io/extraction-envelope": {
|
|
@@ -100,6 +102,8 @@ function buildReviewItem(record, proposal, index) {
|
|
|
100
102
|
...(envelope.result.taskDigest ? { taskDigest: envelope.result.taskDigest } : {}),
|
|
101
103
|
...(envelope.result.exampleDigests ? { exampleDigests: envelope.result.exampleDigests } : {}),
|
|
102
104
|
valueType: { type: valueType, origin: proposal.inferenceType ?? "inferred" },
|
|
105
|
+
...(proposal.producedBy ? { producedBy: proposal.producedBy } : {}),
|
|
106
|
+
...(proposal.evidenceMatch ? { evidenceMatch: proposal.evidenceMatch } : {}),
|
|
103
107
|
occurrence: proposal.provenance.occurrence,
|
|
104
108
|
attempt: { id: envelope.result.runId, providerCalls: envelope.result.providerCalls },
|
|
105
109
|
...(envelope.result.warningClassifications ? { warnings: envelope.result.warningClassifications } : {}),
|
|
@@ -228,7 +232,7 @@ function validateEnvelope(input) {
|
|
|
228
232
|
}
|
|
229
233
|
function validateProposal(input, index, contentLength) {
|
|
230
234
|
const p = obj(input, `proposal[${index}]`);
|
|
231
|
-
exact(p, ["fieldPath", "candidateValue", "confidence", "provenance", "extractor"], `proposal[${index}]`, ["pathIndices", "inferenceType", "valueType", "enumValues"]);
|
|
235
|
+
exact(p, ["fieldPath", "candidateValue", "confidence", "provenance", "extractor"], `proposal[${index}]`, ["pathIndices", "inferenceType", "valueType", "enumValues", "producedBy", "evidenceMatch"]);
|
|
232
236
|
wireNonEmpty(p.fieldPath, "proposal.fieldPath");
|
|
233
237
|
stableIdentity(p.extractor, "proposal.extractor");
|
|
234
238
|
finite(p.confidence, "proposal.confidence", 0, 1);
|
|
@@ -252,6 +256,10 @@ function validateProposal(input, index, contentLength) {
|
|
|
252
256
|
throw new Error("proposal valueType is invalid.");
|
|
253
257
|
if (p.enumValues !== undefined)
|
|
254
258
|
array(p.enumValues, "enumValues").forEach((v) => wellFormedString(v, "enumValue"));
|
|
259
|
+
if (p.producedBy !== undefined)
|
|
260
|
+
validateProducedBy(p.producedBy);
|
|
261
|
+
if (p.evidenceMatch !== undefined)
|
|
262
|
+
validateEvidenceMatch(p.evidenceMatch);
|
|
255
263
|
return cloneJson(p);
|
|
256
264
|
}
|
|
257
265
|
function validateOccurrence(input, start, end) { const o = obj(input, "occurrence"); exact(o, ["resolverVersion", "count", "selected", "selection", "hintUsed", "ambiguous"], "occurrence"); if (o.resolverVersion !== "exact-occurrence-v1")
|
|
@@ -317,8 +325,18 @@ else
|
|
|
317
325
|
throw new Error("partial requires partial outcome."); }
|
|
318
326
|
function validateWarning(v) { const w = obj(v, "warning"); exact(w, ["category", "code"], "warning"); if (!WARNING_CATEGORIES.has(w.category))
|
|
319
327
|
throw new Error("warning category invalid."); stableIdentity(w.code, "warning.code"); }
|
|
320
|
-
function validateFailure(v) { const f = obj(v, "providerFailure"); exact(f, ["provider", "kind", "retryable"], "providerFailure"); stableIdentity(f.provider, "failure.provider"); if (!FAILURE_KINDS.has(f.kind) || typeof f.retryable !== "boolean")
|
|
321
|
-
throw new Error("provider failure invalid.");
|
|
328
|
+
function validateFailure(v) { const f = obj(v, "providerFailure"); exact(f, ["provider", "kind", "retryable"], "providerFailure", ["code"]); stableIdentity(f.provider, "failure.provider"); if (!FAILURE_KINDS.has(f.kind) || typeof f.retryable !== "boolean")
|
|
329
|
+
throw new Error("provider failure invalid."); if (f.code !== undefined) {
|
|
330
|
+
stableIdentity(f.code, "failure.code");
|
|
331
|
+
if (f.code.length > 128)
|
|
332
|
+
throw new Error("failure.code must be at most 128 characters.");
|
|
333
|
+
} }
|
|
334
|
+
function validateProducedBy(v) { const b = obj(v, "proposal.producedBy"); exact(b, ["model", "modelSource", "requestDigest"], "proposal.producedBy"); stableIdentity(b.model, "proposal.producedBy.model"); if (!MODEL_SOURCES.has(b.modelSource))
|
|
335
|
+
throw new Error("proposal.producedBy.modelSource is invalid."); digest(b.requestDigest, "proposal.producedBy.requestDigest"); }
|
|
336
|
+
function validateEvidenceMatch(v) { const m = obj(v, "proposal.evidenceMatch"); exact(m, ["checkerVersion", "schema", "valueInExcerpt"], "proposal.evidenceMatch", ["tokenBoundary"]); stableIdentity(m.checkerVersion, "proposal.evidenceMatch.checkerVersion"); if (!SCHEMA_MATCHES.has(m.schema))
|
|
337
|
+
throw new Error("proposal.evidenceMatch.schema is invalid."); if (!VALUE_IN_EXCERPT_MATCHES.has(m.valueInExcerpt))
|
|
338
|
+
throw new Error("proposal.evidenceMatch.valueInExcerpt is invalid."); if (m.tokenBoundary !== undefined && typeof m.tokenBoundary !== "boolean")
|
|
339
|
+
throw new Error("proposal.evidenceMatch.tokenBoundary must be a boolean."); }
|
|
322
340
|
function validateClaimTarget(v) { const t = obj(v, "claimTarget"); exact(t, ["subjectType", "subjectId", "facet", "claimType", "fieldOrBehavior", "impactLevel"], "claimTarget", ["claimId", "evidenceType", "evidenceMethod", "collectedBy", "derivedFrom"]); for (const key of ["subjectType", "subjectId", "facet", "claimType", "fieldOrBehavior"])
|
|
323
341
|
nonEmpty(t[key], `claimTarget.${key}`); if (!["low", "medium", "high", "critical"].includes(t.impactLevel))
|
|
324
342
|
throw new Error("claimTarget.impactLevel invalid."); for (const key of ["claimId", "evidenceType", "evidenceMethod", "collectedBy"])
|
|
@@ -433,6 +451,9 @@ const PARTIAL = new Set(["cancelled", "max-provider-calls", "max-total-tokens",
|
|
|
433
451
|
const FAILURE_CATEGORIES = new Set(["invalid-config", "invalid-task", "preparation", "provider", "unexpected"]);
|
|
434
452
|
const WARNING_CATEGORIES = new Set(["provider", "normalization", "preparation", "limit", "storage", "content", "other"]);
|
|
435
453
|
const FAILURE_KINDS = new Set(["authentication", "rate-limit", "timeout", "invalid-request", "unavailable", "unknown"]);
|
|
454
|
+
const MODEL_SOURCES = new Set(["provider-reported", "configured"]);
|
|
455
|
+
const SCHEMA_MATCHES = new Set(["ok", "type-mismatch", "enum-mismatch", "format-invalid"]);
|
|
456
|
+
const VALUE_IN_EXCERPT_MATCHES = new Set(["match", "mismatch", "not-evaluated", "not-applicable"]);
|
|
436
457
|
const PREPARATION_MODES = new Set(["text", "markdown", "transcript", "pdf-text", "image-ocr"]);
|
|
437
458
|
const ARTIFACT_INVALID_REASONS = new Set(["not-an-object", "invalid-format", "invalid-version", "invalid-digest", "invalid-ref", "invalid-preparation-mode", "invalid-preparation-version", "invalid-content-length", "invalid-source-snapshot-ref", "ill-formed-unicode", "invalid-resolved-text"]);
|
|
438
459
|
const STABLE_IDENTITY = /^[A-Za-z0-9][A-Za-z0-9._:@/+~-]{0,255}$/;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { CandidateSetStatus, Candidate, CandidateSet, ClaimTarget, EscalationDimension, EscalationRecord, Extraction, Interpretation, LocatorScheme, ProvenanceResolution, RawSource, RawSourceKind, ReviewAuthorizing, ReviewAuthorizingAuthorizedAction, ReviewAuthorizingExchange, ReviewAuthorizingExplicitStatement, ReviewAuthorizingKind, ReviewOutcome, ReviewResolution, ReviewStatus, SurveyInput, } from "./types.js";
|
|
1
|
+
export type { CandidateSetStatus, Candidate, CandidateSet, ClaimTarget, EscalationDimension, EscalationRecord, Extraction, Interpretation, InterpretationAnswerImpact, InterpretationReadingKind, LocatorScheme, ProvenanceResolution, RawSource, RawSourceKind, ReviewAuthorizing, ReviewAuthorizingAuthorizedAction, ReviewAuthorizingExchange, ReviewAuthorizingExplicitStatement, ReviewAuthorizingKind, ReviewOutcome, ReviewResolution, ReviewStatus, SurveyInput, } from "./types.js";
|
|
2
2
|
export { SURVEY_INPUT_CONTRACT_VERSION } from "./types.js";
|
|
3
3
|
export { reviewResourceApiVersion } from "./review-resource.js";
|
|
4
4
|
export { buildReviewItemsFromExtractionEnvelopeImport, createExtractionEnvelopeResolutionIdentity, exportExtractionEnvelopeImport, extractionEnvelopeImportApiVersion, importExtractionEnvelope, portableExtractionResultFormat, portableExtractionResultVersion, reimportExtractionEnvelope, validateExtractionEnvelopeImport, } from "./extraction-envelope.js";
|
|
@@ -8,7 +8,7 @@ export type { BindReviewQueueOptions, ReviewQueueBinding, ReviewQueueBindingIssu
|
|
|
8
8
|
export { resolvePortablePdfRegion } from "./pdf-layout.js";
|
|
9
9
|
export type { PortablePdfBoundingBox, PortablePdfLayout, PortablePdfPageGeometry, PortablePdfRegionContext, PortablePdfTable, PortablePdfTableCell, PortablePdfTextElement, PortablePdfTextRange, } from "./pdf-layout.js";
|
|
10
10
|
export type { ExtractionAlignmentState, ArtifactUnavailableCode, BuiltExtractionInspectorCandidate, BuiltExtractionInspectorModel, ExtractionInspectorCandidate, ExtractionInspectorEntry, ExtractionInspectorExportOptions, ExtractionInspectorFilters, ExtractionInspectorInput, ExtractionInspectorModel, ExtractionInspectorSource, ResolvedExtractionArtifact, } from "./review-workbench/extraction-inspector.js";
|
|
11
|
-
export type { ExtractionEnvelopeImport, ExtractionEnvelopeImportDiagnostic, ExtractionEnvelopeImportOptions, ExtractionEnvelopeImportResult, ExtractionEnvelopeResolutionIdentity, PortableExtractionOccurrence, PortableExtractionProposal, PortableExtractionResultEnvelope, PortablePreparedArtifactState, } from "./extraction-envelope.js";
|
|
11
|
+
export type { ExtractionEnvelopeImport, ExtractionEnvelopeImportDiagnostic, ExtractionEnvelopeImportOptions, ExtractionEnvelopeImportResult, ExtractionEnvelopeResolutionIdentity, PortableExtractionEvidenceMatch, PortableExtractionOccurrence, PortableExtractionProducedBy, PortableExtractionProposal, PortableExtractionResultEnvelope, PortablePreparedArtifactState, } from "./extraction-envelope.js";
|
|
12
12
|
export type { CandidateRole, ClaimTargetHint, ProducerPolicy, ExtractionReference, ResourceEnvelope, ResourceMetadata, ReviewActor, ReviewCandidate, ReviewDecision, ReviewDecisionMode, ReviewDecisionSpec, ReviewDecisionStatus, ReviewItem, ReviewItemSpec, ReviewItemStatus, ReviewLocator, ReviewResource, ReviewResourceApiVersion, ReviewResourceKind, ReviewSession, ReviewSessionEvent, ReviewSessionEventSpec, ReviewSessionEventStatus, ReviewSessionEventType, ReviewSessionSpec, ReviewSessionStatus, ReviewValueDescriptor, ReviewValueType, SourceReference, SurveyRecordProjectionHint, } from "./review-resource.js";
|
|
13
13
|
export { toSurfaceReviewedExtractionDecision, toSurfaceReviewedExtractionImport, toSurfaceReviewedExtractionItem, } from "./surface-reviewed-extraction.js";
|
|
14
14
|
export { candidateReviewRecord, candidateSetStatusFor, SurveyInputBuilder } from "./builder.js";
|
|
@@ -17,7 +17,7 @@ export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js"
|
|
|
17
17
|
export type { ReviewedCandidateResolutionInput } from "./reviewed-candidate-resolution.js";
|
|
18
18
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
19
19
|
export type { CurrentProposedCandidateRole, ReviewedCurrentProposedResolutionInput, } from "./reviewed-current-proposed-resolution.js";
|
|
20
|
-
export { buildSurveyTrustBundle } from "./to-surface.js";
|
|
20
|
+
export { buildSurveyTrustBundle, ReviewAgreementError } from "./to-surface.js";
|
|
21
21
|
export type { BuildSurveyTrustBundleOptions } from "./to-surface.js";
|
|
22
22
|
export { buildCanonicalReviewedTrustInput } from "./canonical-reviewed-trust-input.js";
|
|
23
23
|
export type { BuildCanonicalReviewedTrustInputOptions, CanonicalReviewedTrustInput, } from "./canonical-reviewed-trust-input.js";
|
|
@@ -35,16 +35,17 @@ export { repeatedObservation } from "./repeated-observation.js";
|
|
|
35
35
|
export type { RepeatedObservationInput } from "./repeated-observation.js";
|
|
36
36
|
export { sourceOfAuthorityObservation, sourceOfAuthorityObservationBuilder, SourceOfAuthorityObservationBuilder, } from "./source-of-authority-observation.js";
|
|
37
37
|
export type { SourceAuthorityClass, SourceAuthorityMetadata, SourceOfAuthorityObservationBuilderArgs, SourceOfAuthorityObservationInput, } from "./source-of-authority-observation.js";
|
|
38
|
-
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-workbench/review-presentation.js";
|
|
39
|
-
export type { ReviewCandidatePresentation, ReviewCandidatePresentationContext, ReviewItemPresentation, ReviewItemPresentationContext, ReviewPresentationAdapter, ReviewPresentationLink, ReviewResultPresentation, ReviewTracePresentationContext, ReviewTraceRef, ReviewValuePresentationContext, } from "./review-workbench/review-presentation.js";
|
|
38
|
+
export { buildInterpretationReadingPresentation, buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-workbench/review-presentation.js";
|
|
39
|
+
export type { InterpretationReadingPresentation, InterpretationReadingSource, ReviewCandidatePresentation, ReviewCandidatePresentationContext, ReviewItemPresentation, ReviewItemPresentationContext, ReviewPresentationAdapter, ReviewPresentationLink, ReviewResultPresentation, ReviewTracePresentationContext, ReviewTraceRef, ReviewValuePresentationContext, } from "./review-workbench/review-presentation.js";
|
|
40
40
|
export { apiRecordSource, manualEntrySource, policyStandardSource, uploadedDocumentSource, webPageSource, } from "./raw-source.js";
|
|
41
41
|
export type { ApiRecordSourceInput, ChecksumInput, ManualEntrySourceInput, PolicyStandardMetadata, PolicyStandardSourceInput, RawSourceInput, UploadedDocumentSourceInput, WebPageSourceInput, } from "./raw-source.js";
|
|
42
42
|
export { applyAutoAcceptPolicy, applyMappingReview, buildMappingReviewItems, lookupMapping, lookupRejectedMapping, normalizeQuestion, proposalsToCandidateSet, referenceMappingProposer, resolveQuestion, } from "./inquiry-mapping.js";
|
|
43
|
-
export type {
|
|
43
|
+
export type { AutoAcceptWarning } from "./producer-profile.js";
|
|
44
|
+
export type { AutoAcceptPolicy, AutoAcceptPolicyOptions, InquiryMapping, MappingProposal, MappingProposer, } from "./inquiry-mapping.js";
|
|
44
45
|
export { referenceUtteranceExtractor, surveyAgentUtterance, utteranceToSurveyInput, } from "./agent-utterance.js";
|
|
45
|
-
export type { ExtractedStatement, StatementBadge, UtteranceClaimExtractor, UtteranceStatement, UtteranceStatementRecords, UtteranceTrustReport, } from "./agent-utterance.js";
|
|
46
|
+
export type { ExtractedStatement, LocatorResolution, StatementBadge, StatementValueComparison, UtteranceClaimExtractor, UtteranceStatement, UtteranceStatementRecords, UtteranceTrustReport, } from "./agent-utterance.js";
|
|
46
47
|
export { mappingReviewToSurface, referenceSchemaExtractor, surveySchemaMapping, } from "./schema-mapping.js";
|
|
47
|
-
export type { MappingProposalRecord, ReviewedMapping, SchemaMappingExtractor, SchemaMappingOptions, SystemFieldRef, } from "./schema-mapping.js";
|
|
48
|
+
export type { MappingProposalRecord, ReviewedMapping, SchemaMappingExtractor, SchemaMappingOptions, SchemaMappingValue, SystemFieldRef, } from "./schema-mapping.js";
|
|
48
49
|
export { buildAuthorizedActionAuthorizing, buildPromptRef, isValidAuthorizing, validateAuthorizing } from "./review-authorizing.js";
|
|
49
50
|
export type { BuildAuthorizedActionAuthorizingInput, BuildPromptRefInput, ReviewAuthorizingIssue, ReviewAuthorizingIssueCode, } from "./review-authorizing.js";
|
|
50
51
|
export { confidenceBasisForReview, defineProductVocabulary, stableId } from "./vocabulary.js";
|
package/dist/src/index.js
CHANGED
|
@@ -8,7 +8,7 @@ export { toSurfaceReviewedExtractionDecision, toSurfaceReviewedExtractionImport,
|
|
|
8
8
|
export { candidateReviewRecord, candidateSetStatusFor, SurveyInputBuilder } from "./builder.js";
|
|
9
9
|
export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js";
|
|
10
10
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
11
|
-
export { buildSurveyTrustBundle } from "./to-surface.js";
|
|
11
|
+
export { buildSurveyTrustBundle, ReviewAgreementError } from "./to-surface.js";
|
|
12
12
|
export { buildCanonicalReviewedTrustInput } from "./canonical-reviewed-trust-input.js";
|
|
13
13
|
export { buildSurveyLearningProjections } from "./learning-projections.js";
|
|
14
14
|
export { buildReviewedLearningUpdateProposal } from "./learning-update-proposal.js";
|
|
@@ -17,7 +17,7 @@ export { buildCanonicalReviewProofPayload, buildReviewProofAnchor, canonicalRevi
|
|
|
17
17
|
export { fieldObservation } from "./field-observation.js";
|
|
18
18
|
export { repeatedObservation } from "./repeated-observation.js";
|
|
19
19
|
export { sourceOfAuthorityObservation, sourceOfAuthorityObservationBuilder, SourceOfAuthorityObservationBuilder, } from "./source-of-authority-observation.js";
|
|
20
|
-
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-workbench/review-presentation.js";
|
|
20
|
+
export { buildInterpretationReadingPresentation, buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-workbench/review-presentation.js";
|
|
21
21
|
export { apiRecordSource, manualEntrySource, policyStandardSource, uploadedDocumentSource, webPageSource, } from "./raw-source.js";
|
|
22
22
|
export { applyAutoAcceptPolicy, applyMappingReview, buildMappingReviewItems, lookupMapping, lookupRejectedMapping, normalizeQuestion, proposalsToCandidateSet, referenceMappingProposer, resolveQuestion, } from "./inquiry-mapping.js";
|
|
23
23
|
export { referenceUtteranceExtractor, surveyAgentUtterance, utteranceToSurveyInput, } from "./agent-utterance.js";
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import type { DerivationRule, InquiryRecord, TrustBundle } from "@kontourai/surface";
|
|
21
21
|
import type { CanonicalClaimTarget } from "@kontourai/surface";
|
|
22
22
|
import type { Candidate, CandidateSet, ReviewOutcome } from "./types.js";
|
|
23
|
+
import type { AutoAcceptWarning } from "./producer-profile.js";
|
|
23
24
|
import type { ReviewItem } from "./review-resource.js";
|
|
24
25
|
/**
|
|
25
26
|
* A single machine- or human-generated suggestion that a natural-language
|
|
@@ -140,18 +141,31 @@ export declare function applyMappingReview(candidateSet: CandidateSet, reviewOut
|
|
|
140
141
|
export interface AutoAcceptPolicy {
|
|
141
142
|
minConfidence: number;
|
|
142
143
|
}
|
|
144
|
+
/** Optional hooks for {@link applyAutoAcceptPolicy}. */
|
|
145
|
+
export interface AutoAcceptPolicyOptions {
|
|
146
|
+
/**
|
|
147
|
+
* Called once for each proposal the policy refused because its confidence
|
|
148
|
+
* is not a finite number in [0, 1]. The proposal gets no mapping and stays
|
|
149
|
+
* in human review.
|
|
150
|
+
*/
|
|
151
|
+
onWarning?: (warning: AutoAcceptWarning) => void;
|
|
152
|
+
}
|
|
143
153
|
/**
|
|
144
154
|
* Apply an auto-accept policy to a list of proposals, returning InquiryMappings.
|
|
145
155
|
*
|
|
146
156
|
* Proposals at or above minConfidence → status "assumed", withinComfortZone: true
|
|
147
157
|
* Proposals below minConfidence → return a "needs-review" mapping (not yet durable)
|
|
158
|
+
* Proposals whose confidence is not a finite number in [0, 1] are never
|
|
159
|
+
* auto-accepted. Throws `RangeError` unless `policy.minConfidence` is a finite
|
|
160
|
+
* number in (0, 1]. Refused out-of-range proposals are reported through
|
|
161
|
+
* `options.onWarning`.
|
|
148
162
|
*
|
|
149
163
|
* Only non-conflicting proposals are auto-accepted. If proposals disagree, they
|
|
150
164
|
* need human review regardless of confidence.
|
|
151
165
|
*
|
|
152
166
|
* Returns an array of InquiryMappings (only for accepted proposals).
|
|
153
167
|
*/
|
|
154
|
-
export declare function applyAutoAcceptPolicy(proposals: MappingProposal[], policy: AutoAcceptPolicy): InquiryMapping[];
|
|
168
|
+
export declare function applyAutoAcceptPolicy(proposals: MappingProposal[], policy: AutoAcceptPolicy, options?: AutoAcceptPolicyOptions): InquiryMapping[];
|
|
155
169
|
/**
|
|
156
170
|
* Look up an InquiryMapping for a question by exact normalized-text match.
|
|
157
171
|
*
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* and lives in the flow-agents repo.
|
|
19
19
|
*/
|
|
20
20
|
import { resolveInquiry } from "@kontourai/surface";
|
|
21
|
-
import { evaluateAutoAccept, getProducerProposal, hasCandidateConflict, projectProposalsToCandidateSet, } from "./producer-profile.js";
|
|
21
|
+
import { assertValidAutoAcceptThreshold, evaluateAutoAccept, getProducerProposal, hasCandidateConflict, projectProposalsToCandidateSet, } from "./producer-profile.js";
|
|
22
22
|
import { reviewResourceApiVersion } from "./review-resource.js";
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
// Question normalization
|
|
@@ -140,13 +140,18 @@ export function applyMappingReview(candidateSet, reviewOutcome) {
|
|
|
140
140
|
*
|
|
141
141
|
* Proposals at or above minConfidence → status "assumed", withinComfortZone: true
|
|
142
142
|
* Proposals below minConfidence → return a "needs-review" mapping (not yet durable)
|
|
143
|
+
* Proposals whose confidence is not a finite number in [0, 1] are never
|
|
144
|
+
* auto-accepted. Throws `RangeError` unless `policy.minConfidence` is a finite
|
|
145
|
+
* number in (0, 1]. Refused out-of-range proposals are reported through
|
|
146
|
+
* `options.onWarning`.
|
|
143
147
|
*
|
|
144
148
|
* Only non-conflicting proposals are auto-accepted. If proposals disagree, they
|
|
145
149
|
* need human review regardless of confidence.
|
|
146
150
|
*
|
|
147
151
|
* Returns an array of InquiryMappings (only for accepted proposals).
|
|
148
152
|
*/
|
|
149
|
-
export function applyAutoAcceptPolicy(proposals, policy) {
|
|
153
|
+
export function applyAutoAcceptPolicy(proposals, policy, options = {}) {
|
|
154
|
+
assertValidAutoAcceptThreshold(policy.minConfidence);
|
|
150
155
|
if (proposals.length === 0)
|
|
151
156
|
return [];
|
|
152
157
|
// If proposals disagree, none can be auto-accepted
|
|
@@ -154,6 +159,9 @@ export function applyAutoAcceptPolicy(proposals, policy) {
|
|
|
154
159
|
return [];
|
|
155
160
|
return proposals.flatMap((proposal) => {
|
|
156
161
|
const decision = evaluateAutoAccept({ confidence: proposal.confidence, rationale: proposal.rationale, proposedAt: proposal.proposedAt }, false, policy, proposal.proposedAt);
|
|
162
|
+
if (decision.warning) {
|
|
163
|
+
options.onWarning?.({ code: decision.warning, proposalId: proposal.id, confidence: decision.confidence });
|
|
164
|
+
}
|
|
157
165
|
if (!decision.accepted)
|
|
158
166
|
return [];
|
|
159
167
|
return [
|