Dated evidence in a domain no longer yields another existence question in that domain. Remaining varga discriminators all stay in the pool so the next card is whichever unused split scores highest. Co-authored-by: Cursor <cursoragent@cursor.com>
353 lines
13 KiB
TypeScript
353 lines
13 KiB
TypeScript
/**
|
|
* Shared Case → decideRectification adapter.
|
|
*
|
|
* GET refresh, turn_decision, choice/stop, and interview cards must feed
|
|
* the same inputs. Snapshot selection_allowed is storage, not policy.
|
|
*/
|
|
|
|
import {
|
|
buildCandidateContrastPacket,
|
|
datedDomainsFromEvidence,
|
|
selectDiscriminatorProbe,
|
|
volunteeredDomainsFromEvidence,
|
|
type CandidateContrastPacket,
|
|
type EngineContrastProbe,
|
|
} from "../core/candidate-contrast-packet.ts";
|
|
import {
|
|
decideRectification,
|
|
publicDecisionFields,
|
|
type RectificationDecision,
|
|
} from "../core/rectification-decision.ts";
|
|
import type { InferenceState } from "../core/types.ts";
|
|
import {
|
|
askedDiscriminatorKeys,
|
|
authoritativeCandidateProjection,
|
|
previousInferenceFromReceipt,
|
|
} from "./inference-adapter";
|
|
import {
|
|
blockingMethodsCovered,
|
|
buildMethodFollowupPlan,
|
|
} from "./method-followup";
|
|
import { buildConfirmationGate } from "./confirmation-gate";
|
|
import {
|
|
MIN_ACCEPTANCE_DOMAINS,
|
|
MIN_ACCEPTANCE_EVENTS,
|
|
trainingScoreableGate,
|
|
} from "./evidence-model";
|
|
import { refinementFromDecisionReceipt } from "./refinement-packet";
|
|
import { windowScanFromDecisionReceipt } from "./varga-observations";
|
|
|
|
export type DecisionDossier = Readonly<{
|
|
evidence: readonly Readonly<{
|
|
id?: string;
|
|
status: string;
|
|
domain: string;
|
|
datePrecision: string;
|
|
occurredFrom: string | null;
|
|
occurredTo: string | null;
|
|
eventKind?: string | null;
|
|
summary?: string | null;
|
|
}>[];
|
|
conversationSummary: {
|
|
activeFocus: {
|
|
id?: string;
|
|
intent: string;
|
|
targetDomain: string | null;
|
|
targetKind: string | null;
|
|
expectedAnswerSchema?: Readonly<Record<string, unknown>> | null;
|
|
} | null;
|
|
declinedSkippedTopics: readonly Readonly<Record<string, unknown>>[];
|
|
};
|
|
latestResult: {
|
|
resultId?: string;
|
|
decisionReceipt: Readonly<Record<string, unknown>> | null;
|
|
selectionAllowed?: boolean;
|
|
confirmationAllowed?: boolean;
|
|
candidates?: readonly Readonly<{
|
|
candidateId?: string;
|
|
time: string;
|
|
rank?: number;
|
|
relativeSupport?: number;
|
|
posterior_score?: number;
|
|
tiedMinuteCount?: number;
|
|
}>[];
|
|
representativeTime?: string | null;
|
|
evidenceLedgerFingerprint?: string | null;
|
|
candidateRangeFingerprint?: string | null;
|
|
} | null;
|
|
case: {
|
|
acceptedTime: string | null;
|
|
status?: string;
|
|
};
|
|
turns?: readonly Readonly<{ role: string; text: string | null }>[];
|
|
}>;
|
|
|
|
export function candidateScoresFromDossier(latest: DecisionDossier["latestResult"]) {
|
|
return authoritativeCandidateProjection(latest).scores;
|
|
}
|
|
|
|
const CLOCK_TIME = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
|
|
|
|
export function discriminatorCandidateTimes(
|
|
latest: DecisionDossier["latestResult"],
|
|
): string[] {
|
|
const inference = previousInferenceFromReceipt(latest?.decisionReceipt ?? null);
|
|
const fromInference = [...new Set(
|
|
(inference?.candidates ?? [])
|
|
.filter((item) => item.status !== "eliminated")
|
|
.map((item) => item.time)
|
|
.filter((time) => CLOCK_TIME.test(time)),
|
|
)];
|
|
if (fromInference.length >= 2) return fromInference;
|
|
return candidateScoresFromDossier(latest).map((item) => item.time);
|
|
}
|
|
|
|
function holdoutStatusFromInference(inference: ReturnType<typeof previousInferenceFromReceipt>) {
|
|
if (!inference) return "unavailable" as const;
|
|
const hasHoldout = inference.events.some((item) => item.usage === "holdout");
|
|
if (!hasHoldout) return "unavailable" as const;
|
|
if (inference.holdout_passed === true) return "passed" as const;
|
|
if (inference.holdout_passed === false || inference.result_status === "validation_failed") {
|
|
return "failed" as const;
|
|
}
|
|
return "not_started" as const;
|
|
}
|
|
|
|
function holdoutStatusFromState(state: InferenceState) {
|
|
const hasHoldout = state.events.some((item) => item.usage === "holdout");
|
|
if (state.holdout_passed === true) return "passed" as const;
|
|
if (state.holdout_passed === false || state.result_status === "validation_failed") {
|
|
return "failed" as const;
|
|
}
|
|
if (hasHoldout) return "not_started" as const;
|
|
return "unavailable" as const;
|
|
}
|
|
|
|
export function contrastPacketFromLatestResult(
|
|
latest: DecisionDossier["latestResult"],
|
|
evidence: DecisionDossier["evidence"] = [],
|
|
): CandidateContrastPacket {
|
|
const windowScan = windowScanFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
|
const refinement = refinementFromDecisionReceipt(latest?.decisionReceipt ?? null);
|
|
const inference = previousInferenceFromReceipt(latest?.decisionReceipt ?? null);
|
|
const answered = new Set((inference?.answered_probes ?? []).map((item) => item.id));
|
|
const fromInference: EngineContrastProbe[] = (inference?.probes ?? []).flatMap((probe) => {
|
|
if (answered.has(probe.id) || probe.information_gain <= 0) return [];
|
|
if (probe.source === "known_event_quality") return [];
|
|
return [{
|
|
semantic_key: probe.semantic_key,
|
|
candidate_split_hash: probe.candidate_split_hash,
|
|
domain: probe.domain,
|
|
year: probe.year > 0 ? probe.year : undefined,
|
|
user_meaning: probe.question,
|
|
information_gain: probe.information_gain,
|
|
expected_outcomes: probe.expected_outcomes,
|
|
candidate_ids: probe.candidate_ids,
|
|
}];
|
|
});
|
|
const merged = mergeEngineProbes(
|
|
fromInference,
|
|
refinement.discriminating_event_probes.map((probe) => ({
|
|
semantic_key: probe.semantic_key,
|
|
candidate_split_hash: probe.candidate_split_hash,
|
|
domain: probe.domain,
|
|
year: probe.year,
|
|
user_meaning: probe.user_meaning,
|
|
information_gain: probe.information_gain,
|
|
expected_outcomes: probe.expected_outcomes,
|
|
candidate_ids: probe.candidate_ids,
|
|
choice_kind: probe.choice_kind,
|
|
style_options: probe.style_options,
|
|
})),
|
|
);
|
|
return buildCandidateContrastPacket({
|
|
candidateSetVersion: inference?.candidate_set_id ?? latest?.resultId ?? "none",
|
|
calculationResultId: latest?.resultId ?? null,
|
|
engineProbes: merged,
|
|
vargaDifferences: [
|
|
...(windowScan?.d9_candidates_differ && windowScan.d9_sign_names.length >= 2
|
|
? [{ layer: "d9", signs: windowScan.d9_sign_names }]
|
|
: []),
|
|
...(windowScan?.d10_candidates_differ && windowScan.d10_sign_names.length >= 2
|
|
? [{ layer: "d10", signs: windowScan.d10_sign_names }]
|
|
: []),
|
|
],
|
|
candidateTimes: discriminatorCandidateTimes(latest),
|
|
transitions: windowScan?.transitions ?? [],
|
|
askedKeys: askedDiscriminatorKeys(latest?.decisionReceipt, evidence),
|
|
volunteeredDomains: volunteeredDomainsFromEvidence(evidence),
|
|
providedDomains: datedDomainsFromEvidence(evidence),
|
|
});
|
|
}
|
|
|
|
function mergeEngineProbes(
|
|
...groups: ReadonlyArray<readonly EngineContrastProbe[] | undefined>
|
|
): EngineContrastProbe[] {
|
|
const byKey = new Map<string, EngineContrastProbe>();
|
|
for (const group of groups) {
|
|
for (const probe of group ?? []) {
|
|
const key = probe.semantic_key?.trim() ?? "";
|
|
if (!key) continue;
|
|
const current = byKey.get(key);
|
|
if (!current || (probe.information_gain ?? 0) > (current.information_gain ?? 0)) {
|
|
byKey.set(key, probe);
|
|
}
|
|
}
|
|
}
|
|
return [...byKey.values()];
|
|
}
|
|
|
|
export function contrastPacketFromDossier(dossier: DecisionDossier): CandidateContrastPacket {
|
|
return contrastPacketFromLatestResult(dossier.latestResult, dossier.evidence);
|
|
}
|
|
|
|
export function rectificationFollowupCatalog(
|
|
latest: DecisionDossier["latestResult"],
|
|
evidence: DecisionDossier["evidence"] = [],
|
|
) {
|
|
const receipt = latest?.decisionReceipt ?? null;
|
|
const refinement = refinementFromDecisionReceipt(receipt);
|
|
const inference = previousInferenceFromReceipt(receipt);
|
|
return {
|
|
contrastPacket: contrastPacketFromLatestResult(latest, evidence),
|
|
topCandidateTimes: discriminatorCandidateTimes(latest),
|
|
askedProbeKeys: askedDiscriminatorKeys(receipt, evidence),
|
|
eventProbes: refinement.discriminating_event_probes,
|
|
eventClarificationProbes: refinement.event_clarification_probes,
|
|
evidenceCollectionProbes: refinement.evidence_collection_probes,
|
|
precisionStage: refinement.precision_stage?.current ?? null,
|
|
nakshatraBoundary: refinement.nakshatra_boundary,
|
|
oosBlindPrompts: refinement.oos_blind_prompts,
|
|
holdoutEvents: (inference?.events ?? [])
|
|
.filter((item) => item.usage === "holdout")
|
|
.map((item) => ({ domain: item.domain, year: item.year })),
|
|
};
|
|
}
|
|
|
|
function contrastPacketFromState(state: InferenceState): CandidateContrastPacket {
|
|
const answered = new Set(state.answered_probes.map((item) => item.probe_id));
|
|
return buildCandidateContrastPacket({
|
|
candidateSetVersion: state.candidate_set_id,
|
|
calculationResultId: null,
|
|
engineProbes: state.probes
|
|
.filter((item) => !answered.has(item.id))
|
|
.map((item) => ({
|
|
semantic_key: item.semantic_key,
|
|
candidate_split_hash: item.candidate_split_hash,
|
|
domain: item.domain,
|
|
year: item.year,
|
|
user_meaning: item.question,
|
|
information_gain: item.information_gain,
|
|
expected_outcomes: item.expected_outcomes,
|
|
candidate_ids: item.candidate_ids,
|
|
})),
|
|
candidateTimes: state.candidates
|
|
.filter((item) => item.status !== "eliminated")
|
|
.map((item) => item.time),
|
|
askedKeys: state.answered_probes.map((item) => item.semantic_key),
|
|
});
|
|
}
|
|
|
|
export function decideFromDossier(
|
|
dossier: DecisionDossier,
|
|
options?: { currentEvidenceFingerprint?: string | null },
|
|
): RectificationDecision {
|
|
const inference = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
|
|
const trainingGate = trainingScoreableGate(dossier.evidence);
|
|
const collecting = buildMethodFollowupPlan({
|
|
evidence: dossier.evidence,
|
|
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
|
|
sessionOutcome: "collect_evidence",
|
|
});
|
|
const storedFingerprint = dossier.latestResult?.evidenceLedgerFingerprint ?? "";
|
|
const currentFingerprint = options?.currentEvidenceFingerprint ?? storedFingerprint;
|
|
const snapshotCurrent = !storedFingerprint || storedFingerprint === currentFingerprint;
|
|
const latest = dossier.latestResult;
|
|
const confirmationGate = buildConfirmationGate({
|
|
engineConfirmationAllowed: latest?.confirmationAllowed === true,
|
|
candidates: (latest?.candidates ?? []).map((candidate) => ({
|
|
time: candidate.time,
|
|
rank: candidate.rank ?? Number.MAX_SAFE_INTEGER,
|
|
tiedMinuteCount: candidate.tiedMinuteCount ?? Number.MAX_SAFE_INTEGER,
|
|
})),
|
|
decisionReceipt: latest?.decisionReceipt ?? null,
|
|
});
|
|
return decideRectification({
|
|
methodCoverageAll: blockingMethodsCovered(collecting.methods),
|
|
trainingGateOpen: trainingGate.open,
|
|
confirmationAllowed: confirmationGate.confirmation_allowed,
|
|
userStopped: dossier.case.status === "paused",
|
|
snapshotCurrent,
|
|
candidateScores: candidateScoresFromDossier(dossier.latestResult),
|
|
discriminatorProbe: selectDiscriminatorProbe(contrastPacketFromDossier(dossier)),
|
|
holdoutValidation: holdoutStatusFromInference(inference),
|
|
accepted: Boolean(dossier.case.acceptedTime),
|
|
inferenceCredibleRange: inference?.credible_range ?? null,
|
|
});
|
|
}
|
|
|
|
export function decideAfterInferenceChange(input: {
|
|
dossier: DecisionDossier;
|
|
state: InferenceState | null;
|
|
userStopped: boolean;
|
|
}): RectificationDecision {
|
|
const collecting = buildMethodFollowupPlan({
|
|
evidence: input.dossier.evidence,
|
|
declinedTopics: input.dossier.conversationSummary.declinedSkippedTopics,
|
|
sessionOutcome: "collect_evidence",
|
|
});
|
|
if (!input.state) {
|
|
return decideRectification({
|
|
methodCoverageAll: blockingMethodsCovered(collecting.methods),
|
|
trainingGateOpen: trainingScoreableGate(input.dossier.evidence).open,
|
|
candidateScores: [],
|
|
userStopped: input.userStopped,
|
|
});
|
|
}
|
|
const training = input.state.events.filter((item) => item.usage === "training");
|
|
const trainingDomains = new Set(training.map((item) => item.domain));
|
|
return decideRectification({
|
|
methodCoverageAll: blockingMethodsCovered(collecting.methods),
|
|
trainingGateOpen: training.length >= MIN_ACCEPTANCE_EVENTS
|
|
&& trainingDomains.size >= MIN_ACCEPTANCE_DOMAINS,
|
|
candidateScores: input.state.candidates
|
|
.filter((item) => item.status !== "eliminated")
|
|
.map((item) => ({ time: item.time, score: item.posterior_score })),
|
|
discriminatorProbe: selectDiscriminatorProbe(contrastPacketFromState(input.state)),
|
|
holdoutValidation: holdoutStatusFromState(input.state),
|
|
inferenceCredibleRange: input.state.credible_range,
|
|
userStopped: input.userStopped,
|
|
accepted: Boolean(input.dossier.case.acceptedTime),
|
|
});
|
|
}
|
|
|
|
export function overlayPublicDecision<T extends object>(
|
|
snapshot: T,
|
|
decision: RectificationDecision,
|
|
): T & ReturnType<typeof publicDecisionFields> & {
|
|
selectionAllowed: boolean;
|
|
validated: boolean;
|
|
completionStatus: ReturnType<typeof publicDecisionFields>["completion_status"];
|
|
} {
|
|
const fields = publicDecisionFields(decision);
|
|
const projection = authoritativeCandidateProjection(snapshot as DecisionDossier["latestResult"]);
|
|
const inconsistent = !projection.consistent;
|
|
return {
|
|
...snapshot,
|
|
...fields,
|
|
...(projection.fromInference
|
|
? {
|
|
candidates: projection.candidates,
|
|
representativeTime: projection.representativeTime,
|
|
representative_time: projection.representativeTime,
|
|
credibleRange: projection.credibleRange,
|
|
credible_range: projection.credibleRange,
|
|
}
|
|
: {}),
|
|
...(inconsistent ? { can_adopt: false, selection_allowed: false } : {}),
|
|
selectionAllowed: inconsistent ? false : fields.selection_allowed,
|
|
validated: fields.validated,
|
|
completionStatus: fields.completion_status,
|
|
};
|
|
}
|