feat(ci): catalogue-drift gate for @RequirePrivilege/@RequireRole literals (ADR-0025)
CI / commits (pull_request) Successful in 2m18s
CI / check (pull_request) Successful in 2m57s
CI / scan (pull_request) Successful in 3m5s
CI / a11y (pull_request) Successful in 3m11s
Docs site / build (pull_request) Successful in 3m24s
CI / perf (pull_request) Successful in 6m57s
CI / commits (pull_request) Successful in 2m18s
CI / check (pull_request) Successful in 2m57s
CI / scan (pull_request) Successful in 3m5s
CI / a11y (pull_request) Successful in 3m11s
Docs site / build (pull_request) Successful in 3m24s
CI / perf (pull_request) Successful in 6m57s
Phase 3 of the ADR-0025 phasing per its §"More Information" §347:
a small CI gate that asserts every string literal passed to the
authorization decorators belongs to the closed catalogue declared
in libs/shared/auth/src/lib/authorization.types.ts.
The TypeScript signature on the decorators already enforces this
at compile time — the new gate is defence-in-depth against escape
hatches (`as Privilege` casts, hand-edits to the type union
without updating the runtime constant). It runs in <1s, so it
rides the existing `check` CI job rather than starting its own.
Implementation:
- scripts/check-catalogue-drift.mjs: TypeScript compiler API.
Parses authorization.types.ts to extract the catalogue arrays,
walks every .ts file under apps/ and libs/, finds each
CallExpression whose callee identifier matches @RequirePrivilege
or @RequireRole, validates every string-literal argument.
Non-literal args (variable indirection) are skipped on purpose —
the TypeScript signature catches them at compile time and the
gate's value-add is on literal misspellings. Generated gRPC
stubs under apps/portal-bff/src/grpc/gen are skipped (their
`roles[]` field is a wire-format unrelated to the catalogue).
Reports grouped by file with line:column for each violation,
exits 1 on drift.
- scripts/check-catalogue-drift.spec.mjs: 13 tests via node:test
(built-in, no Vitest dependency). Covers catalogue parsing,
file-level violation detection, workspace-wide aggregation,
skipped folders, gen-stub exclusion, line/column reporting.
- package.json: ci:catalogue-drift + ci:catalogue-drift:test
scripts.
- .gitea/workflows/ci.yml: the `check` job now runs the gate's
unit tests (first, to fail fast if the gate itself is broken)
then the gate against the live workspace.
Self-tested by injecting `RequirePrivilege('Portal.RogueDrift')`
and `RequireRole('rogue-role-x')` into an existing spec — both
caught with file:line:column and exit code 1.
Test plan:
- pnpm ci:catalogue-drift:test: 13/13 green.
- pnpm ci:catalogue-drift: clean (4 privileges, 24 roles).
- pnpm nx affected -t format:check lint test build: no project affected (script lives outside Nx).
This commit is contained in:
@@ -44,6 +44,15 @@ jobs:
|
||||
cache: 'pnpm'
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm ci:check
|
||||
# Catalogue-vs-code drift gate per ADR-0025 §"Confirmation".
|
||||
# Cheap (parses ~workspace .ts files via the TypeScript
|
||||
# compiler API, ~1s on a warm cache) so it rides the same
|
||||
# `check` job rather than spinning up a new one. The
|
||||
# script's own unit tests run first — if they fail the gate
|
||||
# itself is broken and the actual catalogue check would be
|
||||
# noise.
|
||||
- run: pnpm ci:catalogue-drift:test
|
||||
- run: pnpm ci:catalogue-drift
|
||||
|
||||
scan:
|
||||
runs-on: [self-hosted, on-prem]
|
||||
|
||||
Reference in New Issue
Block a user