scripts/ci/permission-parity.snapshot.json. Each entry identifies
the route, exported HTTP method, consumers, owning application, membership
helper, named permissions, permission denial expressions and candidate UI pages.
Source hashes make changes to consumers and analyzed authorization code visible.
Implementation status
The checker, tests and snapshot described here are pending PR #7019. They are not included in this documentation commit. Run the commands below only after that implementation lands.Planned commands (pending #7019)
After #7019 lands, use the repository resource queue: ttr resources run — node —test scripts/ci/permission-parity.test.js After adoption, the existing Root script tests (24) job in.github/workflows/turbo-unit-tests.yaml will discover this file through
scripts/run-script-tests.js. No separate workflow or deployment is required.
The check is subject to the existing workflow admission rules.
Regenerate the snapshot after an intentional change:
ttr resources run — node scripts/ci/permission-parity.js —write
Writing the snapshot does not clear authorization failures. The command
returns a nonzero status while unresolved paths or workspace permission checks
remain. Review the diff before committing; never regenerate solely to turn CI
green.
For complete diagnostics from the repository root:
node -e “const p = require(’./scripts/ci/permission-parity’); console.log(p.checkInventory(p.buildInventory(process.cwd())).join(‘\n’))“
Interpret failures
The gate rejects an unresolved consumed route, missing membership evidence, missing named permission evidence, missing UI policy evidence, differing API and UI denial expressions, or snapshot drift. An unrecognized authorization pattern is a failed proof, not evidence that the endpoint is exploitable. Membership evidence currently recognizes an awaitedgetPermissions result with
an unconditional return or throw when it is absent. Named permissions must appear
in a top-level conditional denial using withoutPermission. Boolean AND and OR
remain distinct. Shared route re-exports are followed; returned helpers and
helper results with a subsequent rejection branch can contribute evidence.
Unused imports, unused functions, ignored helper results and checks inside a
nested conditional cannot supply evidence for an otherwise unguarded method.
GET evidence cannot authorize POST.
Client discovery treats Dart endpoint definitions as possible consumers and
checks every exported method of each matching route. Satellite discovery follows
static imports, barrel dependencies and literal dynamic imports from source
request calls. Shared internal API endpoint builders are included. Tests,
generated registries, server route trees and routing configuration are excluded
from client discovery. Dynamic values are matched conservatively against route
segments; unresolved expressions need review.
Workspace checks apply to routes containing a workspace parameter. Public,
authentication and user-only routes remain in the inventory but do not receive
an invented workspace permission requirement.
Review and adoption
The initial inventory is a strict audit candidate. Do not merge it as a required gate until its unresolved paths, helper contracts and UI policies have been reviewed. It carries no baseline exemptions for missing checks. UI candidates are selected by the longest matching feature path across active app pages. This is a review aid: shared layouts, action-specific controls, ownership rules, board visibility, approval roles, wallet restrictions and parameterized helper policies can require explicit endpoint contracts. Verify those contracts against first-party callers and add behavioral denial and success tests before treating a static match as authorization acceptance. Do not add broad exemptions or remove a client simply to satisfy this checker. Mobile does not send every request to web.api_origins.dart selects owning
satellites for several domains, while web retains other contracts. The inventory
therefore includes matching satellite implementations and shared handler
packages alongside web’s current and retained legacy routes. Matching a path
does not prove that a deployment routes traffic to that owner.
This check does not execute handlers or prove control-flow dominance, returned
HTTP status, row scoping, RLS behavior, deployment identity or complete Dart and
TypeScript data flow. A green static check cannot replace runtime authorization
tests or security review. During adoption, distinguish missing enforcement from
unsupported static analysis and intentional contextual policies.