fix: bound conversational nested JSON arrays

This commit is contained in:
Jesse_Chen
2026-07-20 23:58:12 +08:00
parent ae41023b96
commit 439338ae8a
3 changed files with 71 additions and 4 deletions
@@ -66,8 +66,8 @@ const candidateSchema = boundedJson(z.object({
const technicalReceiptSchema = boundedJson(z.object({
calculationVersion: boundedNonblankText(80),
stableLayers: z.array(boundedNonblankText(80)).max(20),
sensitiveLayers: z.array(boundedNonblankText(80)).max(20),
stableLayers: boundedJson(z.array(boundedNonblankText(80)).max(20), 4_096),
sensitiveLayers: boundedJson(z.array(boundedNonblankText(80)).max(20), 4_096),
candidateDifferenceRefs: z.array(boundedNonblankText(120)).max(40),
}).strict(), 8_192);
@@ -152,10 +152,10 @@ export const privateCandidateSchema = boundedJson(z.object({
rangeEnd: timeSchema.nullable().optional(),
calculationVersion: boundedText(80),
candidateWeights: z.array(z.number().finite().min(0).max(1)).max(1_440).optional(),
candidateModelRefs: z.array(boundedText(120)).max(80).optional(),
candidateModelRefs: boundedJson(z.array(boundedText(120)).max(80), 16_384).optional(),
d1Stability: z.enum(["stable", "sensitive", "unavailable"]).optional(),
boundaryDistanceMinutes: z.number().int().min(0).max(1_440).nullable().optional(),
supportedSensitiveLayers: z.array(boundedText(80)).max(40).optional(),
supportedSensitiveLayers: boundedJson(z.array(boundedText(80)).max(40), 8_192).optional(),
scoredHistoricalEvidence: z.array(scoredEvidenceSchema).max(100).optional(),
suggestedDomains: z.array(evidenceDomainSchema).max(6).optional(),
futureWindows: z.array(futureWindowSchema).max(20).optional(),
@@ -616,6 +616,73 @@ test("evidence recap enforces the SQL-matched aggregate byte limit", () => {
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",