Skip to content

Commit 797917c

Browse files
Implement policy permission system with migrations, admin UI, and query auditing
1 parent f5f88de commit 797917c

69 files changed

Lines changed: 7808 additions & 974 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,29 @@ git config core.hooksPath .githooks
2727

2828
## CI/CD
2929
Push to `main` → GitHub Actions runs Rust tests + admin-ui tests → builds Docker image → deploys to Fly.io.
30+
31+
## Migrations (`migration/`)
32+
33+
### Rules (violations here cause hard-to-fix production incidents)
34+
35+
**One DDL statement per file.**
36+
Never combine `CREATE TABLE` + `CREATE INDEX`, multiple `ALTER TABLE`s, etc. in a single migration. SeaORM does NOT wrap SQLite migrations in a transaction — if a multi-step migration fails mid-way, the already-executed DDL commits immediately and cannot be rolled back. The migration is then not recorded in `seaql_migrations`, so on the next run it retries from the top and fails again on the already-applied step. A single-statement migration is either fully applied or not applied at all.
37+
38+
**Always use `.if_not_exists()`.**
39+
Every `Table::create()` must have `.if_not_exists()`. Every `Index::create()` must have `.if_not_exists()`. This makes migrations safe to retry if a previous run applied the DDL but crashed before recording in `seaql_migrations`.
40+
41+
**Always name indexes.**
42+
SQLite requires a name for every `CREATE INDEX`. A nameless `Index::create()` generates `CREATE INDEX ON ...` which is a syntax error in SQLite. Always call `.name("idx_<table>_<column>")` before `.table(...)`.
43+
44+
**Never rename or delete a migration file that has been applied.**
45+
SeaORM records the file name (without extension) as the migration version in `seaql_migrations`. Renaming or deleting a file whose version is already recorded causes a fatal startup error: "Migration file of version '...' is missing". If you need to change what a migration does, add a new migration — never touch old ones.
46+
47+
**Never touch the SQLite DB directly.**
48+
Never run `rm *.db`, `DROP TABLE`, `DELETE FROM`, `DROP COLUMN`, or modify `seaql_migrations` yourself. If the DB needs manual intervention (e.g. a partial migration left stale state), ask the user to do it. Describe exactly what SQL to run and why.
49+
50+
### Checklist before writing a new migration
51+
1. One DDL operation only
52+
2. `Table::create()``.if_not_exists()`
53+
3. `Index::create()``.if_not_exists()` + `.name("idx_...")`
54+
4. `ALTER TABLE ADD COLUMN` — no idempotency guard exists in SeaORM; document that users must not interrupt this migration
55+
5. Register the new file in `migration/src/lib.rs` in the correct order

.claude/commands/release.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Release
2-
3-
Prepare and tag a new release for this project. Both apps (proxy and admin-ui) share a single version.
1+
---
2+
description: Prepare and tag a new release for this project. Both apps (proxy and admin-ui) share a single version.
3+
---
44

55
## Steps
66

@@ -13,6 +13,7 @@ Prepare and tag a new release for this project. Both apps (proxy and admin-ui) s
1313
### 2. Draft changelog entries
1414

1515
Analyse the commits from step 1 and draft changelog entries grouped by:
16+
1617
- **Added** — new features
1718
- **Changed** — changes to existing behaviour
1819
- **Fixed** — bug fixes
@@ -43,7 +44,9 @@ Once the user confirms the version and changelog entries:
4344
### 5. Finish
4445

4546
Tell the user the release is ready and show the exact command to push:
47+
4648
```
4749
git push && git push origin vX.Y.Z
4850
```
51+
4952
Remind them that pushing the tag triggers CI to build and publish a Docker image tagged `vX.Y.Z`.

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- **Policy system** — configurable row filtering, column masking, and column access control via named policies assigned to datasources and users
12+
- `policy`, `policy_version`, `policy_obligation`, `policy_assignment`, `query_audit_log` database tables (migration 007)
13+
- `PolicyHook` replaces hardcoded `RLSHook`; supports `row_filter`, `column_mask`, and `column_access` obligation types
14+
- Template variables (`{user.tenant}`, `{user.username}`, `{user.id}`) with parse-then-substitute injection safety
15+
- Wildcard matching (`schema: "*"`, `table: "*"`) in obligation definitions
16+
- `access_mode` field on datasources: `"open"` (default) or `"policy_required"` (no policy = empty results)
17+
- Optimistic concurrency on policy updates via `version` field (409 Conflict on mismatch)
18+
- Immutable `policy_version` snapshots on every policy mutation for audit traceability
19+
- Deny policies short-circuit with error before plan execution
20+
- 60-second per-session policy cache with `invalidate_datasource` / `invalidate_user` hooks
21+
- **Policy CRUD API**`GET/POST /policies`, `GET/PUT/DELETE /policies/{id}` (admin-only)
22+
- **Policy assignment API**`GET/POST /datasources/{id}/policies`, `DELETE /datasources/{id}/policies/{assignment_id}`
23+
- **YAML import/export**`GET /policies/export`, `POST /policies/import` (with `?dry_run=true`)
24+
- **Query audit log** — async logging of every proxied query; `GET /audit/queries` with pagination and filtering by user, datasource, date range
25+
- **Admin UI — Policies** — list, create, and edit policies with an obligation builder (row_filter, column_mask, column_access); inline enable/disable toggle
26+
- **Admin UI — Policy Assignments**`PolicyAssignmentPanel` on datasource edit page; assign/remove policies with optional user scope and priority
27+
- **Admin UI — Query Audit** — paginated log with expandable rows showing original query, rewritten query, and applied policy snapshots
28+
- **Demo e-commerce schema**`scripts/demo_ecommerce/` with schema, seed script, and example `policies.yaml` covering all P0 stories
29+
- **Docs**`docs/permission-system.md` (user guide) and `docs/permission-security-tests.md` (security test plan)
30+
1031
## [0.3.0] - 2026-03-04
1132

1233
### Added

0 commit comments

Comments
 (0)