import assert from "node:assert/strict"; import test from "node:test"; import { ConversationalRectificationError, toConversationalRectificationPublicError, } from "../src/lib/conversational-rectification/errors.ts"; import { ConversationalRectificationStore, mapConversationalRectificationStoreError, type ConversationalRectificationRpcClient, } from "../src/lib/conversational-rectification/store.ts"; import { ConversationalRectificationBilling } from "../src/lib/conversational-rectification/billing.ts"; import { conversationalRectificationTurnSchema } from "../src/lib/conversational-rectification/contracts.ts"; import { conversationalRectificationActionReceiptRequestSchema, conversationalRectificationActionReceiptResponseSchema, declaredBirthInputSchema, lifeEventEvidenceSchema, privateCandidateSchema, validationReceiptSchema, } from "../src/lib/conversational-rectification/persistence-contracts.ts"; import { postgresJsonbTextBytes } from "../src/lib/conversational-rectification/json-bounds.ts"; const userId = "00000000-0000-4000-8000-000000000101"; const caseId = "00000000-0000-4000-8000-000000000102"; const actionId = "00000000-0000-4000-8000-000000000103"; const resultId = "00000000-0000-4000-8000-000000000104"; const importCaseId = "00000000-0000-4000-8000-000000000107"; const commandFingerprint = "c".repeat(64); const firstTurn = { caseId, journeyProtocol: "conversational-evidence-v3" as const, status: "active" as const, turnVersion: 0, narrative: "我们会用已经发生的人生事件验证当前候选范围。", candidate: { status: "pending_validation" as const, representativeTime: "05:21", rangeStart: "05:10", rangeEnd: "05:30", }, technicalReceipt: { calculationVersion: "rectification-v3.1", stableLayers: ["D1"], sensitiveLayers: ["D9"], candidateDifferenceRefs: ["difference-1"], }, evidenceRequest: { domains: ["career", "relocation"] as const, datePrecision: "month_preferred" as const, freeTextAllowed: true as const, }, evidenceRecap: [], actions: ["answer", "pause", "abandon"] as const, pendingConsultationQuestion: null, }; const storedRow = { case_id: caseId, user_id: userId, status: "active", turn_version: 0, revision_of_case_id: null, imported_from_case_id: null, baseline_active_time: "04:58", pending_consultation_question: null, billing_state: null, latest_turn: firstTurn, declared_birth_input: { birthDate: "1990-01-01", reportedTime: "05:20", source: "approximate", birthTimeClue: "家人记得天刚亮", uncertaintyBeforeMinutes: 30, uncertaintyAfterMinutes: 30, birthplace: { countryCode: "TW", provinceCode: "TPE", cityCode: "TPE-CITY", districtCode: "DAAN", latitude: 25.0268, longitude: 121.5434, timezoneOffset: 8, }, }, private_candidate: { resultId, calculationVersion: "rectification-v3.1", candidateWeights: [0.6, 0.4], }, event_evidence: [], validation_receipts: [{ modelId: "synthetic-model", schemaValidated: true }], }; const validationReceipt = { modelId: "synthetic-model", schemaValidated: true, }; function rpcClient( handler: (name: string, args: Readonly>) => unknown, ): ConversationalRectificationRpcClient { return { async rpc(name, args) { return { data: handler(name, args), error: null }; }, }; } test("creates an account-level case and first public turn through one RPC", async () => { let call: { name: string; args: Readonly> } | undefined; const store = new ConversationalRectificationStore(rpcClient((name, args) => { call = { name, args }; return storedRow; })); const result = await store.createCaseWithFirstTurn({ userId, caseId, actionId: caseId, expectedVersion: 0, revisionOfCaseId: null, pendingConsultationQuestion: null, declaredBirthInput: { birthDate: "1990-01-01", reportedTime: "05:20", source: "approximate", birthTimeClue: "家人记得天刚亮", uncertaintyBeforeMinutes: 30, uncertaintyAfterMinutes: 30, birthplace: { countryCode: "TW", provinceCode: "TPE", cityCode: "TPE-CITY", districtCode: "DAAN", latitude: 25.0268, longitude: 121.5434, timezoneOffset: 8, }, }, firstTurn, validationReceipt, privateCandidate: { resultId, representativeTime: "05:21", calculationVersion: "rectification-v3.1", candidateWeights: [0.6, 0.4], }, }); assert.equal(call?.name, "create_conversational_rectification_case"); assert.deepEqual(call?.args, { p_user_id: userId, p_case_id: caseId, p_expected_version: 0, p_action_id: caseId, p_revision_of_case_id: null, p_pending_consultation_question: null, p_declared_birth_input: { birthDate: "1990-01-01", reportedTime: "05:20", source: "approximate", birthTimeClue: "家人记得天刚亮", uncertaintyBeforeMinutes: 30, uncertaintyAfterMinutes: 30, birthplace: { countryCode: "TW", provinceCode: "TPE", cityCode: "TPE-CITY", districtCode: "DAAN", latitude: 25.0268, longitude: 121.5434, timezoneOffset: 8, }, }, p_first_turn: firstTurn, p_validation_receipt: validationReceipt, p_private_candidate: { resultId, representativeTime: "05:21", calculationVersion: "rectification-v3.1", candidateWeights: [0.6, 0.4], }, }); assert.equal(result.caseId, caseId); assert.equal(result.baselineActiveTime, "04:58"); assert.deepEqual(result.declaredBirthInput, storedRow.declared_birth_input); assert.deepEqual(result.latestTurn, firstTurn); assert.deepEqual(result.privateCandidate, storedRow.private_candidate); assert.deepEqual(result.eventEvidence, []); assert.deepEqual(result.validationReceipts, storedRow.validation_receipts); }); test("omits the optional new-event follow-up marker for legacy first-turn SQL validators", async () => { let firstTurnPayload: unknown; const store = new ConversationalRectificationStore(rpcClient((name, args) => { assert.equal(name, "create_conversational_rectification_case"); firstTurnPayload = args.p_first_turn; return storedRow; })); await store.createCaseWithFirstTurn({ userId, caseId, actionId: caseId, expectedVersion: 0, revisionOfCaseId: null, pendingConsultationQuestion: null, declaredBirthInput: storedRow.declared_birth_input as never, firstTurn: { ...firstTurn, evidenceRequest: { ...firstTurn.evidenceRequest, followUp: { kind: "new_event", evidenceId: null }, }, }, validationReceipt, privateCandidate: storedRow.private_candidate, }); assert.deepEqual( (firstTurnPayload as { evidenceRequest?: { followUp?: unknown } }).evidenceRequest, { domains: firstTurn.evidenceRequest.domains, datePrecision: firstTurn.evidenceRequest.datePrecision, freeTextAllowed: true, }, ); }); test("omits the optional new-event follow-up marker when saving a turn", async () => { let turnPayload: unknown; const store = new ConversationalRectificationStore(rpcClient((name, args) => { assert.equal(name, "save_conversational_rectification_turn_with_history"); assert.equal(args.p_user_message, "2019 年 7 月换工作"); turnPayload = args.p_turn; return storedRow; })); await store.saveTurn({ userId, caseId, actionId: "00000000-0000-4000-8000-000000000099", expectedVersion: 0, commandFingerprint: "a".repeat(64), userMessage: "2019 年 7 月换工作", turn: { ...firstTurn, turnVersion: 1, evidenceRequest: { ...firstTurn.evidenceRequest, followUp: { kind: "new_event", evidenceId: null }, }, }, evidence: [], validationReceipt, privateCandidate: storedRow.private_candidate, }); assert.equal( (turnPayload as { evidenceRequest?: { followUp?: unknown } }).evidenceRequest?.followUp, undefined, ); }); test("passes a null user message for an assistant-only regenerated turn", async () => { const store = new ConversationalRectificationStore(rpcClient((name, args) => { assert.equal(name, "save_conversational_rectification_turn_with_history"); assert.equal(args.p_user_message, null); return { ...storedRow, turn_version: 1 }; })); await store.saveTurn({ userId, caseId, actionId: "00000000-0000-4000-8000-000000000098", expectedVersion: 0, commandFingerprint: "b".repeat(64), userMessage: null, turn: { ...firstTurn, turnVersion: 1 }, evidence: [], validationReceipt, privateCandidate: { resultId, calculationVersion: "rectification-v3.1" }, }); }); test("loads the latest unfinished case by account without a chat identifier", async () => { const calls: unknown[] = []; const store = new ConversationalRectificationStore(rpcClient((name, args) => { calls.push([name, args]); return storedRow; })); const loaded = await store.loadCase({ userId }); assert.equal(loaded?.caseId, caseId); assert.deepEqual(calls, [["load_conversational_rectification_case_with_history", { p_user_id: userId, p_case_id: null, }]]); }); test("loads an exact owner-scoped historical mutation receipt before current case state", async () => { const calls: unknown[] = []; const publicReceiptRow: Partial = { ...storedRow }; delete publicReceiptRow.declared_birth_input; delete publicReceiptRow.private_candidate; delete publicReceiptRow.event_evidence; delete publicReceiptRow.validation_receipts; const store = new ConversationalRectificationStore(rpcClient((name, args) => { calls.push([name, args]); return publicReceiptRow; })); const replayed = await store.loadActionReceipt({ userId, caseId, actionId, actionKind: "save_turn", expectedVersion: 0, commandFingerprint, }); assert.equal(replayed?.caseId, caseId); assert.deepEqual(replayed?.latestTurn, firstTurn); assert.equal(replayed && "privateCandidate" in replayed, false); assert.deepEqual(calls, [["replay_conversational_rectification_action", { p_user_id: userId, p_case_id: caseId, p_action_id: actionId, p_action_kind: "save_turn", p_expected_version: 0, p_command_fingerprint: commandFingerprint, }]]); }); test("binds every paid start to the public action as its recoverable case id", async () => { let calls = 0; const client = rpcClient(() => { calls += 1; return storedRow; }); const store = new ConversationalRectificationStore(client); const billing = new ConversationalRectificationBilling(client); await assert.rejects( billing.reserve({ userId, caseId, actionId, expectedVersion: 0, price: 3 }), (error: unknown) => error instanceof ConversationalRectificationError && error.code === "action_conflict", ); await assert.rejects( store.createCaseWithFirstTurn({ userId, caseId, actionId, expectedVersion: 0, revisionOfCaseId: null, pendingConsultationQuestion: null, declaredBirthInput: storedRow.declared_birth_input as never, firstTurn, validationReceipt, privateCandidate: storedRow.private_candidate, }), (error: unknown) => error instanceof ConversationalRectificationError && error.code === "action_conflict", ); assert.equal(calls, 0); }); test("save, pause, abandon, confirm, and import carry owner/version/action guards", async () => { const calls: Array<[string, Readonly>]> = []; const store = new ConversationalRectificationStore(rpcClient((name, args) => { calls.push([name, args]); return storedRow; })); const common = { userId, caseId, actionId, expectedVersion: 0, commandFingerprint }; const evidence = [{ id: "00000000-0000-4000-8000-000000000105", rawText: "2019 年 7 月换工作", domain: "career" as const, eventSummary: "换工作", dateValue: "2019-07", datePrecision: "month" as const, extractionStatus: "corrected" as const, correctsEvidenceIds: ["00000000-0000-4000-8000-000000000109"], }]; await store.saveTurn({ ...common, userMessage: "2019 年 7 月换工作", turn: { ...firstTurn, turnVersion: 1 }, evidence, validationReceipt, privateCandidate: { resultId, calculationVersion: "rectification-v3.1" }, }); await store.pause({ ...common, turn: { ...firstTurn, status: "paused", turnVersion: 1 }, validationReceipt, }); await store.abandon({ ...common, turn: { ...firstTurn, status: "abandoned", turnVersion: 1 }, validationReceipt, }); await store.confirm({ ...common, resultId, time: "05:21", calculationVersion: "rectification-v3.1", validationReceipt, turn: { ...firstTurn, status: "completed", turnVersion: 1, candidate: { ...firstTurn.candidate, status: "confirmed" }, evidenceRequest: null, actions: ["continue_original_question"], }, }); await store.importLegacy({ userId, caseId: importCaseId, actionId: importCaseId, expectedVersion: 0, legacyCaseId: "00000000-0000-4000-8000-000000000106", price: 3, pendingConsultationQuestion: null, declaredBirthInput: declaredBirthInputSchema.parse(storedRow.declared_birth_input), evidence: [], firstTurn: { ...firstTurn, caseId: importCaseId }, validationReceipt, privateCandidate: { resultId, calculationVersion: "rectification-v3.1" }, }); assert.deepEqual(calls.map(([name]) => name), [ "save_conversational_rectification_turn_with_history", "pause_conversational_rectification_case", "abandon_conversational_rectification_without_result", "confirm_conversational_rectification_candidate", "import_legacy_conversational_rectification_case", ]); for (const [, args] of calls.slice(0, 4)) { assert.equal(args.p_user_id, userId); assert.equal(args.p_case_id, caseId); assert.equal(args.p_action_id, actionId); assert.equal(args.p_expected_version, 0); assert.equal(args.p_command_fingerprint, commandFingerprint); assert.deepEqual(args.p_validation_receipt, validationReceipt); } assert.equal(calls[4]?.[1].p_case_id, importCaseId); assert.equal(calls[4]?.[1].p_action_id, importCaseId); assert.deepEqual(calls[4]?.[1].p_validation_receipt, validationReceipt); assert.deepEqual(calls[0]?.[1].p_evidence, evidence); assert.equal(calls[0]?.[1].p_turn && typeof calls[0]?.[1].p_turn, "object"); assert.equal("outputValidationReceipt" in (calls[0]?.[1].p_turn as object), false); }); test("a completed unverified range uses the non-confirming completion RPC", async () => { let called = ""; const completedTurn = { ...firstTurn, status: "completed" as const, turnVersion: 1, evidenceRequest: null, actions: [] as const, }; const store = new ConversationalRectificationStore(rpcClient((name) => { called = name; return { ...storedRow, status: "completed", turn_version: 1, latest_turn: completedTurn }; })); const result = await store.saveTurn({ userId, caseId, actionId, expectedVersion: 0, commandFingerprint, userMessage: "这些经历已经补充完了", turn: completedTurn, evidence: [], validationReceipt, privateCandidate: { resultId, calculationVersion: "rectification-v3.1" }, }); assert.equal(called, "complete_conversational_rectification_with_range_and_history"); assert.equal(result.status, "completed"); assert.equal(result.latestTurn.candidate.status, "pending_validation"); }); test("maps only exact allowlisted database failures to stable domain codes", () => { const exact = [ ["conversational_case_not_found", "case_not_found"], ["conversational_stale_turn", "stale_turn"], ["conversational_action_conflict", "action_conflict"], ["conversational_candidate_changed", "candidate_changed"], ["conversational_billing_failed", "billing_failed"], ] as const; for (const [message, code] of exact) { const mapped = mapConversationalRectificationStoreError({ code: "P0001", message }); assert.ok(mapped instanceof ConversationalRectificationError); assert.equal(mapped.code, code); } assert.equal( mapConversationalRectificationStoreError({ code: "08006", message: "conversational_stale_turn" }).code, "store_unavailable", ); assert.equal( mapConversationalRectificationStoreError({ code: "P0001", message: "raw database detail" }).code, "store_unavailable", ); }); test("unknown Supabase errors never survive in the store or public error", async () => { const rawMessage = "WebKit SQL password=model-secret"; const store = new ConversationalRectificationStore({ async rpc() { return { data: null, error: { code: "08006", message: rawMessage } }; }, }); let caught: unknown; try { await store.loadCase({ userId, caseId }); } catch (error) { caught = error; } assert.ok(caught instanceof ConversationalRectificationError); assert.equal(caught.code, "store_unavailable"); assert.equal(JSON.stringify(caught).includes(rawMessage), false); assert.equal(JSON.stringify(toConversationalRectificationPublicError(caught)).includes(rawMessage), false); }); test("billing sends the server fee and never converts completion into a debit", async () => { const calls: Array<[string, Readonly>]> = []; const billing = new ConversationalRectificationBilling(rpcClient((name, args) => { calls.push([name, args]); return { success: true, credits: 7, billing_state: name.includes("complete") ? "charged" : name.includes("release") ? "released" : "reserved", error_code: null, }; })); const common = { userId, caseId, actionId: caseId, expectedVersion: 0 }; assert.equal((await billing.reserve({ ...common, price: 3 })).billingState, "reserved"); assert.equal((await billing.complete(common)).billingState, "charged"); assert.equal((await billing.release({ ...common, price: 3 })).billingState, "released"); assert.deepEqual(calls.map(([name]) => name), [ "reserve_conversational_rectification_fee", "complete_conversational_rectification_fee", "release_conversational_rectification_fee", ]); assert.equal(calls[0]?.[1].p_price, 3); assert.equal("p_price" in (calls[1]?.[1] ?? {}), false); assert.equal(calls[2]?.[1].p_price, 3); }); test("billing rejections use stable domain errors instead of raw RPC messages", async () => { const insufficient = new ConversationalRectificationBilling(rpcClient(() => ({ success: false, credits: 0, billing_state: null, error_code: "insufficient_credits", }))); await assert.rejects( insufficient.reserve({ userId, caseId, actionId: caseId, expectedVersion: 0, price: 3 }), (error: unknown) => error instanceof ConversationalRectificationError && error.code === "insufficient_credits", ); const failed = new ConversationalRectificationBilling(rpcClient(() => ({ success: false, credits: 7, billing_state: "released", error_code: "already_charged", }))); await assert.rejects( failed.release({ userId, caseId, actionId: caseId, expectedVersion: 0, price: 3 }), (error: unknown) => error instanceof ConversationalRectificationError && error.code === "billing_failed", ); }); test("declared birth input is strict, source-aware, bounded, and location-complete", () => { assert.equal(declaredBirthInputSchema.safeParse(storedRow.declared_birth_input).success, true); const commonDeclaration = { birthDate: "1990-01-01", birthTimeClue: null, birthplace: { city: "Taipei", latitude: 25.03, longitude: 121.56, timezoneOffset: 8, }, }; assert.equal(declaredBirthInputSchema.safeParse({ ...commonDeclaration, source: "unknown", birthplace: { city: "台湾 · 台北市", placeId: "1668341", placeType: "locality", provider: "geonames", countryCode: "TW", latitude: 25.03, longitude: 121.56, timezoneId: "Asia/Taipei", timezoneSource: "iana_historical", timezoneOffset: 8, }, }).success, true); assert.equal(declaredBirthInputSchema.safeParse({ ...commonDeclaration, source: "unknown", birthplace: { placeId: "provider-place-id", latitude: 40.7128, longitude: -74.006, timezoneId: "America/New_York", timezoneOffset: -5, }, }).success, true); for (const value of [ { ...commonDeclaration, source: "hospital_record", reportedTime: "05:20", uncertaintyBeforeMinutes: 2, uncertaintyAfterMinutes: 2 }, { ...commonDeclaration, source: "family_exact", reportedTime: "05:20", uncertaintyBeforeMinutes: 10, uncertaintyAfterMinutes: 10 }, { ...commonDeclaration, source: "period_only", reportedPeriod: "morning" }, { ...commonDeclaration, source: "unknown" }, { ...commonDeclaration, source: "legacy_import", reportedTime: "05:20", uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0 }, ]) { assert.equal(declaredBirthInputSchema.safeParse(value).success, true); } const invalid = [ { ...storedRow.declared_birth_input, unexpected: true }, { ...storedRow.declared_birth_input, birthDate: "2023-02-30" }, { ...storedRow.declared_birth_input, birthTimeClue: undefined }, { ...storedRow.declared_birth_input, birthplace: undefined }, { ...storedRow.declared_birth_input, source: "unrecognized" }, { ...storedRow.declared_birth_input, uncertaintyAfterMinutes: 60, }, { ...storedRow.declared_birth_input, reportedPeriod: "morning", }, { ...storedRow.declared_birth_input, birthplace: { ...storedRow.declared_birth_input.birthplace, latitude: 91 }, }, { ...storedRow.declared_birth_input, birthplace: { timezoneOffset: 8 }, }, ]; for (const value of invalid) { assert.equal(declaredBirthInputSchema.safeParse(value).success, false); } const exactClue = declaredBirthInputSchema.parse({ ...storedRow.declared_birth_input, birthTimeClue: " exact clue spacing ", }); assert.equal(exactClue.birthTimeClue, " exact clue spacing "); }); test("durable JSON uses PostgreSQL-stable numeric vectors recursively", () => { const stableVector = { zero: 0, minFraction: 0.000001, ieeeRoundingBoundary: 1.000001, decimal: 0.123456, maxSafe: 9_007_199_254_740_991, }; assert.equal(postgresJsonbTextBytes(stableVector), 120); assert.equal(declaredBirthInputSchema.safeParse({ ...storedRow.declared_birth_input, birthplace: { ...storedRow.declared_birth_input.birthplace, latitude: 25.0268, longitude: 121.5434, timezoneOffset: 8, }, }).success, true); assert.ok(Number.isSafeInteger(1.000001 * 1_000_000) === false); for (const candidateWeights of [[1e-7], [1e-100], [0.0000012], [0.1234567]]) { assert.equal(privateCandidateSchema.safeParse({ resultId, calculationVersion: "rectification-v3.1", candidateWeights, }).success, false); } assert.equal(privateCandidateSchema.safeParse({ resultId, calculationVersion: "rectification-v3.1", scoredHistoricalEvidence: [{ evidenceId: "00000000-0000-4000-8000-000000000105", domain: "career", candidateTime: "05:21", score: 1e-100, ruleRefs: [], }], }).success, false); assert.equal(declaredBirthInputSchema.safeParse({ ...storedRow.declared_birth_input, birthplace: { ...storedRow.declared_birth_input.birthplace, latitude: 1e-100, }, }).success, false); assert.equal(postgresJsonbTextBytes({ nested: [{ score: 1e-100 }] }), Number.POSITIVE_INFINITY); }); test("durable text uses ECMAScript nonblank and UTF-16 maximum semantics", () => { const unicodeWhitespace = "\u00a0\u2007\ufeff"; const validAstral80 = "😀".repeat(40); const invalidAstral82 = "😀".repeat(41); assert.equal(validationReceiptSchema.safeParse({ modelId: "😀".repeat(60), schemaValidated: true, }).success, true); assert.equal(validationReceiptSchema.safeParse({ modelId: "😀".repeat(61), schemaValidated: true, }).success, false); assert.equal(privateCandidateSchema.safeParse({ resultId, calculationVersion: validAstral80, }).success, true); assert.equal(privateCandidateSchema.safeParse({ resultId, calculationVersion: invalidAstral82, }).success, false); for (const invalid of [ { ...firstTurn, narrative: unicodeWhitespace }, { ...firstTurn, technicalReceipt: { ...firstTurn.technicalReceipt, calculationVersion: unicodeWhitespace }, }, { ...firstTurn, technicalReceipt: { ...firstTurn.technicalReceipt, stableLayers: [unicodeWhitespace] }, }, { ...firstTurn, evidenceRecap: [{ id: resultId, summary: unicodeWhitespace, dateLabel: "2020-01" }], }, { ...firstTurn, pendingConsultationQuestion: unicodeWhitespace }, ]) { assert.equal(conversationalRectificationTurnSchema.safeParse(invalid).success, false); } for (const invalid of [ { modelId: unicodeWhitespace, schemaValidated: true }, { modelId: "model", schemaValidated: true, validatorVersion: unicodeWhitespace }, { modelId: "model", schemaValidated: true, issues: [unicodeWhitespace] }, ]) { assert.equal(validationReceiptSchema.safeParse(invalid).success, false); } for (const invalid of [ { resultId, calculationVersion: unicodeWhitespace }, { resultId, calculationVersion: "v1", candidateModelRefs: [unicodeWhitespace] }, { resultId, calculationVersion: "v1", futureWindows: [{ label: unicodeWhitespace, startDate: "2020-01-01", endDate: "2020-01-02", scoreable: false, }], }, { resultId, calculationVersion: "v1", workingState: { phase: "initial", iteration: 0, notes: [unicodeWhitespace] }, }, ]) { assert.equal(privateCandidateSchema.safeParse(invalid).success, false); } assert.equal(declaredBirthInputSchema.safeParse({ ...storedRow.declared_birth_input, birthTimeClue: unicodeWhitespace, }).success, false); assert.equal(declaredBirthInputSchema.safeParse({ ...storedRow.declared_birth_input, birthplace: { ...storedRow.declared_birth_input.birthplace, city: unicodeWhitespace }, }).success, false); }); test("evidence recap enforces the SQL-matched aggregate byte limit", () => { const evidenceRecap = Array.from({ length: 9 }, (_, index) => ({ id: `00000000-0000-4000-8000-${(700 + index).toString().padStart(12, "0")}`, summary: "事".repeat(900), dateLabel: "2020-01", })); const turn = { ...firstTurn, evidenceRecap }; assert.ok(postgresJsonbTextBytes(evidenceRecap) > 24_576); assert.ok(postgresJsonbTextBytes(turn) < 65_536); assert.equal(conversationalRectificationTurnSchema.safeParse(turn).success, false); }); test("nested receipt and private-candidate arrays match SQL UTF-8 byte caps", () => { const fullLayer = "事".repeat(80); const receiptAtLimit = [ ...Array.from({ length: 16 }, () => fullLayer), `${"事".repeat(62)}aa`, ]; const receiptOverLimit = [ ...Array.from({ length: 16 }, () => fullLayer), "事".repeat(61), "aa", ]; assert.equal(postgresJsonbTextBytes(receiptAtLimit), 4_096); assert.equal(postgresJsonbTextBytes(receiptOverLimit), 4_097); for (const field of ["stableLayers", "sensitiveLayers"] as const) { assert.equal(conversationalRectificationTurnSchema.safeParse({ ...firstTurn, technicalReceipt: { ...firstTurn.technicalReceipt, [field]: receiptAtLimit }, }).success, true, `${field} at its SQL byte limit`); assert.equal(conversationalRectificationTurnSchema.safeParse({ ...firstTurn, technicalReceipt: { ...firstTurn.technicalReceipt, [field]: receiptOverLimit }, }).success, false, `${field} above its SQL byte limit`); } const fullModelReference = "事".repeat(120); const modelReferencesAtLimit = [ ...Array.from({ length: 44 }, () => fullModelReference), "事".repeat(119), "aaa", ]; const modelReferencesOverLimit = [ ...Array.from({ length: 44 }, () => fullModelReference), "事".repeat(119), "aaaa", ]; assert.equal(postgresJsonbTextBytes(modelReferencesAtLimit), 16_384); assert.equal(postgresJsonbTextBytes(modelReferencesOverLimit), 16_385); const supportedLayersAtLimit = [ ...Array.from({ length: 33 }, () => fullLayer), `${"事".repeat(45)}a`, ]; const supportedLayersOverLimit = [ ...Array.from({ length: 33 }, () => fullLayer), `${"事".repeat(45)}aa`, ]; assert.equal(postgresJsonbTextBytes(supportedLayersAtLimit), 8_192); assert.equal(postgresJsonbTextBytes(supportedLayersOverLimit), 8_193); for (const [field, atLimit, overLimit] of [ ["candidateModelRefs", modelReferencesAtLimit, modelReferencesOverLimit], ["supportedSensitiveLayers", supportedLayersAtLimit, supportedLayersOverLimit], ] as const) { assert.equal(privateCandidateSchema.safeParse({ resultId, calculationVersion: "rectification-v3.1", [field]: atLimit, }).success, true, `${field} at its SQL byte limit`); assert.equal(privateCandidateSchema.safeParse({ resultId, calculationVersion: "rectification-v3.1", [field]: overLimit, }).success, false, `${field} above its SQL byte limit`); } }); test("life-event evidence rejects unknown, blank, and non-boolean durable values", () => { const evidence = { id: "00000000-0000-4000-8000-000000000105", rawText: "2019 年 7 月换工作", domain: "career" as const, eventSummary: "换工作", dateValue: "2019-07", datePrecision: "month" as const, extractionStatus: "clear" as const, scoreable: true, }; assert.equal(lifeEventEvidenceSchema.safeParse(evidence).success, true); assert.equal(lifeEventEvidenceSchema.safeParse({ ...evidence, extractionStatus: "corrected", correctsEvidenceIds: ["00000000-0000-4000-8000-000000000109"], }).success, true); assert.equal(lifeEventEvidenceSchema.safeParse({ ...evidence, extractionStatus: "needs_clarification", scoreable: false, correctsEvidenceIds: ["00000000-0000-4000-8000-000000000109"], }).success, true); for (const invalid of [ { ...evidence, extra: "discard me" }, { ...evidence, eventSummary: " \t " }, { ...evidence, dateValue: " \t " }, { ...evidence, scoreable: null }, { ...evidence, scoreable: "true" }, { ...evidence, extractionStatus: "corrected", correctsEvidenceIds: [] }, { ...evidence, correctsEvidenceIds: ["not-a-uuid"] }, { ...evidence, extractionStatus: "corrected", correctsEvidenceIds: [ "00000000-0000-4000-8000-000000000109", "00000000-0000-4000-8000-000000000110", ], }, ]) { assert.equal(lifeEventEvidenceSchema.safeParse(invalid).success, false); } }); test("durable private and receipt schemas accept boundaries and reject oversize or unknown fields", () => { assert.equal(postgresJsonbTextBytes({ a: [1, 2] }), 13); assert.equal(validationReceiptSchema.safeParse({ modelId: "m".repeat(120), schemaValidated: true, validatorVersion: "v".repeat(80), retryCount: 2, fallbackUsed: false, issues: ["i".repeat(240)], }).success, true); assert.equal(validationReceiptSchema.safeParse({ modelId: "m".repeat(121), schemaValidated: true, }).success, false); assert.equal(validationReceiptSchema.safeParse({ modelId: `${"m".repeat(120)} `, schemaValidated: true, }).success, false); assert.equal(validationReceiptSchema.safeParse({ modelId: "model", schemaValidated: true, validatedAt: "2026-07-20T12:00:00.123456789012345678901Z", }).success, false); const candidate = { resultId, representativeTime: "05:21", calculationVersion: "rectification-v3.1", candidateWeights: Array.from({ length: 1_440 }, () => 0.5), candidateModelRefs: ["model-ref"], suggestedDomains: ["career", "relocation"], }; assert.equal(privateCandidateSchema.safeParse(candidate).success, true); assert.equal(privateCandidateSchema.safeParse({ ...candidate, candidateWeights: [...candidate.candidateWeights, 0.5], }).success, false); assert.equal(privateCandidateSchema.safeParse({ calculationVersion: "v1", rangeStart: null, }).success, false); assert.equal(privateCandidateSchema.safeParse({ ...candidate, secretExtra: true }).success, false); const request = { kind: "save_turn", userId, caseId, expectedVersion: 0, actionId, requestFingerprint: "a".repeat(64), commandFingerprint, }; assert.equal(conversationalRectificationActionReceiptRequestSchema.safeParse(request).success, true); assert.equal(conversationalRectificationActionReceiptRequestSchema.safeParse({ ...request, extra: "not durable", }).success, false); assert.equal(conversationalRectificationActionReceiptRequestSchema.safeParse({ ...request, requestFingerprint: "a".repeat(65), }).success, false); assert.equal(conversationalRectificationActionReceiptRequestSchema.safeParse({ ...request, commandFingerprint: "c".repeat(63), }).success, false); assert.equal(conversationalRectificationActionReceiptResponseSchema.safeParse({ success: false, credits: 7, billing_state: null, error_code: "e".repeat(80), }).success, true); assert.equal(conversationalRectificationActionReceiptResponseSchema.safeParse({ success: false, credits: 7, billing_state: null, error_code: "e".repeat(81), }).success, false); }); test("durable UUID text is canonical hyphenated syntax and case-insensitive", () => { const canonical = "a9890e09-d535-46f0-9a36-86017515a5a1"; const compact = canonical.replaceAll("-", ""); const braced = `{${canonical}}`; for (const validResultId of [canonical, canonical.toUpperCase()]) { assert.equal(privateCandidateSchema.safeParse({ resultId: validResultId, calculationVersion: "v1", }).success, true); } for (const invalidResultId of [compact, braced]) { assert.equal(privateCandidateSchema.safeParse({ resultId: invalidResultId, calculationVersion: "v1", }).success, false); assert.equal(conversationalRectificationTurnSchema.safeParse({ ...firstTurn, evidenceRecap: [{ id: invalidResultId, summary: "summary", dateLabel: "2020-01" }], }).success, false); } }); test("public turn JSON fields reject field and byte boundary violations", () => { assert.equal(conversationalRectificationActionReceiptResponseSchema.safeParse({ ...storedRow, declared_birth_input: undefined, private_candidate: undefined, event_evidence: undefined, validation_receipts: undefined, }).success, true); const exact = { ...firstTurn, technicalReceipt: { ...firstTurn.technicalReceipt, calculationVersion: "v".repeat(80), }, evidenceRecap: [{ id: "00000000-0000-4000-8000-000000000108", summary: "事".repeat(1_000), dateLabel: "d".repeat(80), }], }; assert.equal(conversationalRectificationActionReceiptResponseSchema.safeParse({ ...storedRow, latest_turn: exact, declared_birth_input: undefined, private_candidate: undefined, event_evidence: undefined, validation_receipts: undefined, }).success, true); const nearTurnLimit = { ...exact, narrative: "n".repeat(12_000), evidenceRecap: Array.from({ length: 7 }, (_, index) => ({ id: `00000000-0000-4000-8000-${(300 + index).toString().padStart(12, "0")}`, summary: "事".repeat(1_000), dateLabel: "d".repeat(80), })), }; assert.equal(conversationalRectificationActionReceiptResponseSchema.safeParse({ ...storedRow, latest_turn: nearTurnLimit, declared_birth_input: undefined, private_candidate: undefined, event_evidence: undefined, validation_receipts: undefined, }).success, true); for (const latest_turn of [ { ...exact, candidate: { ...exact.candidate, unknown: true }, }, { ...exact, technicalReceipt: { ...exact.technicalReceipt, calculationVersion: "v".repeat(81) }, }, { ...exact, evidenceRecap: [{ ...exact.evidenceRecap[0], summary: "事".repeat(1_001) }], }, { ...exact, narrative: "n".repeat(12_000), evidenceRecap: Array.from({ length: 20 }, (_, index) => ({ id: `00000000-0000-4000-8000-${(200 + index).toString().padStart(12, "0")}`, summary: "事".repeat(1_000), dateLabel: "d".repeat(80), })), }, ]) { assert.equal(conversationalRectificationActionReceiptResponseSchema.safeParse({ ...storedRow, latest_turn, declared_birth_input: undefined, private_candidate: undefined, event_evidence: undefined, validation_receipts: undefined, }).success, false); } }); test("store rejects invalid durable inputs before issuing an RPC", async () => { let calls = 0; const store = new ConversationalRectificationStore(rpcClient(() => { calls += 1; return storedRow; })); await assert.rejects(store.createCaseWithFirstTurn({ userId, caseId, actionId: caseId, expectedVersion: 0, revisionOfCaseId: null, pendingConsultationQuestion: null, declaredBirthInput: { birthDate: "1990-01-01", source: "approximate", reportedTime: "05:20", birthTimeClue: null, uncertaintyBeforeMinutes: 30, uncertaintyAfterMinutes: 30, } as never, firstTurn, validationReceipt, privateCandidate: { resultId, calculationVersion: "rectification-v3.1", }, }), (error: unknown) => error instanceof ConversationalRectificationError && error.code === "action_conflict"); assert.equal(calls, 0); });