@kontourai/survey 1.0.0 → 1.1.1
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/src/console/review-console-server.js +6 -4
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -1
- package/dist/src/mcp/review-mcp.js +68 -1
- package/dist/src/review-workbench/canonical.d.ts +7 -0
- package/dist/src/review-workbench/canonical.js +32 -0
- package/dist/src/review-workbench/review-presentation.js +2 -2
- package/dist/src/review-workbench/review-surface-preview.js +2 -2
- package/dist/src/review-workbench/review-workbench.js +9 -20
- package/dist/src/review-workbench/server-review-session.js +1 -18
- package/package.json +2 -2
- package/dist/src/to-flow-artifact.d.ts +0 -41
- package/dist/src/to-flow-artifact.js +0 -31
- /package/dist/src/review-workbench/vendor/{console-kit → kontourai-ui}/tokens/fonts.css +0 -0
- /package/dist/src/review-workbench/vendor/{console-kit → kontourai-ui}/tokens/index.css +0 -0
- /package/dist/src/review-workbench/vendor/{console-kit → kontourai-ui}/tokens/themes.css +0 -0
- /package/dist/src/review-workbench/vendor/{console-kit → kontourai-ui}/tokens/tokens.css +0 -0
|
@@ -175,7 +175,7 @@ function buildConsoleHtml(sessionPath) {
|
|
|
175
175
|
// workbench.js lives at dist/src/review-workbench/review-workbench.js relative to distRoot
|
|
176
176
|
const workbenchJsPath = "/dist/src/review-workbench/review-workbench.js";
|
|
177
177
|
const workbenchCssPath = "/dist/src/review-workbench/review-workbench.css";
|
|
178
|
-
const tokensIndexPath = "/dist/src/review-workbench/vendor/
|
|
178
|
+
const tokensIndexPath = "/dist/src/review-workbench/vendor/kontourai-ui/tokens/index.css";
|
|
179
179
|
return `<!doctype html>
|
|
180
180
|
<html lang="en" class="theme-survey">
|
|
181
181
|
<head>
|
|
@@ -293,9 +293,11 @@ body {
|
|
|
293
293
|
<body>
|
|
294
294
|
<header class="console-topbar" role="banner">
|
|
295
295
|
<div class="console-topbar-logo">
|
|
296
|
-
<svg width="
|
|
297
|
-
<
|
|
298
|
-
<path d="
|
|
296
|
+
<svg class="product-mark" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Survey product mark">
|
|
297
|
+
<circle cx="10" cy="10" r="5.5"/>
|
|
298
|
+
<path d="M14 14 19.5 19.5"/>
|
|
299
|
+
<path d="M7.6 9H12.4"/>
|
|
300
|
+
<path d="M7.6 11.2H11.2"/>
|
|
299
301
|
</svg>
|
|
300
302
|
Survey Review Console
|
|
301
303
|
</div>
|
package/dist/src/index.d.ts
CHANGED
|
@@ -7,8 +7,6 @@ export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js"
|
|
|
7
7
|
export type { ReviewedCandidateResolutionInput } from "./reviewed-candidate-resolution.js";
|
|
8
8
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
9
9
|
export type { CurrentProposedCandidateRole, ReviewedCurrentProposedResolutionInput, } from "./reviewed-current-proposed-resolution.js";
|
|
10
|
-
export { flowTrustArtifactFromReviewOutcome } from "./to-flow-artifact.js";
|
|
11
|
-
export type { FlowTrustArtifact, FlowTrustArtifactOptions } from "./to-flow-artifact.js";
|
|
12
10
|
export { buildSurveyTrustBundle } from "./to-surface.js";
|
|
13
11
|
export type { BuildSurveyTrustBundleOptions } from "./to-surface.js";
|
|
14
12
|
export { buildSurveyLearningProjections } from "./learning-projections.js";
|
package/dist/src/index.js
CHANGED
|
@@ -2,7 +2,6 @@ export { reviewResourceApiVersion } from "./review-resource.js";
|
|
|
2
2
|
export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
|
|
3
3
|
export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js";
|
|
4
4
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
5
|
-
export { flowTrustArtifactFromReviewOutcome } from "./to-flow-artifact.js";
|
|
6
5
|
export { buildSurveyTrustBundle } from "./to-surface.js";
|
|
7
6
|
export { buildSurveyLearningProjections } from "./learning-projections.js";
|
|
8
7
|
export { buildCanonicalReviewProofPayload, buildReviewProofAnchor, canonicalReviewProofJson, hashCanonicalReviewProofPayload, REVIEW_PROOF_CONTRACT_VERSION, REVIEW_PROOF_PACKAGE_NAME, REVIEW_PROOF_SCHEMA, REVIEW_PROOF_SCHEMA_VERSION, } from "./review-proof.js";
|
|
@@ -14,6 +14,17 @@ import { createServerReviewSessionRecord, deriveServerReviewSessionApplyResult,
|
|
|
14
14
|
*/
|
|
15
15
|
const PROTOCOL_VERSION = "2025-06-18";
|
|
16
16
|
const SESSION_NAME = "mcp-review-session";
|
|
17
|
+
// MCP Apps extension (SEP-1865). The review card is offered under both UI
|
|
18
|
+
// conventions so one server renders across hosts: the existing mcp-ui.dev
|
|
19
|
+
// embedded resource in tool results, AND a declared `ui://` resource that the
|
|
20
|
+
// official Apps hosts (ChatGPT/Claude) and Station's SEP-1865 resolver read via
|
|
21
|
+
// resources/read. The canonical pointer is the FLAT `_meta["ui/resourceUri"]`
|
|
22
|
+
// key (what registerAppTool emits); the nested `_meta.ui.resourceUri` is the
|
|
23
|
+
// convenience shape some hosts read — we emit both.
|
|
24
|
+
const UI_RESOURCE_URI_META_KEY = "ui/resourceUri";
|
|
25
|
+
const UI_CAPABILITY_EXTENSION = "io.modelcontextprotocol/ui";
|
|
26
|
+
const QUEUE_PANEL_URI = "ui://survey/review-card/queue";
|
|
27
|
+
const UI_RESOURCE_MIME = "text/html;profile=mcp-app";
|
|
17
28
|
// MCP tool decision strings → ReviewWorkbenchDecision
|
|
18
29
|
const MCP_DECISION_MAP = {
|
|
19
30
|
accept: "accept-proposed",
|
|
@@ -420,6 +431,15 @@ function buildUiResource(item, snapshot, events, instance) {
|
|
|
420
431
|
},
|
|
421
432
|
};
|
|
422
433
|
}
|
|
434
|
+
// Render the SEP-1865 declared review card: load the configured session, replay
|
|
435
|
+
// to current state, and render the active item's card HTML (the same HTML the
|
|
436
|
+
// embedded `queue` resource carries — here served via resources/read).
|
|
437
|
+
async function readQueuePanelHtml(options) {
|
|
438
|
+
const { snapshot, events } = await readSessionFile(options.sessionPath);
|
|
439
|
+
const current = events.length > 0 ? replayReviewSessionEvents(snapshot, events) : snapshot;
|
|
440
|
+
const activeItem = currentReviewItem(current);
|
|
441
|
+
return buildReviewCardHtml(activeItem, snapshot, events);
|
|
442
|
+
}
|
|
423
443
|
// ---- Domain error (maps to isError:true, not a JSON-RPC error) -----------
|
|
424
444
|
class DomainError extends Error {
|
|
425
445
|
isDomainError = true;
|
|
@@ -446,7 +466,14 @@ async function handleLine(line, options, serverVersion) {
|
|
|
446
466
|
id,
|
|
447
467
|
result: {
|
|
448
468
|
protocolVersion: PROTOCOL_VERSION,
|
|
449
|
-
capabilities: {
|
|
469
|
+
capabilities: {
|
|
470
|
+
tools: { listChanged: false },
|
|
471
|
+
// Resources back the SEP-1865 ui:// review card (unless --no-ui).
|
|
472
|
+
...(options.noUi ? {} : { resources: { listChanged: false } }),
|
|
473
|
+
...(options.noUi
|
|
474
|
+
? {}
|
|
475
|
+
: { extensions: { [UI_CAPABILITY_EXTENSION]: {} } }),
|
|
476
|
+
},
|
|
450
477
|
serverInfo: { name: "survey-review-mcp", title: "Survey Review MCP", version: serverVersion },
|
|
451
478
|
instructions: "Use survey_review_queue to inspect the queue, survey_review_item to drill into a single item, and survey_review_decide to record a decision. Decisions are persisted to the session file and are irreversible within this session.",
|
|
452
479
|
},
|
|
@@ -466,6 +493,15 @@ async function handleLine(line, options, serverVersion) {
|
|
|
466
493
|
title: "Review queue",
|
|
467
494
|
description: "Return a text summary and JSON of the current review queue: all items with their status (pending, in-review, resolved, rejected, escalated), the active item, resolved/total counts, and session summary totals.",
|
|
468
495
|
inputSchema: { type: "object", properties: {} },
|
|
496
|
+
// SEP-1865 UI pointer (both flat canonical + nested), unless --no-ui.
|
|
497
|
+
...(options.noUi
|
|
498
|
+
? {}
|
|
499
|
+
: {
|
|
500
|
+
_meta: {
|
|
501
|
+
[UI_RESOURCE_URI_META_KEY]: QUEUE_PANEL_URI,
|
|
502
|
+
ui: { resourceUri: QUEUE_PANEL_URI, visibility: ["model", "app"] },
|
|
503
|
+
},
|
|
504
|
+
}),
|
|
469
505
|
},
|
|
470
506
|
{
|
|
471
507
|
name: "survey_review_item",
|
|
@@ -501,6 +537,37 @@ async function handleLine(line, options, serverVersion) {
|
|
|
501
537
|
},
|
|
502
538
|
});
|
|
503
539
|
}
|
|
540
|
+
else if (method === "resources/list") {
|
|
541
|
+
send({
|
|
542
|
+
jsonrpc: "2.0",
|
|
543
|
+
id,
|
|
544
|
+
result: {
|
|
545
|
+
resources: options.noUi
|
|
546
|
+
? []
|
|
547
|
+
: [
|
|
548
|
+
{
|
|
549
|
+
uri: QUEUE_PANEL_URI,
|
|
550
|
+
name: "Survey review workbench",
|
|
551
|
+
description: "Interactive review card for the active item in the configured review session (MCP Apps UI resource).",
|
|
552
|
+
mimeType: UI_RESOURCE_MIME,
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
else if (method === "resources/read") {
|
|
559
|
+
const uri = typeof params?.uri === "string" ? params.uri : "";
|
|
560
|
+
if (options.noUi || uri !== QUEUE_PANEL_URI) {
|
|
561
|
+
send({ jsonrpc: "2.0", id, error: { code: -32602, message: `Unknown resource: ${uri || "(missing uri)"}` } });
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const html = await readQueuePanelHtml(options);
|
|
565
|
+
send({
|
|
566
|
+
jsonrpc: "2.0",
|
|
567
|
+
id,
|
|
568
|
+
result: { contents: [{ uri: QUEUE_PANEL_URI, mimeType: UI_RESOURCE_MIME, text: html }] },
|
|
569
|
+
});
|
|
570
|
+
}
|
|
504
571
|
else if (method === "tools/call") {
|
|
505
572
|
const name = typeof params?.name === "string" ? params.name : "";
|
|
506
573
|
const toolArgs = (params?.arguments ?? {});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a value into a deterministic, order-independent form: object keys
|
|
3
|
+
* sorted, `undefined`-valued keys dropped, Dates rendered as ISO strings.
|
|
4
|
+
*/
|
|
5
|
+
export declare function canonicalize(value: unknown): unknown;
|
|
6
|
+
/** Deterministic JSON string for hashing or structural comparison. */
|
|
7
|
+
export declare function canonicalJson(value: unknown): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Single canonical JSON serializer shared by the review-workbench structural
|
|
2
|
+
// comparison (`structuralEqual`) and the server-side snapshot hashing, so the
|
|
3
|
+
// same object always produces the same canonical string and hash.
|
|
4
|
+
//
|
|
5
|
+
// Previously two near-identical copies lived in review-workbench.ts and
|
|
6
|
+
// server-review-session.ts and disagreed on Date handling — the workbench copy
|
|
7
|
+
// turned a Date into `{}` (no own enumerable keys), while the server copy used
|
|
8
|
+
// the ISO string. That desynced `structuralEqual` from snapshot freshness/replay
|
|
9
|
+
// hashes for any object carrying a Date (audit 2026-06-28, ops#24).
|
|
10
|
+
/**
|
|
11
|
+
* Normalize a value into a deterministic, order-independent form: object keys
|
|
12
|
+
* sorted, `undefined`-valued keys dropped, Dates rendered as ISO strings.
|
|
13
|
+
*/
|
|
14
|
+
export function canonicalize(value) {
|
|
15
|
+
if (value instanceof Date) {
|
|
16
|
+
return value.toISOString();
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value.map((entry) => canonicalize(entry));
|
|
20
|
+
}
|
|
21
|
+
if (value && typeof value === "object") {
|
|
22
|
+
const entries = Object.entries(value)
|
|
23
|
+
.filter(([, entryValue]) => entryValue !== undefined)
|
|
24
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
25
|
+
return Object.fromEntries(entries.map(([key, entryValue]) => [key, canonicalize(entryValue)]));
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
/** Deterministic JSON string for hashing or structural comparison. */
|
|
30
|
+
export function canonicalJson(value) {
|
|
31
|
+
return JSON.stringify(canonicalize(value));
|
|
32
|
+
}
|
|
@@ -22,7 +22,7 @@ export function buildReviewCandidatePresentation(item, candidate, adapter = {},
|
|
|
22
22
|
roleLabel: adapter.labelForCandidateRole?.(candidate.role, context) ?? defaultCandidateRoleLabel(candidate.role),
|
|
23
23
|
valueLabel: targetLabel,
|
|
24
24
|
valueText: adapter.summarizeValue?.(candidate.value, { ...context, value: candidate.value }) ?? formatValue(candidate.value),
|
|
25
|
-
sourceLabel: "Source",
|
|
25
|
+
sourceLabel: "Source Reference",
|
|
26
26
|
sourceText: sourceLink?.label ?? sourceRef,
|
|
27
27
|
sourceLink,
|
|
28
28
|
traceRefs: traceRefsForCandidate(item, candidate, adapter),
|
|
@@ -78,7 +78,7 @@ function traceRefsForCandidate(item, candidate, adapter) {
|
|
|
78
78
|
kind: "claim",
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
|
-
label: "Source ID",
|
|
81
|
+
label: "Raw Source ID",
|
|
82
82
|
value: candidate.source.sourceId ?? candidate.source.sourceRef,
|
|
83
83
|
kind: "source",
|
|
84
84
|
},
|
|
@@ -92,7 +92,7 @@ function buildIntegrityPosture(item, candidate, projection) {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
function postureDisclaimer() {
|
|
95
|
-
return "Survey records
|
|
95
|
+
return "Survey records Raw Source, Source Reference, and review posture for projection; it does not validate real-world truth.";
|
|
96
96
|
}
|
|
97
97
|
function sourceAuthorityFromProducer(value) {
|
|
98
98
|
if (!isRecord(value)) {
|
|
@@ -115,7 +115,7 @@ function portableAuthorityTrace(value) {
|
|
|
115
115
|
return {
|
|
116
116
|
status: "empty",
|
|
117
117
|
label: "Empty / not provided",
|
|
118
|
-
detail: "No portable authority trace is present. SourceAuthority metadata is shown
|
|
118
|
+
detail: "No portable authority trace is present. SourceAuthority metadata is shown with Raw Source and Source Reference posture and is not promoted into authorityTrace.",
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
function isRecord(value) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalJson } from "./canonical.js";
|
|
1
2
|
import { candidateForDecision, buildReviewSessionEvent, buildReviewSessionEvents, buildReviewSessionResource, currentReviewItem, currentReviewWorkbenchState, defaultReviewSessionName, deriveQueueRowStatus, initialReviewQueueSessionState, nextUnresolvedItemName, replayReviewSessionEvents, reviewSessionSummary, reviewWorkbenchSessionStorageKey, selectedCandidateRole, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
2
3
|
import { validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
3
4
|
import { buildSurfaceProjectionPreview, formatValue, } from "./review-surface-preview.js";
|
|
@@ -229,19 +230,7 @@ function matchingSelectedCandidate(item, result) {
|
|
|
229
230
|
: undefined;
|
|
230
231
|
}
|
|
231
232
|
function structuralEqual(left, right) {
|
|
232
|
-
return
|
|
233
|
-
}
|
|
234
|
-
function canonicalJson(value) {
|
|
235
|
-
if (!value || typeof value !== "object") {
|
|
236
|
-
return value;
|
|
237
|
-
}
|
|
238
|
-
if (Array.isArray(value)) {
|
|
239
|
-
return value.map(canonicalJson);
|
|
240
|
-
}
|
|
241
|
-
return Object.fromEntries(Object.entries(value)
|
|
242
|
-
.filter(([, entry]) => entry !== undefined)
|
|
243
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
244
|
-
.map(([key, entry]) => [key, canonicalJson(entry)]));
|
|
233
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
245
234
|
}
|
|
246
235
|
export function buildReviewWorkbenchResultsFromSession(session) {
|
|
247
236
|
return session.items.flatMap((item) => {
|
|
@@ -653,7 +642,7 @@ function renderReviewFocus(state, presentationAdapter) {
|
|
|
653
642
|
</div>
|
|
654
643
|
<dl class="focus-evidence">
|
|
655
644
|
${fieldItem("Target", presentation.targetLabel)}
|
|
656
|
-
${fieldItem("Proposed
|
|
645
|
+
${fieldItem("Proposed Source Reference", proposed.source.sourceRef)}
|
|
657
646
|
${fieldItemClamped("Proposed excerpt", proposed.locator?.excerpt ?? "none", "excerpt")}
|
|
658
647
|
</dl>
|
|
659
648
|
</section>
|
|
@@ -745,7 +734,7 @@ function renderPendingSurfacePreview() {
|
|
|
745
734
|
<span class="surface-summary-label">Surface preview</span>
|
|
746
735
|
<span class="state-label">pending</span>
|
|
747
736
|
</summary>
|
|
748
|
-
<p class="preview-disclaimer">Choose a review decision to preview the
|
|
737
|
+
<p class="preview-disclaimer">Choose a review decision to preview the Raw Source, Source Reference, and review posture that would project toward Surface.</p>
|
|
749
738
|
</details>
|
|
750
739
|
`;
|
|
751
740
|
}
|
|
@@ -852,12 +841,12 @@ function renderCandidateHistory(preview) {
|
|
|
852
841
|
function renderSourceEvidence(preview) {
|
|
853
842
|
// Excerpt uses a 3-line clamp wrapper with a toggle button.
|
|
854
843
|
const clampedExcerptHtml = fieldItemClamped("Excerpt", preview.sourceEvidence.excerpt, "excerpt");
|
|
855
|
-
// Build
|
|
844
|
+
// Build Raw Source section with clamped excerpt inline.
|
|
856
845
|
return `
|
|
857
846
|
<section class="preview-section" data-testid="surface-source-evidence">
|
|
858
|
-
<h3>${escapeHtml("Source
|
|
847
|
+
<h3>${escapeHtml("Raw Source")}</h3>
|
|
859
848
|
<dl class="field-stack compact">
|
|
860
|
-
${fieldItem("Source
|
|
849
|
+
${fieldItem("Source Reference", preview.sourceEvidence.sourceRef)}
|
|
861
850
|
${clampedExcerptHtml}
|
|
862
851
|
${fieldItem("Extractor", preview.sourceEvidence.extractor)}
|
|
863
852
|
${fieldItem("Observed", preview.sourceEvidence.observedAt)}
|
|
@@ -868,7 +857,7 @@ function renderSourceEvidence(preview) {
|
|
|
868
857
|
].join("") : ""}
|
|
869
858
|
</dl>
|
|
870
859
|
${renderReferenceDetails([
|
|
871
|
-
["Source ID", preview.sourceEvidence.sourceId],
|
|
860
|
+
["Raw Source ID", preview.sourceEvidence.sourceId],
|
|
872
861
|
["Extraction ID", preview.sourceEvidence.extractionId],
|
|
873
862
|
])}
|
|
874
863
|
</section>
|
|
@@ -1194,7 +1183,7 @@ function renderCandidateCard(candidate, state, presentationAdapter) {
|
|
|
1194
1183
|
<p class="field-value">${escapeHtml(presentation.valueText)}</p>
|
|
1195
1184
|
</div>
|
|
1196
1185
|
<dl class="field-stack">
|
|
1197
|
-
${fieldItem(
|
|
1186
|
+
${fieldItem(presentation.sourceLabel, presentation.sourceText)}
|
|
1198
1187
|
${fieldItem("Locator", candidate.locator?.locator ?? candidate.locator?.scheme ?? "none")}
|
|
1199
1188
|
${fieldItemClamped("Excerpt", candidate.locator?.excerpt ?? "none", "excerpt")}
|
|
1200
1189
|
${fieldItem("Extraction confidence", confidence === undefined ? "unknown" : formatConfidence(confidence))}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { deriveReviewSessionApplyResultForSnapshot, } from "./review-workbench.js";
|
|
3
3
|
import { validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
4
|
+
import { canonicalJson } from "./canonical.js";
|
|
4
5
|
export class StaleServerReviewSessionError extends Error {
|
|
5
6
|
name = "StaleServerReviewSessionError";
|
|
6
7
|
issues;
|
|
@@ -107,24 +108,6 @@ function staleIssuesForComparison(comparison) {
|
|
|
107
108
|
}
|
|
108
109
|
return issues;
|
|
109
110
|
}
|
|
110
|
-
function canonicalJson(value) {
|
|
111
|
-
return JSON.stringify(canonicalize(value));
|
|
112
|
-
}
|
|
113
|
-
function canonicalize(value) {
|
|
114
|
-
if (value instanceof Date) {
|
|
115
|
-
return value.toISOString();
|
|
116
|
-
}
|
|
117
|
-
if (Array.isArray(value)) {
|
|
118
|
-
return value.map((entry) => canonicalize(entry));
|
|
119
|
-
}
|
|
120
|
-
if (value && typeof value === "object") {
|
|
121
|
-
const entries = Object.entries(value)
|
|
122
|
-
.filter(([, entryValue]) => entryValue !== undefined)
|
|
123
|
-
.sort(([left], [right]) => left.localeCompare(right));
|
|
124
|
-
return Object.fromEntries(entries.map(([key, entryValue]) => [key, canonicalize(entryValue)]));
|
|
125
|
-
}
|
|
126
|
-
return value;
|
|
127
|
-
}
|
|
128
111
|
function sha256(value) {
|
|
129
112
|
return createHash("sha256").update(value).digest("hex");
|
|
130
113
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/survey",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
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",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@anthropic-ai/sdk": "^0.54.0",
|
|
83
|
-
"@kontourai/
|
|
83
|
+
"@kontourai/ui": "^1.1.0",
|
|
84
84
|
"@playwright/test": "^1.60.0",
|
|
85
85
|
"@types/node": "^25.6.0",
|
|
86
86
|
"marked": "^16.4.2",
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { ReviewOutcome, ReviewStatus } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* The neutral trust-artifact shape Kontour Flow consumes through
|
|
4
|
-
* `flow attach-evidence --trust-artifact`. Flow evaluates only these fields
|
|
5
|
-
* plus its own definition and project config; Survey stays the producer-side
|
|
6
|
-
* authority for what the review actually decided.
|
|
7
|
-
*/
|
|
8
|
-
export interface FlowTrustArtifact {
|
|
9
|
-
schema_version: "0.1";
|
|
10
|
-
artifact_type: "trust-report";
|
|
11
|
-
subject: string;
|
|
12
|
-
producer: string;
|
|
13
|
-
status: string;
|
|
14
|
-
issued_at: string;
|
|
15
|
-
authority_traces: string[];
|
|
16
|
-
claims: Array<{
|
|
17
|
-
type: string;
|
|
18
|
-
subject: string;
|
|
19
|
-
status: string;
|
|
20
|
-
}>;
|
|
21
|
-
}
|
|
22
|
-
export interface FlowTrustArtifactOptions {
|
|
23
|
-
/** Flow claim type the gate expects, e.g. "adversarial.review". */
|
|
24
|
-
claimType: string;
|
|
25
|
-
/** Flow claim subject the gate expects, e.g. "adversarial-pass.review". */
|
|
26
|
-
subject: string;
|
|
27
|
-
/** Producer identity recorded on the artifact, e.g. "survey/adversarial-workbench". */
|
|
28
|
-
producer: string;
|
|
29
|
-
/** Defaults to the review outcome's reviewedAt, then the current time. */
|
|
30
|
-
issuedAt?: string;
|
|
31
|
-
/** Defaults to ["survey:review-outcome/<outcome id>"]. */
|
|
32
|
-
authorityTraces?: string[];
|
|
33
|
-
/** Override the ReviewStatus -> artifact status projection per entry. */
|
|
34
|
-
statusMap?: Partial<Record<ReviewStatus, string>>;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Projects a Survey ReviewOutcome into the neutral trust artifact Flow
|
|
38
|
-
* consumes, so a per-round review (including an adversarial pass) can satisfy
|
|
39
|
-
* or fail a Flow gate without Flow learning Survey vocabulary.
|
|
40
|
-
*/
|
|
41
|
-
export declare function flowTrustArtifactFromReviewOutcome(outcome: ReviewOutcome, options: FlowTrustArtifactOptions): FlowTrustArtifact;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
const defaultStatusMap = {
|
|
2
|
-
verified: "trusted",
|
|
3
|
-
assumed: "assumed",
|
|
4
|
-
proposed: "proposed",
|
|
5
|
-
rejected: "rejected",
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Projects a Survey ReviewOutcome into the neutral trust artifact Flow
|
|
9
|
-
* consumes, so a per-round review (including an adversarial pass) can satisfy
|
|
10
|
-
* or fail a Flow gate without Flow learning Survey vocabulary.
|
|
11
|
-
*/
|
|
12
|
-
export function flowTrustArtifactFromReviewOutcome(outcome, options) {
|
|
13
|
-
if (!outcome.id) {
|
|
14
|
-
throw new Error("flowTrustArtifactFromReviewOutcome requires a review outcome id");
|
|
15
|
-
}
|
|
16
|
-
const statusMap = { ...defaultStatusMap, ...options.statusMap };
|
|
17
|
-
const status = statusMap[outcome.status];
|
|
18
|
-
if (!status) {
|
|
19
|
-
throw new Error(`flowTrustArtifactFromReviewOutcome: unmapped review status '${outcome.status}'`);
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
schema_version: "0.1",
|
|
23
|
-
artifact_type: "trust-report",
|
|
24
|
-
subject: options.subject,
|
|
25
|
-
producer: options.producer,
|
|
26
|
-
status,
|
|
27
|
-
issued_at: options.issuedAt ?? outcome.reviewedAt ?? new Date().toISOString(),
|
|
28
|
-
authority_traces: options.authorityTraces ?? [`survey:review-outcome/${outcome.id}`],
|
|
29
|
-
claims: [{ type: options.claimType, subject: options.subject, status }],
|
|
30
|
-
};
|
|
31
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|