Briefly checked out the repo and demo, looks neat!
Bookmarked to keep a tab on for future reference.
I noticed that SQLAlchemy (an ORM) is part of the stack, and that “Postgres support” is in the roadmap. For people coming from Supabase and the like which is Postgres-first, some upfront clarification around which database is already supported, would be helpful.
You're right. Since I use SQLAlchemy, the core is already database agnostic. Swapping the connection string to Postgres actually works for most features right now.
I listed it as "Roadmap" only because I haven't finished the full end-to-end test suite for Postgres yet, and I wanted the default "Quick Start" to be a zero dependency SQLite setup. I am also working on native read/write splitting so that the system can properly leverage scaling architectures (like Amazon Aurora's single-writer / multi-reader clusters) out of the box.
I'll make sure to clarify in the docs that it's currently "experimental" rather than missing. Appreciate the feedback!
Keybase's Merkle approach is elegant for their use case (efficient proofs without revealing the full chain), but I went simpler with a linear chain because:
1. Audit trails are inherently sequential - they're ordered by time and typically read/written in order. Merkle trees shine for unordered data where you need efficient inclusion proofs.
2. Verification simplicity - with a linear chain, integrity verification is just "walk the sequence and check that each entry's previous_hash matches the prior entry's checksum." O(n) and dead simple.
3. Storage efficiency - each entry stores two SHA-256 hashes as strings. No tree overhead.
4. Regulatory fit - for GxP/CFR Part 11 compliance, the requirement is tamper detection, not zero-knowledge proofs. A linear chain detects any modification equally well.
That said, if I ever need selective verification (prove entry #500 is valid without transmitting the full chain), I'd revisit Merkle. The implementation is in src/snackbase/infrastructure/persistence/repositories/audit_log_repository.py if you're curious.
On the custom DSL:
This was the biggest architectural decision in SnackBase. Here's the honest breakdown:
Why not just use existing options?
>Approach: Python eval()
>>Why I didn't choose it: Security nightmare - can't safely let users store arbitrary code in the database
>Approach: CEL (Google's Common Expression Language)
>>Why I didn't choose it: Battle-tested, but heavy dependency and less control over semantics
>Approach: JEXL/JSONLogic
>>Why I didn't choose it: Another runtime to learn, harder to integrate with my macro system
>Approach: Pure JSON rules
>>Why I didn't choose it: Becomes unreadable for complex expressions
What drove the decision:
1. Permissions are database-storable - rules live as strings in the permissions table, editable via API and admin UI. I needed something safe to parse and evaluate at runtime.
2. Sandboxed execution - the DSL only exposes specific operations (==, in, @has_role(), etc.). No imports, no file access, no arbitrary code. Even if someone compromises the admin UI, they can only express logic within the vocabulary I provide.
3. Syntax for non-programmers - "@has_role('admin') and @owns_record()" is more approachable than Python lambdas when you're building permissions in a web UI.
4. Macro integration - the @ prefix ties into my SQL macro system, letting users define reusable business logic like @is_department_head() that executes database queries.
The trade-off:
It's 700+ lines of lexer/parser/evaluator code I have to maintain. Every edge case (null handling, type coercion, short-circuit evaluation) needs explicit test coverage. Debugging a failed rule means returning syntax errors at position X rather than a stack trace.
If I were starting fresh today, I'd give CEL harder consideration. But since permissions are a core differentiator for SnackBase, having full control over the semantics has been worth it—especially for field-level access control and the wildcard collection system.
Implementation files if you're interested in it:
- src/snackbase/core/rules/lexer.py - tokenizer
- src/snackbase/core/rules/parser.py - recursive descent → AST
- src/snackbase/core/rules/evaluator.py - async evaluation with short-circuiting
Honestly, I picked AGPL mainly to prevent "cloud wrapping". I definitely don't intend for your business logic to get infected by it.
In my view, the custom hooks and schemas are "content/configuration" rather than derivative works, but I get that running in-process makes that legally murky.
To clear that up, I'll add a specific linking exception (like the Classpath Exception) to explicitly exempt user-defined hooks from the license. I want this to be safe for commercial teams to self-host, just not safe for cloud providers to resell.
Bookmarked to keep a tab on for future reference.
I noticed that SQLAlchemy (an ORM) is part of the stack, and that “Postgres support” is in the roadmap. For people coming from Supabase and the like which is Postgres-first, some upfront clarification around which database is already supported, would be helpful.
You're right. Since I use SQLAlchemy, the core is already database agnostic. Swapping the connection string to Postgres actually works for most features right now.
I listed it as "Roadmap" only because I haven't finished the full end-to-end test suite for Postgres yet, and I wanted the default "Quick Start" to be a zero dependency SQLite setup. I am also working on native read/write splitting so that the system can properly leverage scaling architectures (like Amazon Aurora's single-writer / multi-reader clusters) out of the box.
I'll make sure to clarify in the docs that it's currently "experimental" rather than missing. Appreciate the feedback!
> Compliance Core: Immutable audit logs with blockchain-style hashing (prev_hash) for integrity.
Had this in the back of my mind for a while now, too. In terms of prior art, Keybase had been doing something similar, but with Merkle trees.
> I’d love feedback on the DSL implementation
Could you tell in a bit more detail why you decided to go with your own DSL here? :)
Keybase's Merkle approach is elegant for their use case (efficient proofs without revealing the full chain), but I went simpler with a linear chain because:
1. Audit trails are inherently sequential - they're ordered by time and typically read/written in order. Merkle trees shine for unordered data where you need efficient inclusion proofs. 2. Verification simplicity - with a linear chain, integrity verification is just "walk the sequence and check that each entry's previous_hash matches the prior entry's checksum." O(n) and dead simple. 3. Storage efficiency - each entry stores two SHA-256 hashes as strings. No tree overhead. 4. Regulatory fit - for GxP/CFR Part 11 compliance, the requirement is tamper detection, not zero-knowledge proofs. A linear chain detects any modification equally well.
That said, if I ever need selective verification (prove entry #500 is valid without transmitting the full chain), I'd revisit Merkle. The implementation is in src/snackbase/infrastructure/persistence/repositories/audit_log_repository.py if you're curious.
On the custom DSL: This was the biggest architectural decision in SnackBase. Here's the honest breakdown:
Why not just use existing options? >Approach: Python eval() >>Why I didn't choose it: Security nightmare - can't safely let users store arbitrary code in the database
>Approach: CEL (Google's Common Expression Language) >>Why I didn't choose it: Battle-tested, but heavy dependency and less control over semantics
>Approach: JEXL/JSONLogic >>Why I didn't choose it: Another runtime to learn, harder to integrate with my macro system
>Approach: Pure JSON rules >>Why I didn't choose it: Becomes unreadable for complex expressions
What drove the decision:
1. Permissions are database-storable - rules live as strings in the permissions table, editable via API and admin UI. I needed something safe to parse and evaluate at runtime. 2. Sandboxed execution - the DSL only exposes specific operations (==, in, @has_role(), etc.). No imports, no file access, no arbitrary code. Even if someone compromises the admin UI, they can only express logic within the vocabulary I provide. 3. Syntax for non-programmers - "@has_role('admin') and @owns_record()" is more approachable than Python lambdas when you're building permissions in a web UI. 4. Macro integration - the @ prefix ties into my SQL macro system, letting users define reusable business logic like @is_department_head() that executes database queries.
The trade-off:
It's 700+ lines of lexer/parser/evaluator code I have to maintain. Every edge case (null handling, type coercion, short-circuit evaluation) needs explicit test coverage. Debugging a failed rule means returning syntax errors at position X rather than a stack trace.
If I were starting fresh today, I'd give CEL harder consideration. But since permissions are a core differentiator for SnackBase, having full control over the semantics has been worth it—especially for field-level access control and the wildcard collection system.
Implementation files if you're interested in it: - src/snackbase/core/rules/lexer.py - tokenizer - src/snackbase/core/rules/parser.py - recursive descent → AST - src/snackbase/core/rules/evaluator.py - async evaluation with short-circuiting
Happy to go deeper on any of this!
None of Django, Rails, Pocketbase or Supabase, which I think count as competitors, use AGPL.
Unless you can clarify that custom hooks and schemas are outside of the AGPL license, SnackBase may be a non-starter for commercial use.
Honestly, I picked AGPL mainly to prevent "cloud wrapping". I definitely don't intend for your business logic to get infected by it.
In my view, the custom hooks and schemas are "content/configuration" rather than derivative works, but I get that running in-process makes that legally murky.
To clear that up, I'll add a specific linking exception (like the Classpath Exception) to explicitly exempt user-defined hooks from the license. I want this to be safe for commercial teams to self-host, just not safe for cloud providers to resell.