Skip to content

core ​

rowfence itself: the compiler and the rowfence command (Python 3.11 or newer, standard library only), and their tests. What the language and the command do is in the reference; this page is for working on them.

folderwhat
authzlib/the compiler (Python 3, standard library only); database.py is what the command does to a database, migrate.py the migrations
cli/rowfence, the command that compiles and applies, and pgwire.py, the small Postgres client it uses
authzc.pythe same compiler without a database: writes the SQL to a file (for development, tests and the editor)
example/the docs app used throughout: its schema, docs.authz and the policy's tests (docs.test.authz)
tests/every test suite; run_tests.sh runs them all, ci.sh on every Postgres version
bench/the benchmarks: benchmark.sql (the docs app, bigger) and the scale benchmark

From this folder:

cli/rowfence migrate example/docs.authz --tool sql       # the next migration, from the lock file
cli/rowfence --db "dbname=mydb" push example/docs.authz  # a development database, straight away
./run_tests.sh                                           # every test, from scratch (needs Postgres)
./ci.sh                                                  # ... on 16, 17 and 18, each in Docker

Tested ​

./run_tests.sh runs everything against fresh databases, in about 15 minutes (a few with --quick), as a non-superuser owner of the tables (started as a superuser, it makes one), on the stock Postgres image:

suitewhat it checks
tests/scenario.sqlend-to-end checks through RLS as each user, plus the tests in docs.authz
tests/multi_scenario.sqlthe second policy (tests/multi.authz on tests/multi_schema.sql): UUIDs, suspension, mixed-type trees, public and link access, caveats, custom roles, the masked view
tests/difftest.pyrandom data and 100 random changes per policy (moves, links, loops, nesting, shares, expiry and start times, id changes, TRUNCATE, multi-statement transactions); after each, can, list, explain, who, RLS reads, every policy expression and every column rule's condition (as the owner, and as the app role on the rows it may select) and the masked view are compared for every user (and one of them again with a scope) against a separate reference evaluator. Five policies (--gen: docs, alt, multi, composite, loop).
tests/unit_test.pychecks without a database, in about a second: the generated SQL against golden files (--update after an intended change), included files, what the command runs, its file reading, the version
tests/identity.shscopes, API keys, JWTs (bad signature, expiry, missing expiry, issuer, alg: none), view-as, group sync
tests/governance.shaudit, feed, requests, break-glass, reviews, invariants, decision log, --diff, lint
tests/masks.shmasks at apply time, later GRANTs reported by lint and taken back by applying, after renames, in --diff
tests/tools_test.pythe diagram, both clients (every statement prepared against the database; TypeScript type-checked with tsc --strict where it is installed), the editor grammar
tests/client_types.pythe generated TypeScript client under tsc --strict, no database: correct calls pass, wrong names don't (CI's javascript job)
tests/apply.shwhat applying does to a database: no extension, only if changed, both scenarios, tests, included files, who may apply, diff, pg_dump and restore, applying again after an upgrade, removing, and applying as a non-superuser owner
tests/cli.shrowfence: every command, included files from disk, errors and exit codes
tests/migrations.shrowfence migrate for each tool, migrations applied in order, out of order and on a new database, --check, push, a tree built beside while the app writes, dev writing the migration
tests/review.shrowfence review in a git repository with a review database: meaning, access, risk, tests, deploy, the comment, JSON and annotations, a refactor found equivalent, the database left as it was; rowfence fmt
tests/migrate_test.pyeach kind of policy change, both ways: the migration leaves exactly what applying the new policy whole leaves (functions and their privileges, views, triggers, policies, catalogs, inheritance rows), with the app writing in between
tests/concurrency.shtwo sessions racing: moves, loops, sharing during a delete
tests/adversarial.shthe app role, logged in as its own role, trying to read hidden rows (by id, COPY, leaky functions, prepared statements), reach rowfence's internals (and the catalog of every policy here against the rules, tests/catalog.sql), turn row-level security off, change or share what it may not, and learn about hidden objects
tests/docs_test.shthe SQL of docs/getting-started.md runs as written and does what the guide says
tests/fuzz_parser.pythousands of mutated policies: each is accepted or refused with a line number, never a crash
tests/moves.shmoves and links of folders with hundreds below them: the stored rows are shifted (rows inside the moved part left alone), loops fall back to recomputing, and the tables match a rebuild after each
tests/policy_errors.pypolicy mistakes (including included files passed as a map), each reported at compile or apply time, and awkward policies that must still work
tests/keep.shapplying keeps the inheritance tables whose definition didn't change, and rebuilds the ones that did
tests/principals.shservices that sign in as themselves: principal types, their keys and JWTs, service:* next to user:*, what the audit trail records
tests/sessions.shsigned sessions: the app role can't choose its user by setting authz.user_id, widen its scopes, or reuse a sign-in in another transaction or connection
tests/devx.shwhat developers use day to day: named tests with their own data, refusals that say why, authz.who_among, drafting a policy, rowfence dev, test, init, --as
tests/confidence_test.pyrowfence prove, test --coverage, snapshot, indexes, plans and bench, on the docs example
tests/lsp_test.pythe language server over its protocol, as an editor uses it (no database)
tests/mcp_test.pythe MCP server over its protocol, as a coding agent's client uses it: each tool in a project folder
tests/studio_test.pyrowfence why and Studio's API: read-only unless --write, only for the page that has the token, on localhost
tests/cookbook.shdocs/cookbook.md: its policy applies, its tests pass, and every line the page shows is in them
tests/races.shthe full run and --proofs: every pair of tree writes raced in two sessions at each isolation level; the inheritance tables match a rebuild after each
tests/stress.shthe full run and --proofs: 16 clients writing a folder tree at once at each isolation level; the tables match a rebuild afterwards
tests/genpolicy.pythe full run (12) and --soak (100, a new seed each night): random policies, each with its tables and data (write rules with conditions that read another table: a subquery, a function called by a quoted name, an operator), checked against the reference evaluator like the fixed ones