import { type ReviewCandidate, type ReviewItem, type ReviewSession, type ReviewSessionEvent, type ReviewSessionEventSpec, type ReviewSessionPresentation, type ReviewSessionSampling } from "../../src/review-resource.js"; export type { ReviewSessionPresentation, ReviewSessionSampling } from "../../src/review-resource.js"; /** * The decisions a reviewer can record on a ReviewItem. * * `select-proposed` chooses one value of a conflict (two or more `proposed` * candidates) by candidate id, which the state carries beside the decision * (`selectedCandidateId` / `selectedCandidateIdsByItemName`). It is its own * kind rather than `accept-proposed` with an id so that the record always says * a rival was seen and not chosen, and `accept-proposed` keeps its meaning: * accept the one proposed value, refused on a conflict. */ export type ReviewWorkbenchDecision = "accept-proposed" | "select-proposed" | "keep-current" | "reject-proposed" | "could-not-confirm"; export type ReviewQueueRowStatus = "pending" | "in-review" | "resolved" | "rejected" | "could-not-confirm" | "escalated"; export declare const reviewWorkbenchSessionStorageKey = "kontourai.survey.review-workbench.session-events.v1"; export declare const defaultReviewSessionName = "review-workbench-session"; export interface ReviewWorkbenchState { readonly item: ReviewItem; readonly note: string; readonly decision?: ReviewWorkbenchDecision; readonly reviewedAt: string; readonly actorId: string; /** * Reviewer-edited override for the item's proposed value (inline edit in the * field-diff card). Additive/optional: undefined means no edit was made and the * proposed candidate's original value applies. */ readonly editedValue?: unknown; readonly attemptEvidenceIds?: readonly string[]; /** * The candidate a `select-proposed` decision chose. Required for that * decision and ignored for every other one. */ readonly selectedCandidateId?: string; /** The session's presentation; recorded on the decision. See {@link ReviewSessionPresentation}. */ readonly presentation?: ReviewSessionPresentation; /** How the session's items were chosen; recorded on the decision. */ readonly sampling?: ReviewSessionSampling; } export interface ReviewQueueSessionState { readonly items: readonly ReviewItem[]; readonly activeItemName: string; readonly notesByItemName: Readonly>; readonly decisionsByItemName: Readonly>; readonly reviewedAt: string; readonly actorId: string; /** * Reviewer-edited overrides for proposed values, keyed by ReviewItem name. * Additive/optional: a session built before this field existed behaves exactly * as before (every lookup resolves to undefined, meaning "use the candidate's * original value"). */ readonly editedValuesByItemName?: Readonly>; readonly attemptEvidenceIdsByItemName?: Readonly>; /** * The candidate each `select-proposed` decision chose, keyed by ReviewItem * name. Additive/optional: sessions stored before this field existed have no * `select-proposed` decision and never read it. */ readonly selectedCandidateIdsByItemName?: Readonly>; /** * Session-level presentation, set when the session is opened. Every * decision event is stamped with it and replay refuses a mismatch; a * `ReviewQueueBinding` also covers it. Without a binding, an edit to both the * snapshot and every event stamp is not detectable. Absent: scores are shown * and decisions record no presentation. */ readonly presentation?: ReviewSessionPresentation; /** How the session's items were chosen. Absent: decisions record no sampling. */ readonly sampling?: ReviewSessionSampling; } /** Whether the session hides confidence and verifier results from its reviewer. */ export declare function isScoreBlind(session: { readonly presentation?: ReviewSessionPresentation; }): boolean; /** * Throws unless the session's presentation and sampling are well formed: * `scoreBlind` a boolean, and a `random-audit` sampling with a rate in (0, 1] * and a non-empty seed. Unknown keys are refused so a decision never records * a condition nothing reads. */ export declare function assertReviewSessionConditions(session: { readonly presentation?: unknown; readonly sampling?: unknown; }): void; /** * The session conditions a decision event carries, or `undefined` for a * session that declares none. Stamped on every decision event so replay can * refuse events recorded under other conditions than the snapshot's: sighted * events replayed over a blind snapshot, or a snapshot whose presentation was * changed after decisions were recorded. */ export declare function sessionConditionsStamp(session: { readonly presentation?: ReviewSessionPresentation; readonly sampling?: ReviewSessionSampling; }): { presentation?: ReviewSessionPresentation; sampling?: ReviewSessionSampling; } | undefined; export declare function assertAuditRate(rate: unknown): asserts rate is number; export declare function assertAuditSeed(seed: unknown): asserts seed is string; export interface ReviewSessionSummary { readonly accepted: number; readonly keptCurrent: number; readonly rejected: number; readonly couldNotConfirm?: number; readonly escalated: number; readonly unresolved: number; } export declare const workbenchDecisionDefinitions: { "accept-proposed": { label: string; effect: string; candidateRole: "proposed"; status: "verified"; }; "select-proposed": { label: string; effect: string; candidateRole: "proposed"; status: "verified"; }; "keep-current": { label: string; effect: string; candidateRole: "current"; status: "verified"; }; "reject-proposed": { label: string; effect: string; candidateRole: "proposed"; status: "rejected"; }; "could-not-confirm": { label: string; effect: string; candidateRole: "proposed"; status: "proposed"; }; }; export declare function initialReviewWorkbenchState(item?: ReviewItem): ReviewWorkbenchState; export declare function initialReviewQueueSessionState(items?: readonly ReviewItem[]): ReviewQueueSessionState; export declare function currentReviewWorkbenchState(session: ReviewQueueSessionState): ReviewWorkbenchState; export declare function currentReviewItem(session: ReviewQueueSessionState): ReviewItem; export declare function deriveQueueRowStatus(item: ReviewItem, session: ReviewQueueSessionState): ReviewQueueRowStatus; export declare function nextUnresolvedItemName(session: ReviewQueueSessionState): string | undefined; export declare function reviewSessionSummary(session: ReviewQueueSessionState): ReviewSessionSummary; /** * The decision the field card's "keep" control records for a ReviewItem, or * `undefined` when the item cannot represent that action at all. * * Every workbench decision resolves to a candidate role (see * {@link workbenchDecisionDefinitions}), so a decision whose role the item does * not carry is not recordable — `keep-current` on an item that has no `current` * candidate is the case that matters, because that is exactly how an * envelope-imported item is modelled (one `proposed` candidate, nothing prior). * The card already labels that control "Leave unset" rather than "Keep current"; * this returns the decision that means the same thing and IS representable: * `reject-proposed` — the proposed value is not applied and nothing is set. * * Deliberately NOT solved by synthesising an empty `current` candidate: that * would invent a prior value, with provenance, that the source never had. */ export declare function keepActionDecision(item: ReviewItem, flaggedWrong: boolean): ReviewWorkbenchDecision | undefined; /** * The candidate a workbench decision applies to. * * Selection is by role, so the role has to name exactly one candidate before a * decision may trust it. Guarding the render path alone let the workbench emit * an undecidable decision through the export path and then present a different * candidate's value against it; this is the shared selector all of those go * through, which is why the check belongs here rather than at each of them. * * A decision that would make one of several candidates in its role the trusted * value is refused: picking the first would settle the conflict for the * reviewer without showing it. A value-neutral decision (reject, could not * confirm) on such an item selects none of them. It still returns the first * candidate so in-process callers that need one candidate (rendering, the * in-memory `ReviewWorkbenchResult`) have one, but that anchor is not recorded: * {@link decisionCandidateId} is `undefined` for it, so the decision, its * session events and the canonical projection name no candidate. */ export declare function candidateForDecision(item: ReviewItem, decision: ReviewWorkbenchDecision, selectedCandidateId?: string): ReviewCandidate; /** * Why a `select-proposed` decision naming `candidateId` cannot be recorded on * this item, or `undefined` when it can. * * The choice must name, by a unique id, one of the item's `proposed` * candidates, and the item must hold at least two of them: choosing "over a * rival" on an item with no rival would record a conflict that never existed * (use `accept-proposed` there). Proposals excluded at import are not * candidates, so they can never be chosen. */ export declare function conflictSelectionIssue(item: ReviewItem, candidateId: string | undefined): string | undefined; /** * The proposed candidates a `select-proposed` decision saw and did not choose, * in item order. Empty for every other decision. */ export declare function unchosenProposedCandidates(item: ReviewItem, decision: ReviewWorkbenchDecision, selectedCandidateId: string | undefined): ReviewCandidate[]; /** Whether a decision on this item selects no candidate at all (see {@link candidateForDecision}). */ export declare function decisionSelectsNoCandidate(item: ReviewItem, decision: ReviewWorkbenchDecision): boolean; /** * The candidate id a decision records: the selected candidate's, or * `undefined` when the decision selects no candidate. */ export declare function decisionCandidateId(item: ReviewItem, decision: ReviewWorkbenchDecision, selectedCandidateId?: string): string | undefined; /** * The value that should actually be applied for a decision: the reviewer's inline * edit when one was made for an accept-proposed decision, otherwise the selected * candidate's original value. Consumers reading `ReviewWorkbenchResult` should * prefer `effectiveValue`/`effectiveDisplayValue`, which are already computed with * this rule; this helper exists for callers deriving the value from raw session * state directly. */ export declare function effectiveValueForDecision(item: ReviewItem, decision: ReviewWorkbenchDecision, editedValue?: unknown, selectedCandidateId?: string): unknown; export declare function selectedCandidateRole(state: ReviewWorkbenchState): ReviewCandidate["role"] | undefined; export declare function buildReviewSessionResource(session: ReviewQueueSessionState, events?: readonly ReviewSessionEvent[], sessionName?: string): ReviewSession; export declare function buildReviewSessionEvents(session: ReviewQueueSessionState, sessionName?: string): ReviewSessionEvent[]; export declare function replayReviewSessionEvents(startState: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewQueueSessionState; /** * Detects the explicit "clear this ReviewItem's decision" replay signal (emitted * by the workbench's "Change" / undo control): a decision event whose * `data.workbenchDecision` is the literal `null` sentinel, as opposed to `undefined` * (no decision info present — event is ignored by replay, same as before this * feature existed). */ export declare function isClearedWorkbenchDecisionEvent(event: ReviewSessionEvent): boolean; export declare function buildReviewSessionEvent(session: ReviewQueueSessionState, spec: Omit): ReviewSessionEvent;