Unify reader cleanup rules, lock writer table guards, and register the exact fictional timestamp collision. Preserve existing ordinary-report safety contracts and source-data gaps. Validation: report Node 165/165, final safety 29/29, Python 101/101, Chrome 28/28; both PDFs retain all 130 rows. Full Node 3704 tests with the same 91 baseline failures. Privacy test: 62 passed, 1 failed due to 17 protected-file READ_ERRORs; not a green gate. Build, DB, manual checklist and controlled-login gaps remain documented. User explicitly authorized staging push with these gaps disclosed. Co-Authored-By: Claude Code <noreply@anthropic.com>
47 lines
2.7 KiB
TypeScript
47 lines
2.7 KiB
TypeScript
import { z } from "zod";
|
|
|
|
export const FACT_TABLE_GROUPS = ["vimshottari", "ashtakavarga", "shadbala", "functional_roles", "avasthas", "special_points", "sade_sati", "annual"] as const;
|
|
const sourcePath = z.string().min(1).max(400).regex(/^[A-Za-z0-9_.\[\]-]+$/);
|
|
const gridShape = {
|
|
sourcePath,
|
|
note: z.string().max(500),
|
|
columns: z.array(z.string().min(1).max(80)).min(1).max(16),
|
|
rows: z.array(z.object({
|
|
sourcePath,
|
|
cells: z.array(z.union([z.string().max(300), z.number().finite(), z.boolean(), z.null()])).min(1).max(16),
|
|
}).strict()).max(2000),
|
|
};
|
|
const within = (child: string, parent: string) => child === parent || child.startsWith(`${parent}.`) || child.startsWith(`${parent}[`);
|
|
function validateRows(table: { sourcePath: string; columns: string[]; rows: { sourcePath: string; cells: unknown[] }[] }, context: z.RefinementCtx) {
|
|
table.rows.forEach((row, index) => {
|
|
if (row.cells.length !== table.columns.length) context.addIssue({ code: "custom", path: ["rows", index], message: "fact_table_cell_count" });
|
|
if (!within(row.sourcePath, table.sourcePath)) context.addIssue({ code: "custom", path: ["rows", index, "sourcePath"], message: "fact_table_source_mismatch" });
|
|
});
|
|
}
|
|
export const reportFactSubtableSchema = z.object({
|
|
id: z.string().min(1).max(80).regex(/^[a-z][a-z0-9_-]*$/),
|
|
title: z.string().min(1).max(120),
|
|
...gridShape,
|
|
}).strict().superRefine(validateRows);
|
|
|
|
/** Optional subtables are additive: persisted v2 snapshots keep their original rows. */
|
|
export const reportFactTableSchema = z.object({
|
|
id: z.enum(FACT_TABLE_GROUPS),
|
|
title: z.string().min(1).max(120),
|
|
claimStatus: z.enum(["multi_system_consensus", "single_system_inference", "parameter_sensitive", "unclosed_divisional_chart", "user_history_verification_required", "blocked"]),
|
|
...gridShape,
|
|
subtables: z.array(reportFactSubtableSchema).max(8).optional(),
|
|
}).strict().superRefine((table, context) => {
|
|
validateRows(table, context);
|
|
const subtables = table.subtables ?? [];
|
|
if (new Set(subtables.map(subtable => subtable.id)).size !== subtables.length) context.addIssue({ code: "custom", path: ["subtables"], message: "duplicate_fact_subtable" });
|
|
subtables.forEach((subtable, index) => {
|
|
if (!within(subtable.sourcePath, table.sourcePath)) context.addIssue({ code: "custom", path: ["subtables", index, "sourcePath"], message: "fact_table_source_mismatch" });
|
|
});
|
|
});
|
|
export const reportFactTablesSchema = z.array(reportFactTableSchema).max(8).superRefine((tables, context) => {
|
|
if (new Set(tables.map((table) => table.id)).size !== tables.length) context.addIssue({ code: "custom", message: "duplicate_fact_table" });
|
|
});
|
|
export type ReportFactTable = z.infer<typeof reportFactTableSchema>;
|
|
export type ReportFactSubtable = z.infer<typeof reportFactSubtableSchema>;
|