fix(library-decisions): traceSchema accepts the committed trace shape

The strict schema rejected 38 of 39 approved traces in
docs/library-decisions: lastRevalidated was required (most traces omit
it), and the compliance frontmatter fields the _template.md documents
(is-sub-processor, processes-pii, plus the sub-processor block:
data-sent, region, dpa-signed, sccs-required, contact) were
unrecognized keys. Make lastRevalidated optional, add the compliance
fields (booleanish coercion for YAML string scalars), and require
data-sent when is-sub-processor is true. New loop test asserts every
committed trace parses so the schema can never drift away from the
repo's own corpus again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 16:47:18 +02:00
parent 5f38a3efce
commit eccd8b0cc1
2 changed files with 92 additions and 2 deletions

View File

@@ -7,6 +7,12 @@ import fs from "node:fs";
// ---- Zod schema ----
/** YAML scalars arrive as strings — coerce "true"/"false" (or booleans). */
const booleanish = z.preprocess(
(v) => (v === "true" ? true : v === "false" ? false : v),
z.boolean(),
);
const filterResultsSchema = z
.object({
license: z.string().min(1),
@@ -30,12 +36,34 @@ export const traceSchema = z
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "date must be YYYY-MM-DD"),
deciders: z.array(z.string()),
adr: z.string().nullable(),
lastRevalidated: z.string().nullable(),
// Optional: most committed traces have never been revalidated and omit
// the key entirely (requiring it rejected 38 of 39 approved traces).
lastRevalidated: z.string().nullable().optional(),
"filter-results": filterResultsSchema,
"verification-commands": z.array(z.string()),
"accepted-cves": z.array(z.string()).optional(),
// ADR-022 amendment (compliance frontmatter, see _template.md): every
// newer trace declares whether the dependency ships data to a third
// party; sub-processor traces carry the extra contact/DPA fields.
"is-sub-processor": booleanish.optional(),
"processes-pii": booleanish.optional(),
"data-sent": z.string().nullable().optional(),
region: z.string().nullable().optional(),
"dpa-signed": z.union([booleanish, z.string()]).nullable().optional(),
"sccs-required": z.union([booleanish, z.string()]).nullable().optional(),
contact: z.string().nullable().optional(),
})
.strict();
.strict()
.superRefine((trace, ctx) => {
if (trace["is-sub-processor"] === true && trace["data-sent"] == null) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"sub-processor traces must declare data-sent (what leaves the machine)",
path: ["data-sent"],
});
}
});
// ---- Helpers ----