@kontourai/survey 1.1.1 → 1.2.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/README.md +2 -0
- package/dist/src/builder.d.ts +4 -1
- package/dist/src/builder.js +5 -1
- package/dist/src/current-proposed-review-item.d.ts +55 -0
- package/dist/src/current-proposed-review-item.js +64 -0
- package/dist/src/index.d.ts +9 -4
- package/dist/src/index.js +5 -2
- package/dist/src/review-authorizing.d.ts +20 -0
- package/dist/src/review-authorizing.js +27 -0
- package/dist/src/review-resource.d.ts +20 -1
- package/dist/src/review-workbench/producer-decision-mode.d.ts +34 -0
- package/dist/src/review-workbench/producer-decision-mode.js +73 -0
- package/dist/src/review-workbench/review-workbench-data.d.ts +3 -3
- package/dist/src/review-workbench/review-workbench.d.ts +1 -0
- package/dist/src/review-workbench/review-workbench.js +1 -0
- package/dist/src/review-workbench/server-review-session.d.ts +68 -2
- package/dist/src/review-workbench/server-review-session.js +112 -1
- package/dist/src/types.d.ts +16 -0
- package/dist/src/types.js +9 -1
- package/dist/src/vocabulary.d.ts +78 -0
- package/dist/src/vocabulary.js +92 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -96,6 +96,8 @@ One observation, one chain: the page it came from, what the extractor read, who
|
|
|
96
96
|
|
|
97
97
|
Keep producer operational state outside Survey. Queue status, reviewer form state, retries, source caches, and product policy decisions belong in the producer's own data model. Survey carries only the portable evidence chain records needed by Surface.
|
|
98
98
|
|
|
99
|
+
When you build an `authorized-action` authorizing block outside the workbench, pair `buildAuthorizedActionAuthorizing` with `buildPromptRef({ module, component, version?, scheme? })` — `buildPromptRef` formats a well-formed, versioned `promptRef` (bare `"review-workbench/decision-card@v1"` or scheme-prefixed `"survey://<module>/<component>@v1"`) that `buildAuthorizedActionAuthorizing` accepts directly, instead of hand-formatting the string.
|
|
100
|
+
|
|
99
101
|
## Review Workbench embed
|
|
100
102
|
|
|
101
103
|
**Web component** (shadow DOM, no framework required):
|
package/dist/src/builder.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { CandidateSet, ClaimTarget, EscalationRecord, Extraction, Interpretation, RawSource, ReviewOutcome, SurveyInput } from "./types.js";
|
|
1
|
+
import type { CandidateSet, ClaimTarget, EscalationRecord, Extraction, Interpretation, ReviewStatus, RawSource, ReviewOutcome, SurveyInput } from "./types.js";
|
|
2
2
|
export interface SurveyInputBuilderArgs {
|
|
3
3
|
source: string;
|
|
4
4
|
generatedAt?: string;
|
|
5
|
+
contractVersion?: string;
|
|
5
6
|
}
|
|
6
7
|
export interface SurveyClaimRecord {
|
|
7
8
|
rawSource: RawSource;
|
|
@@ -54,6 +55,7 @@ export interface CandidateReviewRecordInput {
|
|
|
54
55
|
export declare class SurveyInputBuilder {
|
|
55
56
|
private readonly source;
|
|
56
57
|
private readonly generatedAt;
|
|
58
|
+
private readonly contractVersion;
|
|
57
59
|
private readonly rawSources;
|
|
58
60
|
private readonly extractions;
|
|
59
61
|
private readonly candidateSets;
|
|
@@ -78,3 +80,4 @@ export declare class SurveyInputBuilder {
|
|
|
78
80
|
private addRecordRawSource;
|
|
79
81
|
}
|
|
80
82
|
export declare function candidateReviewRecord(input: CandidateReviewRecordInput): SurveyClaimRecord[];
|
|
83
|
+
export declare function candidateSetStatusFor(reviewStatus?: ReviewStatus): CandidateSet["status"];
|
package/dist/src/builder.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { SURVEY_INPUT_CONTRACT_VERSION } from "./types.js";
|
|
1
2
|
export class SurveyInputBuilder {
|
|
2
3
|
source;
|
|
3
4
|
generatedAt;
|
|
5
|
+
contractVersion;
|
|
4
6
|
rawSources = new Map();
|
|
5
7
|
extractions = new Map();
|
|
6
8
|
candidateSets = new Map();
|
|
@@ -11,6 +13,7 @@ export class SurveyInputBuilder {
|
|
|
11
13
|
constructor(args) {
|
|
12
14
|
this.source = args.source;
|
|
13
15
|
this.generatedAt = args.generatedAt ?? new Date().toISOString();
|
|
16
|
+
this.contractVersion = args.contractVersion ?? SURVEY_INPUT_CONTRACT_VERSION;
|
|
14
17
|
}
|
|
15
18
|
addRawSource(rawSource) {
|
|
16
19
|
addUnique(this.rawSources, rawSource, "raw source");
|
|
@@ -64,6 +67,7 @@ export class SurveyInputBuilder {
|
|
|
64
67
|
}
|
|
65
68
|
build() {
|
|
66
69
|
return {
|
|
70
|
+
contractVersion: this.contractVersion,
|
|
67
71
|
source: this.source,
|
|
68
72
|
generatedAt: this.generatedAt,
|
|
69
73
|
rawSources: [...this.rawSources.values()],
|
|
@@ -206,7 +210,7 @@ function observationIds(rootId, observation) {
|
|
|
206
210
|
reviewOutcomeId: observation.reviewOutcome?.id ?? `${rootId}.review`,
|
|
207
211
|
};
|
|
208
212
|
}
|
|
209
|
-
function candidateSetStatusFor(reviewStatus) {
|
|
213
|
+
export function candidateSetStatusFor(reviewStatus) {
|
|
210
214
|
if (!reviewStatus || reviewStatus === "proposed")
|
|
211
215
|
return "needs-review";
|
|
212
216
|
return "resolved";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CandidateSetStatus } from "./types.js";
|
|
2
|
+
import { type ClaimTargetHint, type ExtractionReference, type ProducerPolicy, type ReviewItem, type ReviewLocator, type SourceReference, type SurveyRecordProjectionHint } from "./review-resource.js";
|
|
3
|
+
/**
|
|
4
|
+
* A single candidate value for a current/proposed {@link ReviewItem}. Carries
|
|
5
|
+
* the candidate value plus its source, extraction, and claim-target evidence.
|
|
6
|
+
* The builder owns id, role, and candidate-set wiring; the caller owns the
|
|
7
|
+
* value and its domain vocabulary.
|
|
8
|
+
*/
|
|
9
|
+
export interface CurrentProposedCandidateInput {
|
|
10
|
+
readonly value: unknown;
|
|
11
|
+
readonly confidence?: number;
|
|
12
|
+
readonly sourceRank?: number;
|
|
13
|
+
readonly rejectionReason?: string;
|
|
14
|
+
readonly source: SourceReference;
|
|
15
|
+
readonly locator?: ReviewLocator;
|
|
16
|
+
readonly extraction: ExtractionReference;
|
|
17
|
+
readonly claimTarget: ClaimTargetHint;
|
|
18
|
+
readonly projection?: SurveyRecordProjectionHint;
|
|
19
|
+
readonly producer?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Input for {@link currentProposedReviewItem} — the generic envelope, id, and
|
|
23
|
+
* role wiring for a two-candidate current/proposed ReviewItem.
|
|
24
|
+
*/
|
|
25
|
+
export interface CurrentProposedReviewItemInput {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly target: string;
|
|
28
|
+
readonly current: CurrentProposedCandidateInput;
|
|
29
|
+
readonly proposed: CurrentProposedCandidateInput;
|
|
30
|
+
readonly candidateSetStatus?: CandidateSetStatus;
|
|
31
|
+
readonly selectedCandidateRole?: "current" | "proposed";
|
|
32
|
+
readonly rationale?: string;
|
|
33
|
+
readonly labels?: Record<string, string>;
|
|
34
|
+
readonly producerMetadata?: Record<string, unknown>;
|
|
35
|
+
readonly producerPolicy?: ProducerPolicy;
|
|
36
|
+
readonly projection?: SurveyRecordProjectionHint;
|
|
37
|
+
readonly candidateIdSuffix?: {
|
|
38
|
+
readonly current?: string;
|
|
39
|
+
readonly proposed?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Builds the two-candidate current/proposed {@link ReviewItem} envelope that
|
|
44
|
+
* producers otherwise hand-assemble: candidate ids, roles, candidate-set id,
|
|
45
|
+
* observed-candidate count, and selected-candidate mirroring. The caller still
|
|
46
|
+
* owns each candidate's value, source, extraction, and claim-target vocabulary.
|
|
47
|
+
*
|
|
48
|
+
* Candidate ids default to `<name>.current` / `<name>.proposed` (the trailing
|
|
49
|
+
* segment is overridable via `candidateIdSuffix`); the candidate-set id defaults
|
|
50
|
+
* to `<name>.candidates` (overridable via `projection.candidateSetId`);
|
|
51
|
+
* `candidateSetStatus` defaults to `"needs-review"`. `status.observedCandidateCount`
|
|
52
|
+
* is always 2, and `status.selectedCandidateId` mirrors `spec.selectedCandidateId`
|
|
53
|
+
* when `selectedCandidateRole` is set.
|
|
54
|
+
*/
|
|
55
|
+
export declare function currentProposedReviewItem(input: CurrentProposedReviewItemInput): ReviewItem;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { reviewResourceApiVersion, } from "./review-resource.js";
|
|
2
|
+
/**
|
|
3
|
+
* Builds the two-candidate current/proposed {@link ReviewItem} envelope that
|
|
4
|
+
* producers otherwise hand-assemble: candidate ids, roles, candidate-set id,
|
|
5
|
+
* observed-candidate count, and selected-candidate mirroring. The caller still
|
|
6
|
+
* owns each candidate's value, source, extraction, and claim-target vocabulary.
|
|
7
|
+
*
|
|
8
|
+
* Candidate ids default to `<name>.current` / `<name>.proposed` (the trailing
|
|
9
|
+
* segment is overridable via `candidateIdSuffix`); the candidate-set id defaults
|
|
10
|
+
* to `<name>.candidates` (overridable via `projection.candidateSetId`);
|
|
11
|
+
* `candidateSetStatus` defaults to `"needs-review"`. `status.observedCandidateCount`
|
|
12
|
+
* is always 2, and `status.selectedCandidateId` mirrors `spec.selectedCandidateId`
|
|
13
|
+
* when `selectedCandidateRole` is set.
|
|
14
|
+
*/
|
|
15
|
+
export function currentProposedReviewItem(input) {
|
|
16
|
+
const candidateSetId = input.projection?.candidateSetId ?? `${input.name}.candidates`;
|
|
17
|
+
const currentId = `${input.name}.${input.candidateIdSuffix?.current ?? "current"}`;
|
|
18
|
+
const proposedId = `${input.name}.${input.candidateIdSuffix?.proposed ?? "proposed"}`;
|
|
19
|
+
const selectedCandidateId = input.selectedCandidateRole === "current"
|
|
20
|
+
? currentId
|
|
21
|
+
: input.selectedCandidateRole === "proposed"
|
|
22
|
+
? proposedId
|
|
23
|
+
: undefined;
|
|
24
|
+
const currentCandidate = buildCandidate("current", currentId, input.current, candidateSetId);
|
|
25
|
+
const proposedCandidate = buildCandidate("proposed", proposedId, input.proposed, candidateSetId);
|
|
26
|
+
return {
|
|
27
|
+
apiVersion: reviewResourceApiVersion,
|
|
28
|
+
kind: "ReviewItem",
|
|
29
|
+
metadata: {
|
|
30
|
+
name: input.name,
|
|
31
|
+
...(input.labels ? { labels: input.labels } : {}),
|
|
32
|
+
...(input.producerMetadata ? { producer: input.producerMetadata } : {}),
|
|
33
|
+
},
|
|
34
|
+
spec: {
|
|
35
|
+
target: input.target,
|
|
36
|
+
candidates: [currentCandidate, proposedCandidate],
|
|
37
|
+
candidateSetStatus: input.candidateSetStatus ?? "needs-review",
|
|
38
|
+
...(selectedCandidateId ? { selectedCandidateId } : {}),
|
|
39
|
+
...(input.rationale ? { rationale: input.rationale } : {}),
|
|
40
|
+
...(input.producerPolicy ? { producerPolicy: input.producerPolicy } : {}),
|
|
41
|
+
projection: { ...input.projection, candidateSetId },
|
|
42
|
+
},
|
|
43
|
+
status: {
|
|
44
|
+
observedCandidateCount: 2,
|
|
45
|
+
...(selectedCandidateId ? { selectedCandidateId } : {}),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function buildCandidate(role, id, input, candidateSetId) {
|
|
50
|
+
return {
|
|
51
|
+
id,
|
|
52
|
+
role,
|
|
53
|
+
value: input.value,
|
|
54
|
+
...(input.confidence !== undefined ? { confidence: input.confidence } : {}),
|
|
55
|
+
...(input.sourceRank !== undefined ? { sourceRank: input.sourceRank } : {}),
|
|
56
|
+
...(input.rejectionReason ? { rejectionReason: input.rejectionReason } : {}),
|
|
57
|
+
source: input.source,
|
|
58
|
+
...(input.locator ? { locator: input.locator } : {}),
|
|
59
|
+
extraction: input.extraction,
|
|
60
|
+
claimTarget: input.claimTarget,
|
|
61
|
+
projection: { candidateSetId, candidateId: id, ...input.projection },
|
|
62
|
+
...(input.producer ? { producer: input.producer } : {}),
|
|
63
|
+
};
|
|
64
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export type { CandidateSetStatus, Candidate, CandidateSet, ClaimTarget, EscalationDimension, EscalationRecord, Extraction, Interpretation, LocatorScheme, RawSource, RawSourceKind, ReviewAuthorizing, ReviewAuthorizingAuthorizedAction, ReviewAuthorizingExchange, ReviewAuthorizingExplicitStatement, ReviewAuthorizingKind, ReviewOutcome, ReviewStatus, SurveyInput, } from "./types.js";
|
|
2
|
+
export { SURVEY_INPUT_CONTRACT_VERSION } from "./types.js";
|
|
2
3
|
export { reviewResourceApiVersion } from "./review-resource.js";
|
|
3
|
-
export type { CandidateRole, ClaimTargetHint, ExtractionReference, ResourceEnvelope, ResourceMetadata, ReviewActor, ReviewCandidate, ReviewDecision, ReviewDecisionSpec, ReviewDecisionStatus, ReviewItem, ReviewItemSpec, ReviewItemStatus, ReviewLocator, ReviewResource, ReviewResourceApiVersion, ReviewResourceKind, ReviewSession, ReviewSessionEvent, ReviewSessionEventSpec, ReviewSessionEventStatus, ReviewSessionEventType, ReviewSessionSpec, ReviewSessionStatus, SourceReference, SurveyRecordProjectionHint, } from "./review-resource.js";
|
|
4
|
-
export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
|
|
4
|
+
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, SourceReference, SurveyRecordProjectionHint, } from "./review-resource.js";
|
|
5
|
+
export { candidateReviewRecord, candidateSetStatusFor, SurveyInputBuilder } from "./builder.js";
|
|
5
6
|
export type { CandidateReviewRecordInput, SurveyClaimRecord, SurveyInputBuilderArgs, SurveyObservationInput, } from "./builder.js";
|
|
6
7
|
export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js";
|
|
7
8
|
export type { ReviewedCandidateResolutionInput } from "./reviewed-candidate-resolution.js";
|
|
@@ -29,7 +30,11 @@ export { referenceUtteranceExtractor, surveyAgentUtterance, utteranceToSurveyInp
|
|
|
29
30
|
export type { ExtractedStatement, StatementBadge, UtteranceClaimExtractor, UtteranceStatement, UtteranceStatementRecords, UtteranceTrustReport, } from "./agent-utterance.js";
|
|
30
31
|
export { mappingReviewToSurface, referenceSchemaExtractor, surveySchemaMapping, } from "./schema-mapping.js";
|
|
31
32
|
export type { MappingProposalRecord, ReviewedMapping, SchemaMappingExtractor, SchemaMappingOptions, SystemFieldRef, } from "./schema-mapping.js";
|
|
32
|
-
export { buildAuthorizedActionAuthorizing, isValidAuthorizing, validateAuthorizing } from "./review-authorizing.js";
|
|
33
|
-
export type { BuildAuthorizedActionAuthorizingInput, ReviewAuthorizingIssue, ReviewAuthorizingIssueCode, } from "./review-authorizing.js";
|
|
33
|
+
export { buildAuthorizedActionAuthorizing, buildPromptRef, isValidAuthorizing, validateAuthorizing } from "./review-authorizing.js";
|
|
34
|
+
export type { BuildAuthorizedActionAuthorizingInput, BuildPromptRefInput, ReviewAuthorizingIssue, ReviewAuthorizingIssueCode, } from "./review-authorizing.js";
|
|
35
|
+
export { confidenceBasisForReview, defineProductVocabulary, stableId } from "./vocabulary.js";
|
|
36
|
+
export type { ConfidenceBasisForReviewInput, ProductVocabularyDefinition, } from "./vocabulary.js";
|
|
37
|
+
export { currentProposedReviewItem } from "./current-proposed-review-item.js";
|
|
38
|
+
export type { CurrentProposedCandidateInput, CurrentProposedReviewItemInput, } from "./current-proposed-review-item.js";
|
|
34
39
|
export { deriveOversightMetrics, mergeTrustBundleWithOversightMetrics, oversightMetricsToClaims, } from "./oversight-metrics.js";
|
|
35
40
|
export type { AggregateOversightMetrics, DeriveOversightMetricsOptions, OversightMetrics, OversightMetricsClaimsSubject, OversightQualityClaim, ReviewerOversightMetrics, } from "./oversight-metrics.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { SURVEY_INPUT_CONTRACT_VERSION } from "./types.js";
|
|
1
2
|
export { reviewResourceApiVersion } from "./review-resource.js";
|
|
2
|
-
export { candidateReviewRecord, SurveyInputBuilder } from "./builder.js";
|
|
3
|
+
export { candidateReviewRecord, candidateSetStatusFor, SurveyInputBuilder } from "./builder.js";
|
|
3
4
|
export { reviewedCandidateResolution } from "./reviewed-candidate-resolution.js";
|
|
4
5
|
export { reviewedCurrentProposedResolution } from "./reviewed-current-proposed-resolution.js";
|
|
5
6
|
export { buildSurveyTrustBundle } from "./to-surface.js";
|
|
@@ -13,5 +14,7 @@ export { apiRecordSource, manualEntrySource, policyStandardSource, uploadedDocum
|
|
|
13
14
|
export { applyAutoAcceptPolicy, applyMappingReview, buildMappingReviewItems, lookupMapping, lookupRejectedMapping, normalizeQuestion, proposalsToCandidateSet, referenceMappingProposer, resolveQuestion, } from "./inquiry-mapping.js";
|
|
14
15
|
export { referenceUtteranceExtractor, surveyAgentUtterance, utteranceToSurveyInput, } from "./agent-utterance.js";
|
|
15
16
|
export { mappingReviewToSurface, referenceSchemaExtractor, surveySchemaMapping, } from "./schema-mapping.js";
|
|
16
|
-
export { buildAuthorizedActionAuthorizing, isValidAuthorizing, validateAuthorizing } from "./review-authorizing.js";
|
|
17
|
+
export { buildAuthorizedActionAuthorizing, buildPromptRef, isValidAuthorizing, validateAuthorizing } from "./review-authorizing.js";
|
|
18
|
+
export { confidenceBasisForReview, defineProductVocabulary, stableId } from "./vocabulary.js";
|
|
19
|
+
export { currentProposedReviewItem } from "./current-proposed-review-item.js";
|
|
17
20
|
export { deriveOversightMetrics, mergeTrustBundleWithOversightMetrics, oversightMetricsToClaims, } from "./oversight-metrics.js";
|
|
@@ -37,3 +37,23 @@ export interface BuildAuthorizedActionAuthorizingInput {
|
|
|
37
37
|
* runs validateAuthorizing and degrades gracefully instead of throwing.
|
|
38
38
|
*/
|
|
39
39
|
export declare function buildAuthorizedActionAuthorizing(input: BuildAuthorizedActionAuthorizingInput): ReviewAuthorizingAuthorizedAction;
|
|
40
|
+
export interface BuildPromptRefInput {
|
|
41
|
+
readonly module: string;
|
|
42
|
+
readonly component: string;
|
|
43
|
+
readonly version?: string;
|
|
44
|
+
readonly scheme?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Builds a well-formed `promptRef` for an `authorized-action` authorizing block,
|
|
48
|
+
* generalizing the workbench's `<module>/<component>@<version>` convention.
|
|
49
|
+
*
|
|
50
|
+
* Without a `scheme` the result is bare (`"review-workbench/decision-card@v1"`),
|
|
51
|
+
* matching the workbench's own internal prompt refs. With a `scheme` the result
|
|
52
|
+
* is prefixed (`"survey://rules-admin/keep-current@v1"`) for producers that
|
|
53
|
+
* namespace their prompt refs. `version` defaults to `"v1"`.
|
|
54
|
+
*
|
|
55
|
+
* The returned string is a valid `promptRef` input to
|
|
56
|
+
* {@link buildAuthorizedActionAuthorizing}. Throws on an empty `module` or
|
|
57
|
+
* `component`, mirroring that helper's throw-on-invalid-input style.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildPromptRef(input: BuildPromptRefInput): string;
|
|
@@ -123,3 +123,30 @@ export function buildAuthorizedActionAuthorizing(input) {
|
|
|
123
123
|
}
|
|
124
124
|
return block;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Builds a well-formed `promptRef` for an `authorized-action` authorizing block,
|
|
128
|
+
* generalizing the workbench's `<module>/<component>@<version>` convention.
|
|
129
|
+
*
|
|
130
|
+
* Without a `scheme` the result is bare (`"review-workbench/decision-card@v1"`),
|
|
131
|
+
* matching the workbench's own internal prompt refs. With a `scheme` the result
|
|
132
|
+
* is prefixed (`"survey://rules-admin/keep-current@v1"`) for producers that
|
|
133
|
+
* namespace their prompt refs. `version` defaults to `"v1"`.
|
|
134
|
+
*
|
|
135
|
+
* The returned string is a valid `promptRef` input to
|
|
136
|
+
* {@link buildAuthorizedActionAuthorizing}. Throws on an empty `module` or
|
|
137
|
+
* `component`, mirroring that helper's throw-on-invalid-input style.
|
|
138
|
+
*/
|
|
139
|
+
export function buildPromptRef(input) {
|
|
140
|
+
const module = input.module?.trim();
|
|
141
|
+
const component = input.component?.trim();
|
|
142
|
+
if (!module) {
|
|
143
|
+
throw new Error("buildPromptRef: 'module' must be a non-empty string.");
|
|
144
|
+
}
|
|
145
|
+
if (!component) {
|
|
146
|
+
throw new Error("buildPromptRef: 'component' must be a non-empty string.");
|
|
147
|
+
}
|
|
148
|
+
const version = input.version?.trim() || "v1";
|
|
149
|
+
const scheme = input.scheme?.trim();
|
|
150
|
+
const ref = `${module}/${component}@${version}`;
|
|
151
|
+
return scheme ? `${scheme}://${ref}` : ref;
|
|
152
|
+
}
|
|
@@ -59,6 +59,25 @@ export interface SurveyRecordProjectionHint {
|
|
|
59
59
|
reviewOutcomeId?: ReviewOutcome["id"];
|
|
60
60
|
claimId?: ClaimTarget["id"];
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Well-known decision-mode vocabulary for {@link ProducerPolicy.decisionMode}.
|
|
64
|
+
* A producer declares how a ReviewItem is allowed to be resolved:
|
|
65
|
+
* `keep-current` — only a keep-current decision is admissible.
|
|
66
|
+
* `current-proposed` — only the current or proposed candidate may be selected.
|
|
67
|
+
* `free-select` — any declared candidate may be selected.
|
|
68
|
+
* Enforcement is opt-in; see `assertReviewDecisionModeAllows` and the
|
|
69
|
+
* `enforceProducerPolicy` option on `applyReviewSession`.
|
|
70
|
+
*/
|
|
71
|
+
export type ReviewDecisionMode = "keep-current" | "current-proposed" | "free-select";
|
|
72
|
+
/**
|
|
73
|
+
* Producer-declared policy carried on a ReviewItem. The well-known
|
|
74
|
+
* `decisionMode` sub-key is typed and optionally enforceable; all other keys
|
|
75
|
+
* remain opaque to Survey (tolerated via the index signature, never inspected).
|
|
76
|
+
*/
|
|
77
|
+
export interface ProducerPolicy {
|
|
78
|
+
decisionMode?: ReviewDecisionMode;
|
|
79
|
+
[key: string]: unknown;
|
|
80
|
+
}
|
|
62
81
|
export interface ReviewCandidate {
|
|
63
82
|
id: string;
|
|
64
83
|
role?: CandidateRole;
|
|
@@ -79,7 +98,7 @@ export interface ReviewItemSpec {
|
|
|
79
98
|
candidateSetStatus?: CandidateSetStatus;
|
|
80
99
|
selectedCandidateId?: string;
|
|
81
100
|
rationale?: string;
|
|
82
|
-
producerPolicy?:
|
|
101
|
+
producerPolicy?: ProducerPolicy;
|
|
83
102
|
projection?: SurveyRecordProjectionHint;
|
|
84
103
|
}
|
|
85
104
|
export interface ReviewItemStatus {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReviewItem } from "../review-resource.js";
|
|
2
|
+
import type { ReviewWorkbenchResult } from "./review-workbench.js";
|
|
3
|
+
export type ReviewDecisionModeIssueCode = "unknown-decision-mode" | "decision-not-allowed" | "candidate-not-in-item";
|
|
4
|
+
export interface ReviewDecisionModeIssue {
|
|
5
|
+
readonly code: ReviewDecisionModeIssueCode;
|
|
6
|
+
readonly message: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The subset of a {@link ReviewWorkbenchResult} a decision-mode check inspects.
|
|
10
|
+
*/
|
|
11
|
+
export type ReviewDecisionModeResult = Pick<ReviewWorkbenchResult, "decision" | "selectedCandidateId" | "selectedCandidateRole">;
|
|
12
|
+
/**
|
|
13
|
+
* Validates a review result against the item's declared `producerPolicy.decisionMode`.
|
|
14
|
+
*
|
|
15
|
+
* Returns an empty array when the item declares no `producerPolicy` or no
|
|
16
|
+
* `decisionMode` (the default, un-enforced posture). For a known mode it checks:
|
|
17
|
+
* `keep-current` — only a `keep-current` decision is admissible.
|
|
18
|
+
* `current-proposed` — only the current or proposed candidate role may be selected.
|
|
19
|
+
* `free-select` — the selected candidate must be one declared on the item.
|
|
20
|
+
* An unrecognized `decisionMode` string fails closed with a single
|
|
21
|
+
* `unknown-decision-mode` issue.
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateReviewDecisionMode(item: ReviewItem, result: ReviewDecisionModeResult): ReviewDecisionModeIssue[];
|
|
24
|
+
export declare class DecisionModeViolationError extends Error {
|
|
25
|
+
readonly name = "DecisionModeViolationError";
|
|
26
|
+
readonly issues: readonly ReviewDecisionModeIssue[];
|
|
27
|
+
constructor(reviewItemName: string, issues: readonly ReviewDecisionModeIssue[]);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Asserts a review result satisfies the item's declared decision mode, throwing
|
|
31
|
+
* a {@link DecisionModeViolationError} otherwise. Mirrors the
|
|
32
|
+
* validate-then-assert idiom in `review-authorizing.ts`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function assertReviewDecisionModeAllows(item: ReviewItem, result: ReviewDecisionModeResult): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const KNOWN_DECISION_MODES = new Set([
|
|
2
|
+
"keep-current",
|
|
3
|
+
"current-proposed",
|
|
4
|
+
"free-select",
|
|
5
|
+
]);
|
|
6
|
+
/**
|
|
7
|
+
* Validates a review result against the item's declared `producerPolicy.decisionMode`.
|
|
8
|
+
*
|
|
9
|
+
* Returns an empty array when the item declares no `producerPolicy` or no
|
|
10
|
+
* `decisionMode` (the default, un-enforced posture). For a known mode it checks:
|
|
11
|
+
* `keep-current` — only a `keep-current` decision is admissible.
|
|
12
|
+
* `current-proposed` — only the current or proposed candidate role may be selected.
|
|
13
|
+
* `free-select` — the selected candidate must be one declared on the item.
|
|
14
|
+
* An unrecognized `decisionMode` string fails closed with a single
|
|
15
|
+
* `unknown-decision-mode` issue.
|
|
16
|
+
*/
|
|
17
|
+
export function validateReviewDecisionMode(item, result) {
|
|
18
|
+
const decisionMode = item.spec.producerPolicy?.decisionMode;
|
|
19
|
+
if (decisionMode === undefined) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
if (!KNOWN_DECISION_MODES.has(decisionMode)) {
|
|
23
|
+
return [{
|
|
24
|
+
code: "unknown-decision-mode",
|
|
25
|
+
message: `ReviewItem ${item.metadata.name} declares an unrecognized producerPolicy.decisionMode '${String(decisionMode)}'.`,
|
|
26
|
+
}];
|
|
27
|
+
}
|
|
28
|
+
if (decisionMode === "keep-current") {
|
|
29
|
+
if (result.decision !== "keep-current") {
|
|
30
|
+
return [{
|
|
31
|
+
code: "decision-not-allowed",
|
|
32
|
+
message: `ReviewItem ${item.metadata.name} declares decisionMode 'keep-current'; decision '${result.decision}' is not allowed.`,
|
|
33
|
+
}];
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
if (decisionMode === "current-proposed") {
|
|
38
|
+
if (result.selectedCandidateRole !== "current" && result.selectedCandidateRole !== "proposed") {
|
|
39
|
+
return [{
|
|
40
|
+
code: "decision-not-allowed",
|
|
41
|
+
message: `ReviewItem ${item.metadata.name} declares decisionMode 'current-proposed'; selected candidate role '${result.selectedCandidateRole ?? "unknown"}' is not current or proposed.`,
|
|
42
|
+
}];
|
|
43
|
+
}
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
// free-select: the selected candidate must be one declared on the item.
|
|
47
|
+
if (!item.spec.candidates.some((candidate) => candidate.id === result.selectedCandidateId)) {
|
|
48
|
+
return [{
|
|
49
|
+
code: "candidate-not-in-item",
|
|
50
|
+
message: `ReviewItem ${item.metadata.name} declares decisionMode 'free-select'; selected candidate '${result.selectedCandidateId}' is not declared on the item.`,
|
|
51
|
+
}];
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
export class DecisionModeViolationError extends Error {
|
|
56
|
+
name = "DecisionModeViolationError";
|
|
57
|
+
issues;
|
|
58
|
+
constructor(reviewItemName, issues) {
|
|
59
|
+
super(`ReviewItem ${reviewItemName} violates its declared producerPolicy.decisionMode: ${issues.map((issue) => issue.message).join(" ")}`);
|
|
60
|
+
this.issues = issues;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Asserts a review result satisfies the item's declared decision mode, throwing
|
|
65
|
+
* a {@link DecisionModeViolationError} otherwise. Mirrors the
|
|
66
|
+
* validate-then-assert idiom in `review-authorizing.ts`.
|
|
67
|
+
*/
|
|
68
|
+
export function assertReviewDecisionModeAllows(item, result) {
|
|
69
|
+
const issues = validateReviewDecisionMode(item, result);
|
|
70
|
+
if (issues.length > 0) {
|
|
71
|
+
throw new DecisionModeViolationError(item.metadata.name, issues);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -152,7 +152,7 @@ export declare const regulatedRuleConflictReviewItemExample: {
|
|
|
152
152
|
candidateSetId: string;
|
|
153
153
|
};
|
|
154
154
|
producerPolicy: {
|
|
155
|
-
decisionMode:
|
|
155
|
+
decisionMode: "keep-current";
|
|
156
156
|
sourceAuthorityProjection: string;
|
|
157
157
|
feedbackTags: string[];
|
|
158
158
|
};
|
|
@@ -290,7 +290,7 @@ export declare const facilityCredentialReviewItemExample: {
|
|
|
290
290
|
candidateSetId: string;
|
|
291
291
|
};
|
|
292
292
|
producerPolicy: {
|
|
293
|
-
decisionMode:
|
|
293
|
+
decisionMode: "current-proposed";
|
|
294
294
|
sourceAuthorityProjection: string;
|
|
295
295
|
feedbackTags: string[];
|
|
296
296
|
};
|
|
@@ -577,7 +577,7 @@ export declare const reviewWorkbenchQueueExamples: (ReviewItem | {
|
|
|
577
577
|
candidateSetId: string;
|
|
578
578
|
};
|
|
579
579
|
producerPolicy: {
|
|
580
|
-
decisionMode:
|
|
580
|
+
decisionMode: "keep-current";
|
|
581
581
|
sourceAuthorityProjection: string;
|
|
582
582
|
feedbackTags: string[];
|
|
583
583
|
};
|
|
@@ -6,6 +6,7 @@ export { buildReviewSessionEvents, buildReviewSessionEvent, buildReviewSessionRe
|
|
|
6
6
|
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, type ReviewCandidatePresentation, type ReviewCandidatePresentationContext, type ReviewItemPresentation, type ReviewItemPresentationContext, type ReviewPresentationAdapter, type ReviewPresentationLink, type ReviewResultPresentation, type ReviewTracePresentationContext, type ReviewTraceRef, type ReviewValuePresentationContext, } from "./review-presentation.js";
|
|
7
7
|
export { buildSurfaceProjectionPreview, type PreviewAuthorityTrace, type PreviewCandidateHistory, type PreviewClaim, type PreviewIntegrityPosture, type PreviewReviewEvent, type PreviewSourceAuthority, type PreviewSourceEvidence, type SurfaceProjectionPreview, } from "./review-surface-preview.js";
|
|
8
8
|
export { validateReviewSessionEventsForSnapshot, type ReviewSessionReplayIssue, type ReviewSessionReplayIssueCode, } from "./review-session-replay.js";
|
|
9
|
+
export { assertReviewDecisionModeAllows, DecisionModeViolationError, validateReviewDecisionMode, type ReviewDecisionModeIssue, type ReviewDecisionModeIssueCode, type ReviewDecisionModeResult, } from "./producer-decision-mode.js";
|
|
9
10
|
export declare function buildReviewDecision(state: ReviewWorkbenchState): ReviewDecision | undefined;
|
|
10
11
|
export interface ReviewWorkbenchSessionExport {
|
|
11
12
|
readonly session: ReviewSession;
|
|
@@ -10,6 +10,7 @@ export { buildReviewSessionEvents, buildReviewSessionEvent, buildReviewSessionRe
|
|
|
10
10
|
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-presentation.js";
|
|
11
11
|
export { buildSurfaceProjectionPreview, } from "./review-surface-preview.js";
|
|
12
12
|
export { validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
13
|
+
export { assertReviewDecisionModeAllows, DecisionModeViolationError, validateReviewDecisionMode, } from "./producer-decision-mode.js";
|
|
13
14
|
export function buildReviewDecision(state) {
|
|
14
15
|
if (!state.decision) {
|
|
15
16
|
return undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type DeriveReviewSessionApplyResultForSnapshotResult, type ReviewSessionApplyResolutionRequirement } from "./review-workbench.js";
|
|
1
|
+
import { type DeriveReviewSessionApplyResultForSnapshotResult, type MapReviewWorkbenchResultsToApplyActionsOptions, type ReviewApplyActionIssue, type ReviewApplyActionMapping, type ReviewSessionApplyIssue, type ReviewSessionApplyResolutionRequirement, type ReviewWorkbenchResult } from "./review-workbench.js";
|
|
2
|
+
import { type ReviewDecisionModeIssue } from "./producer-decision-mode.js";
|
|
2
3
|
import { type ReviewSessionReplayIssue } from "./review-session-replay.js";
|
|
3
|
-
import type { ReviewSessionEvent } from "../review-resource.js";
|
|
4
|
+
import type { ReviewDecision, ReviewSessionEvent } from "../review-resource.js";
|
|
4
5
|
import type { ReviewQueueSessionState } from "./review-queue-session.js";
|
|
5
6
|
export interface ServerReviewSessionRecord {
|
|
6
7
|
readonly sessionName: string;
|
|
@@ -63,3 +64,68 @@ export declare function assertServerReviewSessionFreshness(record: ServerReviewS
|
|
|
63
64
|
export declare function validateServerReviewSessionEvents(record: ServerReviewSessionRecord, events: readonly ReviewSessionEvent[]): ServerReviewSessionEventValidationIssue[];
|
|
64
65
|
export declare function assertServerReviewSessionEvents(record: ServerReviewSessionRecord, events: readonly ReviewSessionEvent[]): void;
|
|
65
66
|
export declare function deriveServerReviewSessionApplyResult(options: DeriveServerReviewSessionApplyResultOptions): DeriveReviewSessionApplyResultForSnapshotResult;
|
|
67
|
+
export interface ApplyReviewSessionOptions<TAction = never> {
|
|
68
|
+
/** Pre-built server record. When omitted, one is built from `snapshot`. */
|
|
69
|
+
readonly record?: ServerReviewSessionRecord;
|
|
70
|
+
/** Reviewed snapshot to build a server record from when `record` is omitted. */
|
|
71
|
+
readonly snapshot?: ReviewQueueSessionState;
|
|
72
|
+
/** Session name for the built record; falls back to the first event's session name. */
|
|
73
|
+
readonly sessionName?: string;
|
|
74
|
+
/** `updatedAt` stamp for the built record. */
|
|
75
|
+
readonly recordUpdatedAt?: string | Date;
|
|
76
|
+
readonly events: readonly ReviewSessionEvent[];
|
|
77
|
+
readonly currentSnapshot?: ReviewQueueSessionState;
|
|
78
|
+
readonly currentEventCount?: number;
|
|
79
|
+
readonly requiredResolvedItems?: ReviewSessionApplyResolutionRequirement;
|
|
80
|
+
/** When true, each result is checked against its item's producerPolicy.decisionMode. */
|
|
81
|
+
readonly enforceProducerPolicy?: boolean;
|
|
82
|
+
/** When provided, results are mapped to product apply actions in the same call. */
|
|
83
|
+
readonly mapActions?: Omit<MapReviewWorkbenchResultsToApplyActionsOptions<TAction>, "results" | "items">;
|
|
84
|
+
}
|
|
85
|
+
export type ApplyReviewSessionIssue = ReviewSessionApplyIssue | {
|
|
86
|
+
readonly code: "stale-session";
|
|
87
|
+
readonly message: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly code: "invalid-events";
|
|
90
|
+
readonly message: string;
|
|
91
|
+
} | {
|
|
92
|
+
readonly code: "decision-mode-violation";
|
|
93
|
+
readonly reviewItemName: string;
|
|
94
|
+
readonly message: string;
|
|
95
|
+
readonly issues: readonly ReviewDecisionModeIssue[];
|
|
96
|
+
} | {
|
|
97
|
+
readonly code: "action-mapping-failed";
|
|
98
|
+
readonly message: string;
|
|
99
|
+
readonly issues: readonly ReviewApplyActionIssue[];
|
|
100
|
+
};
|
|
101
|
+
export type ApplyReviewSessionResult<TAction = never> = {
|
|
102
|
+
readonly ok: true;
|
|
103
|
+
readonly issues: readonly [];
|
|
104
|
+
readonly decisions: readonly ReviewDecision[];
|
|
105
|
+
readonly results: readonly ReviewWorkbenchResult[];
|
|
106
|
+
readonly actions: readonly ReviewApplyActionMapping<TAction>[];
|
|
107
|
+
readonly replayedSession: ReviewQueueSessionState;
|
|
108
|
+
} | {
|
|
109
|
+
readonly ok: false;
|
|
110
|
+
readonly issues: readonly ApplyReviewSessionIssue[];
|
|
111
|
+
readonly decisions: readonly ReviewDecision[];
|
|
112
|
+
readonly results: readonly ReviewWorkbenchResult[];
|
|
113
|
+
readonly actions: readonly ReviewApplyActionMapping<TAction>[];
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* One-call server apply: collapses the "resolve server record → derive apply
|
|
117
|
+
* result → normalize freshness/event errors → (optionally) enforce
|
|
118
|
+
* producerPolicy.decisionMode → (optionally) map results to product actions"
|
|
119
|
+
* choreography into a single call.
|
|
120
|
+
*
|
|
121
|
+
* Returns a discriminated `{ ok }` result rather than throwing for expected
|
|
122
|
+
* failure modes (stale session, invalid events, unresolved items, decision-mode
|
|
123
|
+
* violations, action-mapping failures) — matching the
|
|
124
|
+
* `deriveReviewSessionApplyResultForSnapshot` idiom. Unexpected errors still
|
|
125
|
+
* propagate.
|
|
126
|
+
*
|
|
127
|
+
* Supply either a pre-built `record` or a `snapshot` (a record is then built via
|
|
128
|
+
* `createServerReviewSessionRecord`). `enforceProducerPolicy` is off by default;
|
|
129
|
+
* unset `producerPolicy`/`decisionMode` never changes behavior.
|
|
130
|
+
*/
|
|
131
|
+
export declare function applyReviewSession<TAction = never>(options: ApplyReviewSessionOptions<TAction>): ApplyReviewSessionResult<TAction>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { deriveReviewSessionApplyResultForSnapshot, } from "./review-workbench.js";
|
|
2
|
+
import { deriveReviewSessionApplyResultForSnapshot, mapReviewWorkbenchResultsToApplyActions, ReviewApplyActionMappingError, } from "./review-workbench.js";
|
|
3
|
+
import { validateReviewDecisionMode, } from "./producer-decision-mode.js";
|
|
3
4
|
import { validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
4
5
|
import { canonicalJson } from "./canonical.js";
|
|
5
6
|
export class StaleServerReviewSessionError extends Error {
|
|
@@ -114,3 +115,113 @@ function sha256(value) {
|
|
|
114
115
|
function isoTimestamp(value) {
|
|
115
116
|
return value instanceof Date ? value.toISOString() : value;
|
|
116
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* One-call server apply: collapses the "resolve server record → derive apply
|
|
120
|
+
* result → normalize freshness/event errors → (optionally) enforce
|
|
121
|
+
* producerPolicy.decisionMode → (optionally) map results to product actions"
|
|
122
|
+
* choreography into a single call.
|
|
123
|
+
*
|
|
124
|
+
* Returns a discriminated `{ ok }` result rather than throwing for expected
|
|
125
|
+
* failure modes (stale session, invalid events, unresolved items, decision-mode
|
|
126
|
+
* violations, action-mapping failures) — matching the
|
|
127
|
+
* `deriveReviewSessionApplyResultForSnapshot` idiom. Unexpected errors still
|
|
128
|
+
* propagate.
|
|
129
|
+
*
|
|
130
|
+
* Supply either a pre-built `record` or a `snapshot` (a record is then built via
|
|
131
|
+
* `createServerReviewSessionRecord`). `enforceProducerPolicy` is off by default;
|
|
132
|
+
* unset `producerPolicy`/`decisionMode` never changes behavior.
|
|
133
|
+
*/
|
|
134
|
+
export function applyReviewSession(options) {
|
|
135
|
+
const record = resolveApplyReviewSessionRecord(options);
|
|
136
|
+
let derived;
|
|
137
|
+
try {
|
|
138
|
+
derived = deriveServerReviewSessionApplyResult({
|
|
139
|
+
record,
|
|
140
|
+
events: options.events,
|
|
141
|
+
currentSnapshot: options.currentSnapshot,
|
|
142
|
+
currentEventCount: options.currentEventCount,
|
|
143
|
+
requiredResolvedItems: options.requiredResolvedItems,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (error instanceof StaleServerReviewSessionError) {
|
|
148
|
+
return { ok: false, issues: [{ code: "stale-session", message: error.message }], decisions: [], results: [], actions: [] };
|
|
149
|
+
}
|
|
150
|
+
if (error instanceof ServerReviewSessionEventValidationError) {
|
|
151
|
+
return { ok: false, issues: [{ code: "invalid-events", message: error.message }], decisions: [], results: [], actions: [] };
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
if (!derived.ok) {
|
|
156
|
+
return { ok: false, issues: derived.issues, decisions: derived.decisions, results: derived.results, actions: [] };
|
|
157
|
+
}
|
|
158
|
+
if (options.enforceProducerPolicy) {
|
|
159
|
+
const itemsByName = new Map(record.snapshot.items.map((item) => [item.metadata.name, item]));
|
|
160
|
+
const violations = [];
|
|
161
|
+
for (const result of derived.results) {
|
|
162
|
+
const item = itemsByName.get(result.reviewItemName);
|
|
163
|
+
if (!item) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const issues = validateReviewDecisionMode(item, result);
|
|
167
|
+
if (issues.length > 0) {
|
|
168
|
+
violations.push({
|
|
169
|
+
code: "decision-mode-violation",
|
|
170
|
+
reviewItemName: result.reviewItemName,
|
|
171
|
+
message: issues.map((issue) => issue.message).join(" "),
|
|
172
|
+
issues,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (violations.length > 0) {
|
|
177
|
+
return { ok: false, issues: violations, decisions: derived.decisions, results: derived.results, actions: [] };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
let actions = [];
|
|
181
|
+
if (options.mapActions) {
|
|
182
|
+
try {
|
|
183
|
+
actions = mapReviewWorkbenchResultsToApplyActions({
|
|
184
|
+
...options.mapActions,
|
|
185
|
+
results: derived.results,
|
|
186
|
+
items: record.snapshot.items,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (error instanceof ReviewApplyActionMappingError) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
issues: [{ code: "action-mapping-failed", message: error.message, issues: error.issues }],
|
|
194
|
+
decisions: derived.decisions,
|
|
195
|
+
results: derived.results,
|
|
196
|
+
actions: [],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
issues: [],
|
|
205
|
+
decisions: derived.decisions,
|
|
206
|
+
results: derived.results,
|
|
207
|
+
actions,
|
|
208
|
+
replayedSession: derived.replayedSession,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function resolveApplyReviewSessionRecord(options) {
|
|
212
|
+
if (options.record) {
|
|
213
|
+
return options.record;
|
|
214
|
+
}
|
|
215
|
+
if (!options.snapshot) {
|
|
216
|
+
throw new Error("applyReviewSession requires either a server `record` or a `snapshot` to build one.");
|
|
217
|
+
}
|
|
218
|
+
const sessionName = options.sessionName ?? options.events[0]?.spec.sessionName;
|
|
219
|
+
if (!sessionName) {
|
|
220
|
+
throw new Error("applyReviewSession requires a `sessionName` (or a `record`, or events that name their session) to build a server record.");
|
|
221
|
+
}
|
|
222
|
+
return createServerReviewSessionRecord({
|
|
223
|
+
sessionName,
|
|
224
|
+
snapshot: options.snapshot,
|
|
225
|
+
updatedAt: options.recordUpdatedAt,
|
|
226
|
+
});
|
|
227
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -129,7 +129,23 @@ export interface ClaimTarget {
|
|
|
129
129
|
eventMethod?: string;
|
|
130
130
|
metadata?: Record<string, unknown>;
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Shape-contract version for the flat record arrays in a {@link SurveyInput}
|
|
134
|
+
* batch (`rawSources`, `extractions`, `candidateSets`, `reviewOutcomes`,
|
|
135
|
+
* `claims`). Stamped by {@link SurveyInputBuilder.build} when a batch does not
|
|
136
|
+
* set one of its own. See `docs/record-contracts.md` for the compatibility
|
|
137
|
+
* policy — a future breaking change to any of those record shapes must bump
|
|
138
|
+
* this constant and document the migration before release.
|
|
139
|
+
*/
|
|
140
|
+
export declare const SURVEY_INPUT_CONTRACT_VERSION = "1";
|
|
132
141
|
export interface SurveyInput {
|
|
142
|
+
/**
|
|
143
|
+
* Identifies the shape contract for the record arrays in this batch. Optional
|
|
144
|
+
* and defaults to {@link SURVEY_INPUT_CONTRACT_VERSION} when omitted, so
|
|
145
|
+
* batches built before this field existed remain valid. Compatibility marker
|
|
146
|
+
* only — Survey does not gate records on it. See `docs/record-contracts.md`.
|
|
147
|
+
*/
|
|
148
|
+
contractVersion?: string;
|
|
133
149
|
source: string;
|
|
134
150
|
generatedAt: string;
|
|
135
151
|
rawSources: RawSource[];
|
package/dist/src/types.js
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Shape-contract version for the flat record arrays in a {@link SurveyInput}
|
|
3
|
+
* batch (`rawSources`, `extractions`, `candidateSets`, `reviewOutcomes`,
|
|
4
|
+
* `claims`). Stamped by {@link SurveyInputBuilder.build} when a batch does not
|
|
5
|
+
* set one of its own. See `docs/record-contracts.md` for the compatibility
|
|
6
|
+
* policy — a future breaking change to any of those record shapes must bump
|
|
7
|
+
* this constant and document the migration before release.
|
|
8
|
+
*/
|
|
9
|
+
export const SURVEY_INPUT_CONTRACT_VERSION = "1";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ConfidenceBasis, ImpactLevel, TrustStatus } from "@kontourai/surface";
|
|
2
|
+
/**
|
|
3
|
+
* Builds a stable, url-safe identifier from ordered parts. Each part is
|
|
4
|
+
* lowercased, non-alphanumeric runs collapse to a single hyphen, leading and
|
|
5
|
+
* trailing hyphens are trimmed, and parts join with a dot.
|
|
6
|
+
*
|
|
7
|
+
* `stableId(["Public Record", "entity-123", "current"])` → `"public-record.entity-123.current"`.
|
|
8
|
+
*
|
|
9
|
+
* Producers use this to derive deterministic candidate, candidate-set, and claim
|
|
10
|
+
* identifiers from domain values without inventing their own slug helper.
|
|
11
|
+
*/
|
|
12
|
+
export declare function stableId(parts: ReadonlyArray<string | number>): string;
|
|
13
|
+
/**
|
|
14
|
+
* A product's Survey/Surface vocabulary: the subject type and surface it
|
|
15
|
+
* projects onto, its claim-type names, and its decision-effect names. Generic
|
|
16
|
+
* over the caller's claim-type and decision-effect key maps so the concrete
|
|
17
|
+
* string literals stay visible to callers.
|
|
18
|
+
*/
|
|
19
|
+
export interface ProductVocabularyDefinition<TClaimTypes extends Readonly<Record<string, string>>, TDecisionEffects extends Readonly<Record<string, string>>> {
|
|
20
|
+
readonly subjectType: string;
|
|
21
|
+
readonly surface: string;
|
|
22
|
+
readonly claimTypes: TClaimTypes;
|
|
23
|
+
readonly decisionEffects: TDecisionEffects;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Defines a product vocabulary as a deep-frozen, discoverable value that a
|
|
27
|
+
* `currentProposedReviewItem` caller can pass instead of loose top-level
|
|
28
|
+
* constants. Returns the same shape it received, frozen so callers cannot
|
|
29
|
+
* mutate a shared vocabulary at runtime.
|
|
30
|
+
*/
|
|
31
|
+
export declare function defineProductVocabulary<TClaimTypes extends Readonly<Record<string, string>>, TDecisionEffects extends Readonly<Record<string, string>>>(definition: ProductVocabularyDefinition<TClaimTypes, TDecisionEffects>): ProductVocabularyDefinition<TClaimTypes, TDecisionEffects>;
|
|
32
|
+
export interface ConfidenceBasisForReviewInput {
|
|
33
|
+
readonly status: TrustStatus;
|
|
34
|
+
readonly impactLevel: ImpactLevel;
|
|
35
|
+
readonly extractionConfidence?: number;
|
|
36
|
+
readonly sourceQuality?: ConfidenceBasis["sourceQuality"];
|
|
37
|
+
readonly reviewerAuthority?: ConfidenceBasis["reviewerAuthority"];
|
|
38
|
+
readonly evidenceStrength?: ConfidenceBasis["evidenceStrength"];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds a {@link ConfidenceBasis} from a reviewed status and impact level.
|
|
42
|
+
*
|
|
43
|
+
* The two known real-world consumers hand-roll *different* five-field
|
|
44
|
+
* mappings, not one shared algorithm: one derives `sourceQuality` and
|
|
45
|
+
* `evidenceStrength` from whether any extraction/review support exists at
|
|
46
|
+
* all, and never returns `"strong"` for either field even when `status` is
|
|
47
|
+
* `"verified"` with no supporting evidence; the other derives `sourceQuality`
|
|
48
|
+
* from the extracted document's source type (independent of `status`) and
|
|
49
|
+
* `evidenceStrength` from `status` alone, never returning `"weak"` or
|
|
50
|
+
* `"none"`. There is no single formula that reproduces both, so this helper
|
|
51
|
+
* does not attempt to guess one.
|
|
52
|
+
*
|
|
53
|
+
* Defaults are therefore conservative-by-default: `sourceQuality` defaults to
|
|
54
|
+
* `"unknown"` and `evidenceStrength` defaults to `"none"` — the weakest
|
|
55
|
+
* possible values — unless the caller supplies an explicit value. The only
|
|
56
|
+
* field this helper derives from `status` without an explicit override is
|
|
57
|
+
* `reviewerAuthority` (`"operator"` when `status === "verified"`, otherwise
|
|
58
|
+
* `"none"`), because that is the one mapping both known real algorithms
|
|
59
|
+
* agree on for every non-verified status, and it is exactly what the more
|
|
60
|
+
* conservative of the two returns for the verified case too. `impactLevel`
|
|
61
|
+
* is always the caller-supplied value (never defaulted). `extractionConfidence`
|
|
62
|
+
* is copied through and included on the result only when provided; its mere
|
|
63
|
+
* presence no longer implies a stronger `sourceQuality`/`evidenceStrength` by
|
|
64
|
+
* default — callers who know that an extraction or review should count as
|
|
65
|
+
* support must say so explicitly.
|
|
66
|
+
*
|
|
67
|
+
* Producers with domain knowledge about their own source quality or evidence
|
|
68
|
+
* strength should pass `sourceQuality`/`evidenceStrength` explicitly instead
|
|
69
|
+
* of relying on the bare defaults — for example, a source-type-driven
|
|
70
|
+
* mapping (strong for corrected/high-confidence documents, moderate for
|
|
71
|
+
* medium-confidence documents, weak otherwise) the way one known consumer
|
|
72
|
+
* derives `sourceQuality` from its extracted record's source type. This
|
|
73
|
+
* function is a conservative, independently-designed baseline, not a
|
|
74
|
+
* behavioral match for either known consumer's algorithm; verify against
|
|
75
|
+
* your own data before treating it as a drop-in replacement for hand-rolled
|
|
76
|
+
* mapping logic.
|
|
77
|
+
*/
|
|
78
|
+
export declare function confidenceBasisForReview(input: ConfidenceBasisForReviewInput): ConfidenceBasis;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a stable, url-safe identifier from ordered parts. Each part is
|
|
3
|
+
* lowercased, non-alphanumeric runs collapse to a single hyphen, leading and
|
|
4
|
+
* trailing hyphens are trimmed, and parts join with a dot.
|
|
5
|
+
*
|
|
6
|
+
* `stableId(["Public Record", "entity-123", "current"])` → `"public-record.entity-123.current"`.
|
|
7
|
+
*
|
|
8
|
+
* Producers use this to derive deterministic candidate, candidate-set, and claim
|
|
9
|
+
* identifiers from domain values without inventing their own slug helper.
|
|
10
|
+
*/
|
|
11
|
+
export function stableId(parts) {
|
|
12
|
+
return parts
|
|
13
|
+
.map((part) => String(part)
|
|
14
|
+
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
15
|
+
.replace(/^-|-$/g, "")
|
|
16
|
+
.toLowerCase())
|
|
17
|
+
.join(".");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Defines a product vocabulary as a deep-frozen, discoverable value that a
|
|
21
|
+
* `currentProposedReviewItem` caller can pass instead of loose top-level
|
|
22
|
+
* constants. Returns the same shape it received, frozen so callers cannot
|
|
23
|
+
* mutate a shared vocabulary at runtime.
|
|
24
|
+
*/
|
|
25
|
+
export function defineProductVocabulary(definition) {
|
|
26
|
+
return deepFreeze({
|
|
27
|
+
subjectType: definition.subjectType,
|
|
28
|
+
surface: definition.surface,
|
|
29
|
+
claimTypes: { ...definition.claimTypes },
|
|
30
|
+
decisionEffects: { ...definition.decisionEffects },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Builds a {@link ConfidenceBasis} from a reviewed status and impact level.
|
|
35
|
+
*
|
|
36
|
+
* The two known real-world consumers hand-roll *different* five-field
|
|
37
|
+
* mappings, not one shared algorithm: one derives `sourceQuality` and
|
|
38
|
+
* `evidenceStrength` from whether any extraction/review support exists at
|
|
39
|
+
* all, and never returns `"strong"` for either field even when `status` is
|
|
40
|
+
* `"verified"` with no supporting evidence; the other derives `sourceQuality`
|
|
41
|
+
* from the extracted document's source type (independent of `status`) and
|
|
42
|
+
* `evidenceStrength` from `status` alone, never returning `"weak"` or
|
|
43
|
+
* `"none"`. There is no single formula that reproduces both, so this helper
|
|
44
|
+
* does not attempt to guess one.
|
|
45
|
+
*
|
|
46
|
+
* Defaults are therefore conservative-by-default: `sourceQuality` defaults to
|
|
47
|
+
* `"unknown"` and `evidenceStrength` defaults to `"none"` — the weakest
|
|
48
|
+
* possible values — unless the caller supplies an explicit value. The only
|
|
49
|
+
* field this helper derives from `status` without an explicit override is
|
|
50
|
+
* `reviewerAuthority` (`"operator"` when `status === "verified"`, otherwise
|
|
51
|
+
* `"none"`), because that is the one mapping both known real algorithms
|
|
52
|
+
* agree on for every non-verified status, and it is exactly what the more
|
|
53
|
+
* conservative of the two returns for the verified case too. `impactLevel`
|
|
54
|
+
* is always the caller-supplied value (never defaulted). `extractionConfidence`
|
|
55
|
+
* is copied through and included on the result only when provided; its mere
|
|
56
|
+
* presence no longer implies a stronger `sourceQuality`/`evidenceStrength` by
|
|
57
|
+
* default — callers who know that an extraction or review should count as
|
|
58
|
+
* support must say so explicitly.
|
|
59
|
+
*
|
|
60
|
+
* Producers with domain knowledge about their own source quality or evidence
|
|
61
|
+
* strength should pass `sourceQuality`/`evidenceStrength` explicitly instead
|
|
62
|
+
* of relying on the bare defaults — for example, a source-type-driven
|
|
63
|
+
* mapping (strong for corrected/high-confidence documents, moderate for
|
|
64
|
+
* medium-confidence documents, weak otherwise) the way one known consumer
|
|
65
|
+
* derives `sourceQuality` from its extracted record's source type. This
|
|
66
|
+
* function is a conservative, independently-designed baseline, not a
|
|
67
|
+
* behavioral match for either known consumer's algorithm; verify against
|
|
68
|
+
* your own data before treating it as a drop-in replacement for hand-rolled
|
|
69
|
+
* mapping logic.
|
|
70
|
+
*/
|
|
71
|
+
export function confidenceBasisForReview(input) {
|
|
72
|
+
const verified = input.status === "verified";
|
|
73
|
+
const basis = {
|
|
74
|
+
sourceQuality: input.sourceQuality ?? "unknown",
|
|
75
|
+
reviewerAuthority: input.reviewerAuthority ?? (verified ? "operator" : "none"),
|
|
76
|
+
evidenceStrength: input.evidenceStrength ?? "none",
|
|
77
|
+
impactLevel: input.impactLevel,
|
|
78
|
+
};
|
|
79
|
+
if (input.extractionConfidence !== undefined) {
|
|
80
|
+
basis.extractionConfidence = input.extractionConfidence;
|
|
81
|
+
}
|
|
82
|
+
return basis;
|
|
83
|
+
}
|
|
84
|
+
function deepFreeze(value) {
|
|
85
|
+
if (value && typeof value === "object") {
|
|
86
|
+
for (const nested of Object.values(value)) {
|
|
87
|
+
deepFreeze(nested);
|
|
88
|
+
}
|
|
89
|
+
Object.freeze(value);
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/survey",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"check:generated-css": "node scripts/copy-review-workbench-package-assets.cjs --check"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@kontourai/surface": "^
|
|
71
|
+
"@kontourai/surface": "^1.3.0"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"@anthropic-ai/sdk": ">=0.20.0"
|