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

@@ -218,3 +218,65 @@ describe("parseTrace", () => {
assert.throws(() => parseTrace(file), /invalid_type|Required/i);
});
});
describe("validateTrace > compliance frontmatter (ADR-022 amendment)", () => {
test("accepts is-sub-processor / processes-pii as YAML string booleans", () => {
const parsed = validateTrace(
validRaw({ "is-sub-processor": "false", "processes-pii": "true" }),
);
assert.equal(parsed["is-sub-processor"], false);
assert.equal(parsed["processes-pii"], true);
});
test("accepts a trace that omits lastRevalidated entirely", () => {
const raw = validRaw();
delete raw.lastRevalidated;
assert.equal(validateTrace(raw).lastRevalidated, undefined);
});
test("accepts a full sub-processor block", () => {
const parsed = validateTrace(
validRaw({
"is-sub-processor": "true",
"processes-pii": "true",
"data-sent": "error events, stack traces",
region: "eu",
"dpa-signed": "true",
"sccs-required": "false",
contact: "https://example.com/dpa",
}),
);
assert.equal(parsed["is-sub-processor"], true);
assert.equal(parsed["data-sent"], "error events, stack traces");
});
test("rejects is-sub-processor: true without data-sent", () => {
assert.throws(
() => validateTrace(validRaw({ "is-sub-processor": "true" })),
/data-sent/,
);
});
});
describe("parseTrace > every committed trace parses", () => {
// The schema exists to validate the traces actually in the repo — a schema
// that rejects committed, approved traces is broken (it silently rejected
// 38 of 39 before lastRevalidated became optional and the compliance
// fields were added).
test("all docs/library-decisions traces validate", () => {
const docsDir = path.join(
path.dirname(new URL(import.meta.url).pathname),
"..",
"..",
"docs",
"library-decisions",
);
const files = fs
.globSync(path.join(docsDir, "**", "*.md"))
.filter((f) => !f.endsWith("_template.md"));
assert.ok(files.length > 0, "expected committed traces to exist");
for (const f of files) {
assert.doesNotThrow(() => parseTrace(f), `trace failed to parse: ${f}`);
}
});
});