Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/conformance-ledger-helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@objectstack/verify": minor
---

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

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

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

describe('ADR-0056 D10 — authorization conformance matrix', () => {
it('has no duplicate primitive ids', () => {
const ids = AUTHZ_CONFORMANCE.map((p) => p.id);
expect(new Set(ids).size).toBe(ids.length);
});

it('every primitive is in exactly one honest state', () => {
for (const p of AUTHZ_CONFORMANCE) {
expect(VALID.has(p.state), `${p.id} has invalid state '${p.state}'`).toBe(true);
}
});

it('every ENFORCED primitive declares an enforcement site (no silent claims)', () => {
const missing = AUTHZ_CONFORMANCE.filter((p) => p.state === 'enforced' && !p.enforcement).map((p) => p.id);
expect(missing, `enforced primitives missing an enforcement site: ${missing.join(', ')}`).toEqual([]);
});

it('every experimental/removed primitive carries a note (honest rationale)', () => {
const missing = AUTHZ_CONFORMANCE.filter((p) => p.state !== 'enforced' && !p.note).map((p) => p.id);
expect(missing, `non-enforced primitives missing a note: ${missing.join(', ')}`).toEqual([]);
});

it('every referenced dogfood proof FILE EXISTS (the ratchet)', () => {
const broken: string[] = [];
for (const p of AUTHZ_CONFORMANCE as AuthzPrimitive[]) {
if (p.proof && !existsSync(join(HERE, p.proof))) broken.push(`${p.id} → ${p.proof}`);
}
expect(broken, `conformance proofs missing on disk: ${broken.join(', ')}`).toEqual([]);
});

it('the high-risk owner/derived OWD primitives each carry an end-to-end proof', () => {
const highRisk = ['owd-private', 'owd-public-read', 'controlled-by-parent', 'anonymous-deny', 'default-profile'];
for (const id of highRisk) {
const p = AUTHZ_CONFORMANCE.find((x) => x.id === id);
expect(p, `missing matrix entry: ${id}`).toBeTruthy();
expect(p!.proof, `${id} must carry a dogfood proof`).toBeTruthy();
}
it('is a sound conformance ledger (ADR-0060 checkLedger)', () => {
const problems = checkLedger(AUTHZ_CONFORMANCE, {
proofRoot: HERE, // proofs are dogfood test files alongside this one
highRisk: ['owd-private', 'owd-public-read', 'controlled-by-parent', 'anonymous-deny', 'default-profile'],
});
expect(problems, problems.join('\n')).toEqual([]);
});
});
54 changes: 54 additions & 0 deletions packages/dogfood/test/conformance-helper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
//
// ADR-0060 P1 — unit coverage for the reusable `checkLedger` helper. The two
// real ledgers (authz, expression) exercise it end-to-end; this pins each
// invariant in isolation.

import { describe, expect, it } from 'vitest';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { checkLedger, type ConformanceRow } from '@objectstack/verify';

const HERE = dirname(fileURLToPath(import.meta.url));
const ok = (extra: Partial<ConformanceRow> = {}): ConformanceRow =>
({ id: 'a', summary: 's', state: 'enforced', enforcement: 'site', ...extra });

describe('checkLedger (ADR-0060)', () => {
it('a sound ledger yields no problems', () => {
expect(checkLedger([ok()], { proofRoot: HERE })).toEqual([]);
});
it('flags duplicate ids', () => {
expect(checkLedger([ok(), ok()], { proofRoot: HERE }).some((x) => x.includes('duplicate id'))).toBe(true);
});
it('flags invalid state', () => {
expect(checkLedger([{ id: 'a', summary: 's', state: 'bogus' as never }], { proofRoot: HERE }).some((x) => x.includes('invalid state'))).toBe(true);
});
it('flags enforced-without-enforcement', () => {
expect(checkLedger([{ id: 'a', summary: 's', state: 'enforced' }], { proofRoot: HERE }).some((x) => x.includes('names no enforcement'))).toBe(true);
});
it('flags experimental-without-note', () => {
expect(checkLedger([{ id: 'a', summary: 's', state: 'experimental' }], { proofRoot: HERE }).some((x) => x.includes('carries no note'))).toBe(true);
});
it('flags a missing proof file; accepts an existing one', () => {
expect(checkLedger([ok({ proof: 'does/not/exist.ts' })], { proofRoot: HERE }).some((x) => x.includes('proof missing on disk'))).toBe(true);
expect(checkLedger([ok({ proof: 'conformance-helper.test.ts' })], { proofRoot: HERE })).toEqual([]);
});
it('high-risk must carry a proof', () => {
expect(checkLedger([ok()], { proofRoot: HERE, highRisk: ['a'] }).some((x) => x.includes('must carry a proof'))).toBe(true);
});
it('proofRequiredForEnforced flags enforced-without-proof', () => {
expect(checkLedger([ok()], { proofRoot: HERE, proofRequiredForEnforced: true }).some((x) => x.includes('carries no proof'))).toBe(true);
});
it('flags a surface classified by two rows', () => {
expect(checkLedger([ok({ id: 'a', covers: ['x'] }), ok({ id: 'b', covers: ['x'] })], { proofRoot: HERE }).some((x) => x.includes('more than one row'))).toBe(true);
});
it('ratchet: unclassified discovered surface', () => {
expect(checkLedger([ok({ covers: ['x'] })], { proofRoot: HERE, discover: () => ['x', 'y'] }).some((x) => x.includes('UNCLASSIFIED surface') && x.includes('y'))).toBe(true);
});
it('ratchet: stale covers', () => {
expect(checkLedger([ok({ covers: ['x', 'z'] })], { proofRoot: HERE, discover: () => ['x'] }).some((x) => x.includes('STALE covers') && x.includes('z'))).toBe(true);
});
it('ratchet: fully covered yields no problems', () => {
expect(checkLedger([ok({ covers: ['x', 'y'] })], { proofRoot: HERE, discover: () => ['x', 'y'] })).toEqual([]);
});
});
35 changes: 15 additions & 20 deletions packages/dogfood/test/expression-conformance.ledger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import type { ConformanceRow } from '@objectstack/verify';
//
// ADR-0058 D7 — Expression Surface Conformance ledger.
//
Expand All @@ -24,21 +26,14 @@ export type ExprState = 'enforced' | 'experimental' | 'removed';
/** ADR-0058 D5 fail-policy tiers. */
export type FailPolicy = 'compile-error' | 'fail-closed' | 'fail-soft-log' | 'throw';

export interface ExprSurface {
id: string;
summary: string;
export interface ExprSurface extends ConformanceRow {
dialect: ExprDialect;
mode: ExprMode;
state: ExprState;
failPolicy: FailPolicy;
/** Runtime evaluator / compiler site. */
site: string;
/** Runtime evaluator / compiler site (ConformanceRow.enforcement, required here). */
enforcement: string;
/** `file:field` surfaces (relative to packages/spec/src) this row classifies — the ratchet keys. */
covers: string[];
/** Proof path (repo-root-relative). Required for ENFORCED COMPILE (security) rows. */
proof?: string;
/** Rationale for experimental/removed, or a roadmap pointer. */
note?: string;
}

export const EXPRESSION_SURFACE: ExprSurface[] = [
Expand All @@ -47,23 +42,23 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
id: 'rls-using',
summary: 'RLS `using` read / pre-image predicate',
dialect: 'cel', mode: 'compile', state: 'enforced', failPolicy: 'fail-closed',
site: 'plugin-security/rls-compiler.ts → @objectstack/formula compileCelToFilter (legacy SQL bridged); AND-injected by security-plugin computeRlsFilter + service-analytics read-scope-sql',
enforcement: 'plugin-security/rls-compiler.ts → @objectstack/formula compileCelToFilter (legacy SQL bridged); AND-injected by security-plugin computeRlsFilter + service-analytics read-scope-sql',
covers: ['security/rls.zod.ts:using'],
proof: 'packages/dogfood/test/rls-fixture.dogfood.test.ts',
},
{
id: 'rls-check',
summary: 'RLS `check` write post-image validation (ADR-0058 D4)',
dialect: 'cel', mode: 'compile', state: 'enforced', failPolicy: 'fail-closed',
site: 'plugin-security/security-plugin.ts step 3.6 → compileCelToFilter + @objectstack/formula matchesFilterCondition',
enforcement: 'plugin-security/security-plugin.ts step 3.6 → compileCelToFilter + @objectstack/formula matchesFilterCondition',
covers: ['security/rls.zod.ts:check'],
proof: 'packages/plugins/plugin-security/src/security-plugin.test.ts',
},
{
id: 'sharing-condition',
summary: 'sharing-rule `condition` → criteria_json (ADR-0058 D3, closes #1887)',
dialect: 'cel', mode: 'compile', state: 'enforced', failPolicy: 'fail-closed',
site: 'plugin-sharing/bootstrap-declared-sharing-rules.ts celToFilter → compileCelToFilter; matched by sharing-rule-service findMatchingRecords',
enforcement: 'plugin-sharing/bootstrap-declared-sharing-rules.ts celToFilter → compileCelToFilter; matched by sharing-rule-service findMatchingRecords',
covers: ['security/sharing.zod.ts:condition'],
proof: 'packages/plugins/plugin-sharing/src/sharing-rule.test.ts',
},
Expand All @@ -73,21 +68,21 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
id: 'cel-validation',
summary: 'object validation predicate (condition / when)',
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
site: '@objectstack/formula celEngine (interpret) via the validation runner',
enforcement: '@objectstack/formula celEngine (interpret) via the validation runner',
covers: ['data/validation.zod.ts:condition', 'data/validation.zod.ts:when'],
},
{
id: 'cel-hook',
summary: 'hook gate condition',
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
site: '@objectstack/formula celEngine (interpret) via the hook runner',
enforcement: '@objectstack/formula celEngine (interpret) via the hook runner',
covers: ['data/hook.zod.ts:condition'],
},
{
id: 'cel-formula',
summary: 'computed / formula field + mapping / graphql / feature expressions',
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
site: '@objectstack/formula celEngine (interpret)',
enforcement: '@objectstack/formula celEngine (interpret)',
covers: [
'data/field.zod.ts:expression',
'shared/mapping.zod.ts:expression',
Expand All @@ -99,7 +94,7 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
id: 'cel-field-rule',
summary: 'field UI rules (requiredWhen / readonlyWhen / visibleWhen / conditionalRequired)',
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
site: '@objectstack/formula celEngine (interpret) — console (objectui) + server',
enforcement: '@objectstack/formula celEngine (interpret) — console (objectui) + server',
covers: [
'data/field.zod.ts:requiredWhen',
'data/field.zod.ts:readonlyWhen',
Expand All @@ -111,7 +106,7 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
id: 'cel-ui',
summary: 'UI visibility / routing / submit predicates',
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
site: 'console (objectui) SchemaRenderer + server celEngine (interpret)',
enforcement: 'console (objectui) SchemaRenderer + server celEngine (interpret)',
covers: [
'data/object.zod.ts:visibleOn',
'ui/action.zod.ts:visible',
Expand All @@ -127,7 +122,7 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
id: 'cel-flow',
summary: 'flow / sync / loader branching + filter predicates',
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'throw',
site: '@objectstack/formula celEngine (interpret) via the automation runtime',
enforcement: '@objectstack/formula celEngine (interpret) via the automation runtime',
covers: [
'automation/flow.zod.ts:condition',
'automation/sync.zod.ts:condition',
Expand All @@ -138,7 +133,7 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
id: 'cel-advanced-policy',
summary: 'advanced security / versioning policy conditions',
dialect: 'cel', mode: 'interpret', state: 'experimental', failPolicy: 'fail-closed',
site: '(no runtime consumer yet)',
enforcement: '(no runtime consumer yet)',
covers: [
'kernel/plugin-security-advanced.zod.ts:condition',
'kernel/plugin-versioning.zod.ts:condition',
Expand Down
Loading
Loading