From f67eec7e84e1c40436adfca69818dede71e37bd2 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:22:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(dogfood):=20ADR-0060=20P2=20=E2=80=94=20va?= =?UTF-8?q?lidation-rule=20conformance=20ledger=20(pins=20ADR-0020)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The third instance of the reusable conformance-ledger pattern, built on the same checkLedger helper (P1) — proving a new surface is a filled-in table, not a re-derived discipline. One row per `validations` union rule type (state_machine / script / cross_field / format / json_schema / conditional), all ENFORCED on the engine write path via rule-validator.ts (ADR-0020 wired the union in), each carrying the rule-validator runtime proof. The ratchet re-discovers every rule type from validation.zod.ts's discriminator and fails the build if one is unclassified — so a new validation rule type added without enforcement (the pre-ADR-0020 state_machine disease) can't ship silently. Ratchet verified to bite. dogfood-private, no changeset. Co-Authored-By: Claude Opus 4.8 --- .../test/validation-conformance.ledger.ts | 71 +++++++++++++++++++ .../test/validation-conformance.test.ts | 46 ++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 packages/dogfood/test/validation-conformance.ledger.ts create mode 100644 packages/dogfood/test/validation-conformance.test.ts diff --git a/packages/dogfood/test/validation-conformance.ledger.ts b/packages/dogfood/test/validation-conformance.ledger.ts new file mode 100644 index 0000000000..81fc0335f8 --- /dev/null +++ b/packages/dogfood/test/validation-conformance.ledger.ts @@ -0,0 +1,71 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// ADR-0060 P2 — Validation-Rule Conformance ledger (the third instance of the +// reusable pattern). One row per `validations` union rule type, each pinned to +// its runtime enforcement site and a proof. ADR-0020 wired this whole union into +// the write path (engine insert/update → rule-validator.ts), turning every rule +// type from declaration into enforcement; this ledger makes that a CHECKED fact +// and — via the ratchet — makes a NEW rule type with no enforcement a build break +// (the exact pre-ADR-0020 `state_machine` disease). + +import type { ConformanceRow } from '@objectstack/verify'; + +const RULE_VALIDATOR = 'packages/objectql/src/validation/rule-validator.ts'; +const PROOF = 'packages/objectql/src/validation/rule-validator.test.ts'; + +export const VALIDATION_SURFACE: ConformanceRow[] = [ + { + id: 'rule-state-machine', + summary: 'state_machine — legal status-transition guard (ADR-0020 D3)', + surface: 'validation.zod.ts:state_machine', + state: 'enforced', + enforcement: `${RULE_VALIDATOR} checkStateMachine — engine insert/update path rejects an illegal from→to transition`, + covers: ['state_machine'], + proof: PROOF, + }, + { + id: 'rule-script', + summary: 'script — arbitrary CEL failure predicate', + surface: 'validation.zod.ts:script', + state: 'enforced', + enforcement: `${RULE_VALIDATOR} checkPredicate — CEL predicate; TRUE = violation`, + covers: ['script'], + proof: PROOF, + }, + { + id: 'rule-cross-field', + summary: 'cross_field — multi-field CEL invariant', + surface: 'validation.zod.ts:cross_field', + state: 'enforced', + enforcement: `${RULE_VALIDATOR} checkPredicate — evaluated against the merged record (prior ∪ change)`, + covers: ['cross_field'], + proof: PROOF, + }, + { + id: 'rule-format', + summary: 'format — value-shape rule (regex/builtin)', + surface: 'validation.zod.ts:format', + state: 'enforced', + enforcement: `${RULE_VALIDATOR} checkFormat`, + covers: ['format'], + proof: PROOF, + }, + { + id: 'rule-json-schema', + summary: 'json_schema — structural validation of a JSON field', + surface: 'validation.zod.ts:json_schema', + state: 'enforced', + enforcement: `${RULE_VALIDATOR} checkJsonSchema`, + covers: ['json_schema'], + proof: PROOF, + }, + { + id: 'rule-conditional', + summary: 'conditional — when/then predicate rule', + surface: 'validation.zod.ts:conditional', + state: 'enforced', + enforcement: `${RULE_VALIDATOR} checkConditional`, + covers: ['conditional'], + proof: PROOF, + }, +]; diff --git a/packages/dogfood/test/validation-conformance.test.ts b/packages/dogfood/test/validation-conformance.test.ts new file mode 100644 index 0000000000..ecf4e42eee --- /dev/null +++ b/packages/dogfood/test/validation-conformance.test.ts @@ -0,0 +1,46 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// ADR-0060 P2 — the validation-rule conformance ledger is a CHECKED artifact, +// built on the SAME reusable checkLedger helper as the authz and expression +// ledgers (proving the pattern composes: a new surface is a filled-in table, not +// a re-derived discipline). The ratchet re-discovers every `validations` union +// rule type from spec source and fails the build if any is unclassified — so a +// new validation rule type added without runtime enforcement (the pre-ADR-0020 +// `state_machine` failure) can't ship silently. + +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { checkLedger } from '@objectstack/verify'; +import { VALIDATION_SURFACE } from './validation-conformance.ledger.js'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = join(HERE, '../../..'); +const VALIDATION_ZOD = join(REPO_ROOT, 'packages/spec/src/data/validation.zod.ts'); + +/** Re-discover every `validations` union rule type from its discriminator. */ +function discoverRuleTypes(): Set { + const src = readFileSync(VALIDATION_ZOD, 'utf8'); + const found = new Set(); + for (const m of src.matchAll(/type:\s*z\.literal\('([a-z_]+)'\)/g)) found.add(m[1]); + return found; +} + +describe('ADR-0060 P2 — validation-rule conformance ledger', () => { + it('is a sound conformance ledger + ratchet, every rule type enforced + proven', () => { + const problems = checkLedger(VALIDATION_SURFACE, { + proofRoot: REPO_ROOT, + discover: discoverRuleTypes, + proofRequiredForEnforced: true, // a write-path guard must carry a runtime proof + }); + expect(problems, problems.join('\n')).toEqual([]); + }); + + it('sanity: discovery finds the known rule types', () => { + const found = discoverRuleTypes(); + for (const t of ['state_machine', 'script', 'cross_field', 'format', 'json_schema', 'conditional']) { + expect(found.has(t), `rule type '${t}' not discovered`).toBe(true); + } + }); +});