ADR 0001: Core Architecture for CrateStack v0
Status
ProposedDate
2026-04-26Context
CrateStack is intended to be a Rust-native, schema-first backend framework layer for building typed database-backed HTTP REST APIs, generated clients, declarative authorization policies, and custom business procedures. The primary developer experience should be:.cstack schema files. CrateStack should generate a typed ORM client, canonical REST CRUD routes, procedure interfaces, request/response types, generated client libraries, and policy enforcement code.
The project has several important constraints:
- Rust-first.
- SQLx-backed.
- PostgreSQL-first for v0.
- Axum-first for v0.
- HTTP REST only.
- No RPC transport in v0.
- No separate service-description requirement in v0.
- Authentication is delegated to the host framework or application.
- Authorization remains a core CrateStack responsibility.
- Procedures are essential and must be first-class.
- Procedures must always be schema-declared and generated as Rust traits.
- CRUD exposure must be schema-configurable per model and per operation.
- Field visibility and filterability must be controlled by separate schema directives.
- Generated HTTP routes are canonical APIs and are valid for both public and internal service-to-service use.
- JSON must not be assumed as the wire format.
- JSON and CBOR must both be first-class body codecs.
- COSE must be supportable as an optional envelope layer.
- Rust client generation is a first-class deliverable; Dart client generation follows later.
- Resolver-backed custom fields must be supportable from schema directives.
- v0 must avoid an
as_systemor superuser bypass API.
Decision
CrateStack v0 will use a macro-first, schema-first architecture centered around:.cstack schema at compile time and generate a Rust module named cratestack_schema containing:
- model structs
- input structs
- ORM delegates
- field references
- policy enforcement code
- procedure traits
- procedure call helpers
- Axum REST routes
- client-generation metadata
- custom-field resolver traits
- the generated
CrateStackruntime type
CoolContext representing the already-authenticated request identity. CrateStack will enforce authorization policies using this context.
CrateStack will support procedures as first-class schema declarations. Applications will implement generated Rust procedure traits. Procedure-level permissions will be checked before the application procedure implementation is called. Handwritten special endpoints are out of scope for the framework philosophy; non-CRUD operations should be declared as procedures.
CrateStack will enforce default-deny authorization semantics. No matching allow rule means the operation is denied.
CrateStack v0 will not provide as_system or any equivalent policy-bypass API.
CrateStack will not hard-code JSON into the REST layer. Instead, generated handlers will use a CoolCodec trait for body encoding and decoding. JSON and CBOR will both be first-class codec crates, while generated services decide which codecs are enabled.
COSE will be modeled separately from body encoding through a CoolEnvelope trait. The envelope layer wraps encoded bytes and can verify, decrypt, sign, encrypt, or MAC request/response bodies.
Generated success responses should default to raw typed bodies. When metadata is needed, schemas should model that explicitly through generated wrapper types instead of forcing a universal { data, meta } success envelope.
Architecture
Compile-Time Schema Inclusion
The primary integration point is:- Resolve the schema path relative to
CARGO_MANIFEST_DIR. - Read the schema file.
- Parse the schema.
- Perform semantic validation.
- Generate Rust code.
- Ensure schema changes trigger recompilation.
Generated Runtime Surface
The generated module will expose:ORM Backend
CrateStack v0 will use:sqlx::PgPoolsqlx::Postgressqlx::QueryBuilder<Postgres>- generated
sqlx::FromRowstructs
CoolContext at execution time:
REST Transport
CrateStack v0 will generate conventional REST CRUD endpoints:- procedure inputs may be complex
- CBOR request bodies are easier than query-string encoding
- COSE envelopes wrap bodies, not query strings
- procedure semantics are not guaranteed to be cache-safe
- v0 should avoid complex HTTP method inference
Procedures
Procedures are declared in the schema:Custom Fields
Schemas may declare resolver-backed custom fields with a field directive:@custom, CrateStack generates resolver trait methods that applications implement to derive the field value from the source object and request context.
Initial v0 scope:
@customis supported first ontypedeclarations- generated resolver traits and metadata are part of the compile-time output
- runtime response-field resolution is a staged follow-up slice
Client Generation
Generated HTTP routes are intended to be consumed through generated clients. Planned client roadmap:- Rust async client generation first
- Dart client generation later, optimized for Riverpod-based frontends
- high-level typed methods per CRUD operation and procedure
- a lower-level request-builder escape hatch for advanced use cases
Authorization Model
CrateStack owns authorization, not authentication. The host application or framework owns:- login
- sessions
- cookies
- JWT verification
- OAuth
- user lookup
No System Bypass in v0
CrateStack v0 will not expose:Codec Layer
CrateStack generated REST handlers will not assume JSON. Instead, CrateStack defines:Envelope Layer
COSE is not a codec. COSE wraps encoded bytes. CrateStack defines:Crate Layout
CrateStack v0 will use a multi-crate workspace:Consequences
Positive Consequences
- Excellent developer experience through a single
include_schema!macro. - Schema remains the source of truth.
- Generated ORM and REST routes stay consistent with the schema.
- Authorization is centralized and declarative.
- Policy literals, predicates, and procedure-policy evaluation now have a canonical shared home in
cratestack-policy. - Procedures provide a first-class place for business logic.
- Procedures remain policy-aware by default.
- Authentication stays framework-agnostic.
- The runtime can grow toward richer actor/session/tenant semantics without breaking existing
auth()-style schemas. - SQLx provides a mature async database execution layer.
- PostgreSQL-only v0 keeps scope manageable.
- Axum-only v0 keeps HTTP integration manageable.
- CBOR can be used without fighting JSON assumptions.
- COSE can be added without contaminating the ORM or policy layers.
- The codec/envelope split keeps transport concerns clean.
- Default-deny permissions reduce accidental exposure risk.
Negative Consequences
- Procedural macro-generated code may be harder to debug than generated files.
- Large schema files may increase compile times.
- Compile errors from generated code may be confusing.
- SQLx dynamic queries sacrifice some compile-time SQL checking.
- PostgreSQL-only v0 excludes MySQL and SQLite users.
- Axum-only v0 excludes Actix, Poem, and other framework users.
- POST-only procedures are simple but less semantically pure for read-like operations.
- No
as_systemAPI may make some administrative workflows harder in v0. - Compatibility between structured principals and the legacy
auth()projection adds runtime translation complexity. - Pluggable codecs add complexity compared to assuming JSON.
- COSE support introduces security-sensitive implementation responsibilities.
Neutral Consequences
- JSON can still exist, but only as an optional codec.
- Additional documentation exports can be added later, but are not a v0 requirement.
- RPC could theoretically be added later, but is explicitly not part of the current product direction.
- Additional frameworks can be supported later through separate integration crates.
Alternatives Considered
Alternative 1: Generate an External Crate Instead of Using a Macro
A CLI could generate acratestack-generated crate that the application imports.
Example:
.cstack files and include them directly with:
Alternative 2: Build a Runtime Schema Interpreter
CrateStack could parse.cstack at runtime and dynamically serve APIs.
Rejected because:
- weaker type safety
- worse Rust developer experience
- less IDE support
- more runtime failure modes
- harder to expose typed ORM APIs
Alternative 3: Use Diesel Instead of SQLx
Diesel offers strong compile-time query guarantees. Rejected for v0 because:- CrateStack needs dynamic query generation for filters and policy injection
- SQLx has straightforward async support
- SQLx integrates naturally with Axum/Tokio services
- SQLx
QueryBuilderis well suited to generated dynamic SQL
Alternative 4: Assume JSON and Add CBOR Later
CrateStack could start with JSON as the default HTTP format and later add CBOR. Rejected because JSON is explicitly unacceptable for some target projects. If JSON is baked into handlers early, removing that assumption later would be expensive. The correct abstraction is a codec trait from v0.Alternative 5: Treat COSE as a Codec
CrateStack could exposeapplication/cose as just another codec.
Rejected because COSE is an envelope over bytes, while CBOR is a serialization format. Treating COSE as a codec would mix serialization, signing, verification, encryption, and application data decoding into one layer.
The chosen model keeps:
Alternative 6: Let Procedures Bypass Policies
Procedures could be privileged by default and bypass model-level authorization. Rejected for v0 because this creates a high risk of accidental data exposure. Procedures should compose the same policy-protected ORM APIs unless and until an explicit privileged operation model is designed.Alternative 7: Add as_system in v0
CrateStack could provide:
Alternative 8: Support RPC Transport
Procedures could be exposed through RPC-style endpoints. Rejected because CrateStack v0 is explicitly REST-only. Procedures are exposed over REST as POST endpoints, not as an RPC protocol.Decision Drivers
The decision optimizes for:- Rust-native developer experience.
- Schema-first design.
- Type safety.
- REST-only APIs.
- First-class procedures.
- Strong authorization defaults.
- External authentication.
- CBOR/COSE readiness.
- Manageable v0 scope.
- Avoiding JSON lock-in.
Implementation Notes
- The macro should generate
include_str!references or equivalent compile-time dependencies so schema changes trigger recompilation. - Generated SQL must use bind parameters, never string interpolation of untrusted values.
- Policy SQL generation should be snapshot-tested.
- Procedure permissions should be checked before invoking application code.
- Errors should be encoded with the configured codec.
- If an envelope is configured, error responses should also be sealed unless request verification fails before a response context can be established.
- The CLI should provide
cratestack checkandcratestack print-irto make macro debugging easier. - JSON support should live outside core.
- CBOR should be the first official codec implementation.
- COSE support should be optional and isolated in
cratestack-cose.
Follow-Up ADRs
Potential future ADRs:- ADR 0002:
.cstackSchema Grammar and Type System. - ADR 0003: Permission Expression Semantics and SQL Compilation.
- ADR 0004: Procedure Routing and Naming.
- ADR 0005: CBOR Codec Implementation.
- ADR 0006: COSE Envelope Modes and Key Management.
- ADR 0007: Migration Strategy.
- ADR 0008: Relation Loading Strategy.
- ADR 0009: Privileged Operations and Possible
as_systemAlternative. - ADR 0010: Multi-Framework Support Beyond Axum.
Final Decision Statement
CrateStack v0 will be a macro-first, schema-first Rust framework layer that generates a SQLx-backed ORM, policy-protected REST CRUD routes, and REST procedure endpoints from.cstack files. It will delegate authentication to the host application, enforce default-deny permissions, avoid system-level policy bypasses, and abstract HTTP body handling through codec and envelope traits so CBOR and COSE can be first-class without sacrificing general developer experience.