on GitHub

Permissions

A schema file alone serves everything to everyone holding the token. The optional second file narrows that:

n6kd -f schema.sql --perms perms.yml

The rules are declarative, they live outside the application, and they are checked when the process starts rather than when the first client shows up.

The file

version: 1

source:
  catalog: memory
  schema: main

catalogs:
  demo:
    schema: main
    tables:
      customers: read
      orders: readwrite
      audit_log: read

  demo_readonly:
    schema: main
    tables:
      customers: read
      orders: read
      audit_log: read

source names where the real tables live. Each entry under catalogs is a view of that source that clients can attach by name, and each names its tables explicitly with read or readwrite. A table not listed is not served — absence is denial, so a new table added to the schema is invisible until someone grants it.

Two catalogs, one copy of the data

demo and demo_readonly above are the same physical rows with different rules. There is no replica and no second process. Attach both from one client and the difference shows up per statement:

ATTACH 'n6ks://…' AS demo (TYPE n6k);
ATTACH 'n6ks://…' AS demo_readonly (TYPE n6k);

UPDATE demo.orders          SET status = 'refunded' WHERE id = 42;  -- ok
UPDATE demo_readonly.orders SET status = 'refunded' WHERE id = 42;
Binder Error: Table 'orders' is read-only

That is one server handing an analyst a read-only endpoint and an application a writable one, over identical data.

Checked at boot

Every rule is verified against the real schema before the listener opens:

INFO  catalog "demo" serves 3 tables from memory.main
INFO    demo.main.audit_log  read
INFO    demo.main.customers  read
INFO    demo.main.orders     readwrite, primary key id (from schema)
INFO  perms check passed: 6 tables found in memory.main,
      1 writable table with a usable primary key

Three classes of mistake are caught here rather than in production:

  • a table named in perms.yml that does not exist in the schema
  • a readwrite table with no discoverable primary key — writes need one to address rows, so it is refused up front instead of failing on the first UPDATE
  • an unknown key in the YAML, which is a parse error rather than a silently ignored line. A typo in a permission name cannot quietly grant nothing.

A bad permissions file fails the boot. A deploy either serves the policy you wrote or does not serve at all.

The layers

Three separate things decide whether a statement runs, and it is worth keeping them apart:

LayerQuestionWhere it lives
AuthWho is connecting?A token, checked at the upgrade
PermissionsWhat may they touch?perms.yml, checked at boot
LockdownCan their SQL leave the database?On by default

They compose but do not substitute. A valid token still cannot write to a read table; a readwrite grant still cannot read /etc/passwd.

Limits

Permissions are per table, not per row or per column — there is no predicate that scopes a reader to their own tenant. When you need that, keep the narrowing in the source: define a view in schema.sql and grant the view, or scope the connection upstream the way the Postgres page does.

Grants are also static for the life of the process. Changing perms.yml means a restart, which is a fair trade for the boot-time verification but does mean permissions are a deploy, not an API call.