fix: harden conversational persistence boundaries

This commit is contained in:
Jesse_Chen
2026-07-20 23:11:01 +08:00
parent d6b3ecdb31
commit c883c401aa
5 changed files with 531 additions and 102 deletions
@@ -82,6 +82,10 @@ const evidenceRecapEntrySchema = boundedJson(z.object({
summary: boundedNonblankText(1_000),
dateLabel: boundedNonblankText(80),
}).strict(), 4_096);
const evidenceRecapSchema = boundedJson(
z.array(evidenceRecapEntrySchema).max(20),
24_576,
);
export const conversationalRectificationTurnSchema = boundedJson(z.object({
caseId: caseIdSchema,
@@ -92,7 +96,7 @@ export const conversationalRectificationTurnSchema = boundedJson(z.object({
candidate: candidateSchema,
technicalReceipt: technicalReceiptSchema,
evidenceRequest: evidenceRequestSchema.nullable(),
evidenceRecap: z.array(evidenceRecapEntrySchema).max(20),
evidenceRecap: evidenceRecapSchema,
actions: z.array(z.enum([
"answer",
"pause",
@@ -1,5 +1,32 @@
import { z } from "zod";
const POSTGRES_JSON_DECIMAL_SCALE = 1_000_000;
const POSTGRES_JSON_MIN_FRACTION = 1 / POSTGRES_JSON_DECIMAL_SCALE;
const POSTGRES_JSON_MAX_FRACTION_MAGNITUDE = 1_000_000;
function postgresStableJsonNumber(value: number): boolean {
if (!Number.isFinite(value)) return false;
if (Number.isSafeInteger(value)) return true;
const magnitude = Math.abs(value);
return magnitude >= POSTGRES_JSON_MIN_FRACTION
&& magnitude <= POSTGRES_JSON_MAX_FRACTION_MAGNITUDE
&& Number.isSafeInteger(value * POSTGRES_JSON_DECIMAL_SCALE);
}
function postgresJsonNumbersAreStable(
value: unknown,
ancestors: Set<object> = new Set<object>(),
): boolean {
if (typeof value === "number") return postgresStableJsonNumber(value);
if (value === null || typeof value !== "object") return true;
if (ancestors.has(value)) return false;
ancestors.add(value);
const values = Array.isArray(value) ? value : Object.values(value);
const stable = values.every((item) => postgresJsonNumbersAreStable(item, ancestors));
ancestors.delete(value);
return stable;
}
function postgresSeparatorBytes(value: unknown): number {
if (Array.isArray(value)) {
return Math.max(0, value.length - 1)
@@ -13,9 +40,15 @@ function postgresSeparatorBytes(value: unknown): number {
return 0;
}
/** Matches PostgreSQL jsonb::text, which adds one space after each comma and colon. */
/**
* Matches PostgreSQL jsonb::text for the durable numeric contract: safe
* integers or nonzero decimals with at most six places and magnitude <= 1e6.
* PostgreSQL expands exponent-form numerics, so values outside that contract
* return Infinity instead of undercounting their durable representation.
*/
export function postgresJsonbTextBytes(value: unknown): number {
try {
if (!postgresJsonNumbersAreStable(value)) return Number.POSITIVE_INFINITY;
const compact = JSON.stringify(value);
if (compact === undefined) return Number.POSITIVE_INFINITY;
const serializedValue: unknown = JSON.parse(compact);