gen:catalog — the Page Card authority generator + check:catalog gate
The Page Card permission authority used to be
hand-declared in
permissions.ts (a six-string BACKEND_PERMISSIONS
list with an "interim, replace when gen:catalog lands" note). This
change builds that generator: pnpm gen:catalog
derives the authority from the backend's own artifacts
(openapi/permission-map.json + openapi/app-api.json)
into one committed file, and check:catalog fails CI on a
stale artifact or on a Page Card operation claiming a permission the
backend does not enforce. No hand-authored authority remains.
What changed
scripts/page-card/gen-catalog.ts reads the backend
permission-map.json + app-api.json via the
same sibling-read resolution as kubb.config (LI-6:
env override → sibling working tree → vendored mirror), and emits
src/lib/surface-registry/page-card/generated-authority.ts.
ENFORCED_OPERATION_PERMISSIONS = every distinct
needs string actually enforced on a backend op
(matters:read, matters:write,
clients:write, settings:write).
BACKEND_PERMISSIONS = the read+write namespace expanded
from those (each <ns>:write implies its browse /
resolver <ns>:read, RFD D-8) — reproducing the
old six strings, but generated.
findCatalogViolations now validates an
operation.needs against the enforced set
(∈ permission-map), while a card's read / resolver permission is
validated against the namespace. A card op claiming a real-but-not
-enforced string (e.g. clients:read) is now rejected.
The second input does real work: every app operation the
permission-map names must exist as an operationId in
app-api.json — catching a permission-map that has
drifted from the contract (190/190 today).
Where it lives
scripts/page-card/gen-catalog.ts— the generator:resolveBackendArtifact(LI-6 candidates),deriveAuthority(pure),render, and--check(freshness, exit 1 on stale).src/lib/surface-registry/page-card/generated-authority.ts— GENERATED, committed. The two permission sets. Do not hand-edit.src/lib/surface-registry/page-card/permissions.ts— now LOGIC ONLY:isBackendPermission(namespace) +isEnforcedOperationPermission(op), both over the generated data. The interim hand-declared mirror is gone.src/lib/surface-registry/page-card/catalog.ts— op needs vs namespace reads split; the six pending settings-catalogunsupportedreasons updated (authority now generated; their view card is the S-D3 follow-on).openapi/permission-map.json— new vendored mirror (likeapp-api.json);openapi/AGENTS.mddocuments the sync.package.json—gen:catalog+check:catalog(= freshness thenfindCatalogViolations)..github/workflows/ci.yml— sparse-checks outpermission-map.json, syncs the mirror, regenerates, and diffs the artifact for freshness.
Verify
pnpm gen:catalog # regenerate; output is committed pnpm check:catalog # freshness + op-needs subset, green pnpm lint pnpm typecheck pnpm test pnpm build
- Generated the artifact reproduces the old six
BACKEND_PERMISSIONSexactly — existing matters/clients/forums cards unchanged,findCatalogViolationsstill green. - Freshness
check:catalogfails if the committed artifact differs from a regenerate (the LI-6 gate, mirroringcheck:routes). - Subset an op whose
needs∉ permission-map (firm:manage, or even a realclients:read) is rejected. - Authority no hand-authored permission strings remain; the interim note is removed.
Coverage in generated-authority.test.ts (freshness,
namespace expansion, OpenAPI cross-check, the stricter op gate) plus
the existing catalog.test.ts /
listing-surface.test.ts proofs.
Non-goal (RFD-AGT-004): this builds the authority half
only. The component / dto_bindings catalog and the
settings-catalog ListingSurface view cards are the S-D3 schema-extension
follow-on — view config (columns, filters, labels) is not in the OpenAPI.
Files changed
9 file(s), +2175 / -60 lines
| Path | Role | + | - |
|---|---|---|---|
| .github/workflows/ci.yml | Workflow | +8 | -4 |
| openapi/AGENTS.md | Source | +14 | -9 |
| openapi/permission-map.json | Config | +1786 | -0 |
| package.json | Config | +2 | -1 |
| scripts/page-card/gen-catalog.ts | Script | +189 | -0 |
| src/lib/surface-registry/page-card/catalog.ts | Library | +37 | -19 |
| src/lib/surface-registry/page-card/generated-authority.test.ts | Library | +78 | -0 |
| src/lib/surface-registry/page-card/generated-authority.ts | Library | +30 | -0 |
| src/lib/surface-registry/page-card/permissions.ts | Library | +31 | -27 |