Skip to content

Commit 4cf25b3

Browse files
os-zhuangclaude
andcommitted
feat(verify): ADR-0060 P1 — reusable conformance-ledger helper + unify two ledgers
Promote the conformance-ledger discipline (hand-written twice: ADR-0056 D10 authz matrix, ADR-0058 D7 expression surface) to a reusable platform capability: - @objectstack/verify gains a `conformance` module: `ConformanceRow` + `checkLedger (rows, opts): string[]` — returns problems (empty = sound), so the helper carries no test-runner dependency. It encodes the shared invariants once (unique ids, valid state, enforced-has-enforcement, experimental/removed-has-note, proof-file- exists, high-risk-has-proof, exactly-one-cover) and the ratchet (discover the real surface from source; fail on unclassified or stale covers). 12-case unit test. - authz-conformance + expression-conformance refactored onto checkLedger: one call replaces the duplicated assertion logic. The expression ledger's `site` field is unified to `enforcement` and ExprSurface now `extends ConformanceRow`; its expression-specific invariants (mode/dialect/fail-policy, compile rows name the canonical compiler) stay local. Ratchet verified still has teeth. Green: full build 75/75, dogfood 133 (incl. 16 conformance tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6ca20b3 commit 4cf25b3

7 files changed

Lines changed: 231 additions & 129 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@objectstack/verify": minor
3+
---
4+
5+
ADR-0060 P1 — add the reusable conformance-ledger helper. `@objectstack/verify`
6+
now exports `checkLedger(rows, opts)` + `ConformanceRow`: the static complement to
7+
its runtime harness, encoding the shared invariants the platform had hand-written
8+
twice (unique ids / valid state / enforced-has-site / experimental·removed-has-note
9+
/ proof-file-exists / high-risk-has-proof / exactly-one-cover / discover ratchet).
10+
The ADR-0056 authz and ADR-0058 expression ledgers are refactored onto it.
Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,25 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22
//
3-
// ADR-0056 D10 — the conformance matrix is a CHECKED artifact. These assertions
4-
// make "every authorization primitive is in exactly one honest state, and every
5-
// claimed proof exists" a green CI gate. A new fail-open (enforced row with no
6-
// site/proof) or a deleted proof file breaks the build.
3+
// ADR-0056 D10 — the authorization conformance matrix is a CHECKED artifact.
4+
// Refactored onto the reusable ADR-0060 `checkLedger` helper: one call asserts
5+
// every shared invariant (valid state, enforced-has-site, experimental/removed-
6+
// has-note, proof-file-exists, high-risk-has-proof). A new fail-open or a deleted
7+
// proof breaks the build.
78

8-
import { describe, it, expect } from 'vitest';
9-
import { existsSync } from 'node:fs';
9+
import { describe, expect, it } from 'vitest';
1010
import { fileURLToPath } from 'node:url';
11-
import { dirname, join } from 'node:path';
12-
import { AUTHZ_CONFORMANCE, type AuthzPrimitive } from './authz-conformance.matrix.js';
11+
import { dirname } from 'node:path';
12+
import { checkLedger } from '@objectstack/verify';
13+
import { AUTHZ_CONFORMANCE } from './authz-conformance.matrix.js';
1314

1415
const HERE = dirname(fileURLToPath(import.meta.url));
15-
const VALID = new Set(['enforced', 'experimental', 'removed']);
1616

1717
describe('ADR-0056 D10 — authorization conformance matrix', () => {
18-
it('has no duplicate primitive ids', () => {
19-
const ids = AUTHZ_CONFORMANCE.map((p) => p.id);
20-
expect(new Set(ids).size).toBe(ids.length);
21-
});
22-
23-
it('every primitive is in exactly one honest state', () => {
24-
for (const p of AUTHZ_CONFORMANCE) {
25-
expect(VALID.has(p.state), `${p.id} has invalid state '${p.state}'`).toBe(true);
26-
}
27-
});
28-
29-
it('every ENFORCED primitive declares an enforcement site (no silent claims)', () => {
30-
const missing = AUTHZ_CONFORMANCE.filter((p) => p.state === 'enforced' && !p.enforcement).map((p) => p.id);
31-
expect(missing, `enforced primitives missing an enforcement site: ${missing.join(', ')}`).toEqual([]);
32-
});
33-
34-
it('every experimental/removed primitive carries a note (honest rationale)', () => {
35-
const missing = AUTHZ_CONFORMANCE.filter((p) => p.state !== 'enforced' && !p.note).map((p) => p.id);
36-
expect(missing, `non-enforced primitives missing a note: ${missing.join(', ')}`).toEqual([]);
37-
});
38-
39-
it('every referenced dogfood proof FILE EXISTS (the ratchet)', () => {
40-
const broken: string[] = [];
41-
for (const p of AUTHZ_CONFORMANCE as AuthzPrimitive[]) {
42-
if (p.proof && !existsSync(join(HERE, p.proof))) broken.push(`${p.id}${p.proof}`);
43-
}
44-
expect(broken, `conformance proofs missing on disk: ${broken.join(', ')}`).toEqual([]);
45-
});
46-
47-
it('the high-risk owner/derived OWD primitives each carry an end-to-end proof', () => {
48-
const highRisk = ['owd-private', 'owd-public-read', 'controlled-by-parent', 'anonymous-deny', 'default-profile'];
49-
for (const id of highRisk) {
50-
const p = AUTHZ_CONFORMANCE.find((x) => x.id === id);
51-
expect(p, `missing matrix entry: ${id}`).toBeTruthy();
52-
expect(p!.proof, `${id} must carry a dogfood proof`).toBeTruthy();
53-
}
18+
it('is a sound conformance ledger (ADR-0060 checkLedger)', () => {
19+
const problems = checkLedger(AUTHZ_CONFORMANCE, {
20+
proofRoot: HERE, // proofs are dogfood test files alongside this one
21+
highRisk: ['owd-private', 'owd-public-read', 'controlled-by-parent', 'anonymous-deny', 'default-profile'],
22+
});
23+
expect(problems, problems.join('\n')).toEqual([]);
5424
});
5525
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// ADR-0060 P1 — unit coverage for the reusable `checkLedger` helper. The two
4+
// real ledgers (authz, expression) exercise it end-to-end; this pins each
5+
// invariant in isolation.
6+
7+
import { describe, expect, it } from 'vitest';
8+
import { fileURLToPath } from 'node:url';
9+
import { dirname } from 'node:path';
10+
import { checkLedger, type ConformanceRow } from '@objectstack/verify';
11+
12+
const HERE = dirname(fileURLToPath(import.meta.url));
13+
const ok = (extra: Partial<ConformanceRow> = {}): ConformanceRow =>
14+
({ id: 'a', summary: 's', state: 'enforced', enforcement: 'site', ...extra });
15+
16+
describe('checkLedger (ADR-0060)', () => {
17+
it('a sound ledger yields no problems', () => {
18+
expect(checkLedger([ok()], { proofRoot: HERE })).toEqual([]);
19+
});
20+
it('flags duplicate ids', () => {
21+
expect(checkLedger([ok(), ok()], { proofRoot: HERE }).some((x) => x.includes('duplicate id'))).toBe(true);
22+
});
23+
it('flags invalid state', () => {
24+
expect(checkLedger([{ id: 'a', summary: 's', state: 'bogus' as never }], { proofRoot: HERE }).some((x) => x.includes('invalid state'))).toBe(true);
25+
});
26+
it('flags enforced-without-enforcement', () => {
27+
expect(checkLedger([{ id: 'a', summary: 's', state: 'enforced' }], { proofRoot: HERE }).some((x) => x.includes('names no enforcement'))).toBe(true);
28+
});
29+
it('flags experimental-without-note', () => {
30+
expect(checkLedger([{ id: 'a', summary: 's', state: 'experimental' }], { proofRoot: HERE }).some((x) => x.includes('carries no note'))).toBe(true);
31+
});
32+
it('flags a missing proof file; accepts an existing one', () => {
33+
expect(checkLedger([ok({ proof: 'does/not/exist.ts' })], { proofRoot: HERE }).some((x) => x.includes('proof missing on disk'))).toBe(true);
34+
expect(checkLedger([ok({ proof: 'conformance-helper.test.ts' })], { proofRoot: HERE })).toEqual([]);
35+
});
36+
it('high-risk must carry a proof', () => {
37+
expect(checkLedger([ok()], { proofRoot: HERE, highRisk: ['a'] }).some((x) => x.includes('must carry a proof'))).toBe(true);
38+
});
39+
it('proofRequiredForEnforced flags enforced-without-proof', () => {
40+
expect(checkLedger([ok()], { proofRoot: HERE, proofRequiredForEnforced: true }).some((x) => x.includes('carries no proof'))).toBe(true);
41+
});
42+
it('flags a surface classified by two rows', () => {
43+
expect(checkLedger([ok({ id: 'a', covers: ['x'] }), ok({ id: 'b', covers: ['x'] })], { proofRoot: HERE }).some((x) => x.includes('more than one row'))).toBe(true);
44+
});
45+
it('ratchet: unclassified discovered surface', () => {
46+
expect(checkLedger([ok({ covers: ['x'] })], { proofRoot: HERE, discover: () => ['x', 'y'] }).some((x) => x.includes('UNCLASSIFIED surface') && x.includes('y'))).toBe(true);
47+
});
48+
it('ratchet: stale covers', () => {
49+
expect(checkLedger([ok({ covers: ['x', 'z'] })], { proofRoot: HERE, discover: () => ['x'] }).some((x) => x.includes('STALE covers') && x.includes('z'))).toBe(true);
50+
});
51+
it('ratchet: fully covered yields no problems', () => {
52+
expect(checkLedger([ok({ covers: ['x', 'y'] })], { proofRoot: HERE, discover: () => ['x', 'y'] })).toEqual([]);
53+
});
54+
});

packages/dogfood/test/expression-conformance.ledger.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import type { ConformanceRow } from '@objectstack/verify';
24
//
35
// ADR-0058 D7 — Expression Surface Conformance ledger.
46
//
@@ -24,21 +26,14 @@ export type ExprState = 'enforced' | 'experimental' | 'removed';
2426
/** ADR-0058 D5 fail-policy tiers. */
2527
export type FailPolicy = 'compile-error' | 'fail-closed' | 'fail-soft-log' | 'throw';
2628

27-
export interface ExprSurface {
28-
id: string;
29-
summary: string;
29+
export interface ExprSurface extends ConformanceRow {
3030
dialect: ExprDialect;
3131
mode: ExprMode;
32-
state: ExprState;
3332
failPolicy: FailPolicy;
34-
/** Runtime evaluator / compiler site. */
35-
site: string;
33+
/** Runtime evaluator / compiler site (ConformanceRow.enforcement, required here). */
34+
enforcement: string;
3635
/** `file:field` surfaces (relative to packages/spec/src) this row classifies — the ratchet keys. */
3736
covers: string[];
38-
/** Proof path (repo-root-relative). Required for ENFORCED COMPILE (security) rows. */
39-
proof?: string;
40-
/** Rationale for experimental/removed, or a roadmap pointer. */
41-
note?: string;
4237
}
4338

4439
export const EXPRESSION_SURFACE: ExprSurface[] = [
@@ -47,23 +42,23 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
4742
id: 'rls-using',
4843
summary: 'RLS `using` read / pre-image predicate',
4944
dialect: 'cel', mode: 'compile', state: 'enforced', failPolicy: 'fail-closed',
50-
site: 'plugin-security/rls-compiler.ts → @objectstack/formula compileCelToFilter (legacy SQL bridged); AND-injected by security-plugin computeRlsFilter + service-analytics read-scope-sql',
45+
enforcement: 'plugin-security/rls-compiler.ts → @objectstack/formula compileCelToFilter (legacy SQL bridged); AND-injected by security-plugin computeRlsFilter + service-analytics read-scope-sql',
5146
covers: ['security/rls.zod.ts:using'],
5247
proof: 'packages/dogfood/test/rls-fixture.dogfood.test.ts',
5348
},
5449
{
5550
id: 'rls-check',
5651
summary: 'RLS `check` write post-image validation (ADR-0058 D4)',
5752
dialect: 'cel', mode: 'compile', state: 'enforced', failPolicy: 'fail-closed',
58-
site: 'plugin-security/security-plugin.ts step 3.6 → compileCelToFilter + @objectstack/formula matchesFilterCondition',
53+
enforcement: 'plugin-security/security-plugin.ts step 3.6 → compileCelToFilter + @objectstack/formula matchesFilterCondition',
5954
covers: ['security/rls.zod.ts:check'],
6055
proof: 'packages/plugins/plugin-security/src/security-plugin.test.ts',
6156
},
6257
{
6358
id: 'sharing-condition',
6459
summary: 'sharing-rule `condition` → criteria_json (ADR-0058 D3, closes #1887)',
6560
dialect: 'cel', mode: 'compile', state: 'enforced', failPolicy: 'fail-closed',
66-
site: 'plugin-sharing/bootstrap-declared-sharing-rules.ts celToFilter → compileCelToFilter; matched by sharing-rule-service findMatchingRecords',
61+
enforcement: 'plugin-sharing/bootstrap-declared-sharing-rules.ts celToFilter → compileCelToFilter; matched by sharing-rule-service findMatchingRecords',
6762
covers: ['security/sharing.zod.ts:condition'],
6863
proof: 'packages/plugins/plugin-sharing/src/sharing-rule.test.ts',
6964
},
@@ -73,21 +68,21 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
7368
id: 'cel-validation',
7469
summary: 'object validation predicate (condition / when)',
7570
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
76-
site: '@objectstack/formula celEngine (interpret) via the validation runner',
71+
enforcement: '@objectstack/formula celEngine (interpret) via the validation runner',
7772
covers: ['data/validation.zod.ts:condition', 'data/validation.zod.ts:when'],
7873
},
7974
{
8075
id: 'cel-hook',
8176
summary: 'hook gate condition',
8277
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
83-
site: '@objectstack/formula celEngine (interpret) via the hook runner',
78+
enforcement: '@objectstack/formula celEngine (interpret) via the hook runner',
8479
covers: ['data/hook.zod.ts:condition'],
8580
},
8681
{
8782
id: 'cel-formula',
8883
summary: 'computed / formula field + mapping / graphql / feature expressions',
8984
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
90-
site: '@objectstack/formula celEngine (interpret)',
85+
enforcement: '@objectstack/formula celEngine (interpret)',
9186
covers: [
9287
'data/field.zod.ts:expression',
9388
'shared/mapping.zod.ts:expression',
@@ -99,7 +94,7 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
9994
id: 'cel-field-rule',
10095
summary: 'field UI rules (requiredWhen / readonlyWhen / visibleWhen / conditionalRequired)',
10196
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
102-
site: '@objectstack/formula celEngine (interpret) — console (objectui) + server',
97+
enforcement: '@objectstack/formula celEngine (interpret) — console (objectui) + server',
10398
covers: [
10499
'data/field.zod.ts:requiredWhen',
105100
'data/field.zod.ts:readonlyWhen',
@@ -111,7 +106,7 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
111106
id: 'cel-ui',
112107
summary: 'UI visibility / routing / submit predicates',
113108
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
114-
site: 'console (objectui) SchemaRenderer + server celEngine (interpret)',
109+
enforcement: 'console (objectui) SchemaRenderer + server celEngine (interpret)',
115110
covers: [
116111
'data/object.zod.ts:visibleOn',
117112
'ui/action.zod.ts:visible',
@@ -127,7 +122,7 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
127122
id: 'cel-flow',
128123
summary: 'flow / sync / loader branching + filter predicates',
129124
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'throw',
130-
site: '@objectstack/formula celEngine (interpret) via the automation runtime',
125+
enforcement: '@objectstack/formula celEngine (interpret) via the automation runtime',
131126
covers: [
132127
'automation/flow.zod.ts:condition',
133128
'automation/sync.zod.ts:condition',
@@ -138,7 +133,7 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
138133
id: 'cel-advanced-policy',
139134
summary: 'advanced security / versioning policy conditions',
140135
dialect: 'cel', mode: 'interpret', state: 'experimental', failPolicy: 'fail-closed',
141-
site: '(no runtime consumer yet)',
136+
enforcement: '(no runtime consumer yet)',
142137
covers: [
143138
'kernel/plugin-security-advanced.zod.ts:condition',
144139
'kernel/plugin-versioning.zod.ts:condition',

0 commit comments

Comments
 (0)