CrateStack v0 PRD

0.3.0 macro-split update

This PRD was authored when CrateStack exposed a single include_schema! macro. As of 0.3.0, the developer surface is three role-specific macros, replacing the single one:
  • include_server_schema!("schema.cstack", db = Postgres) — server / sqlx
  • include_embedded_schema!("schema.cstack") — embedded / rusqlite (native + browser via OPFS)
  • include_client_schema!("schema.cstack") — HTTP client (renamed from include_client_macro!)
The PRD’s intent (one schema, typed delegates, generated routes, policy enforcement) is unchanged. References to include_schema! below describe the developer surface in server contexts unless otherwise noted; they map to include_server_schema!("…", db = Postgres) in 0.3.0+.

1. Product Summary

CrateStack is 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. Developers define their data model, authorization rules, field exposure directives, custom fields, and procedures in .cstack schema files, then include them in Rust using one of the role-specific macros — for example, in a server crate:
The macro generates a typed ORM client, canonical REST CRUD routes, procedure interfaces, request/response types, generated client metadata, and policy enforcement code. CrateStack uses sqlx behind the scenes for database access and supports pluggable transport composition so applications can use JSON, CBOR, sequence-aware transports such as application/cbor-seq, and future COSE envelopes without sacrificing developer experience. CrateStack v0 is intentionally focused:
  • Rust-first
  • PostgreSQL-first
  • Axum-first
  • SQLx-backed
  • HTTP REST only
  • Schema-defined ORM models
  • Schema-defined authorization policies
  • Schema-defined CRUD exposure controls
  • Schema-defined field visibility and filterability controls
  • Schema-defined custom fields
  • Schema-defined procedures
  • Framework-delegated authentication
  • Pluggable codecs
  • Pluggable framing
  • Optional COSE envelope support
  • Generated Rust clients as a first-class output
CrateStack v0 does not attempt to provide RPC, GraphQL, a full plugin marketplace, or built-in authentication.

2. Problem Statement

Rust backend developers often need to repeatedly build the same foundation across projects:
  • database models
  • typed query APIs
  • REST CRUD endpoints
  • request and response structs
  • authorization checks
  • business procedures
  • stable internal client libraries
  • pagination and filtering
  • serialization format handling
  • framework integration
Existing Rust ORMs and web frameworks are powerful, but they usually require significant manual wiring between schema, database, authorization, and HTTP transport. Many systems also assume JSON as the default wire format, which is unacceptable for projects that require CBOR and COSE. CrateStack solves this by making the schema the source of truth and generating the surrounding Rust API, REST layer, and client contracts while keeping authentication, transport encoding, and cryptographic wrapping configurable.

2.1 Representative Use Cases

  1. Internal catalog or CMS service Teams can define models, policies, and procedures once, then get generated CRUD routes and typed Rust delegates without hand-writing every handler and query parser.
  2. CBOR-first service boundary Platforms that do not want to normalize around JSON can keep generated HTTP APIs and clients aligned with a CBOR-first contract from day one.
  3. Policy-heavy content or workflow APIs Applications can encode data visibility rules such as published || authorId == auth().id in schema policy declarations instead of duplicating checks across handlers and queries.
  4. Shared server and client contract generation Teams can use a .cstack schema to drive generated routes, Rust delegate usage, and experimental generated client outputs from the same source of truth.

3. Goals

3.1 Product Goals

  1. Let developers define data models and policies in .cstack files.
  2. Generate a typed Rust ORM client from the schema.
  3. Generate canonical HTTP REST CRUD routes for schema models.
  4. Support schema-configurable CRUD exposure per model and per operation.
  5. Support separate schema directives for field visibility and filterability.
  6. Support custom schema-defined procedures for business logic.
  7. Enforce model-level and procedure-level permissions.
  8. Delegate authentication to the host framework or application.
  9. Avoid forcing JSON as the wire format.
  10. Support JSON and CBOR as first-class codecs.
  11. Support framing-aware transports, including application/cbor-seq, where route semantics justify them.
  12. Support COSE as a transport envelope layer.
  13. Use SQLx behind the scenes for database execution.
  14. Generate Rust clients for the canonical HTTP APIs.
  15. Provide a clean include_schema! developer experience.
  16. Support resolver-backed custom fields.

3.2 Developer Experience Goals

The ideal developer flow:
The developer defines a .cstack file, implements generated procedure traits and custom-field resolver traits, provides authentication context through the framework, and gets REST endpoints plus a typed ORM and generated client contracts. Canonical transport design and HTTP negotiation rules are documented in:
  1. ../architecture/transport-architecture.md
  2. ../architecture/http-transport-contract.md

4. Non-Goals for v0

CrateStack v0 explicitly does not include:
  1. RPC transport.
  2. GraphQL.
  3. schema-first transport and client generation remain the core outputs.
  4. Production-stable non-Rust client generation in the initial slice.
  5. Built-in authentication.
  6. Session management.
  7. OAuth, JWT, password hashing, or login flows.
  8. Multi-database support beyond PostgreSQL.
  9. A complete migration engine.
  10. Polymorphic models.
  11. Deep nested writes.
  12. Advanced include trees.
  13. A broad plugin marketplace.
  14. as_system or superuser bypass APIs.
  15. Automatic policy bypass inside procedures.
  16. Prisma compatibility as a formal goal.
  17. ZenStack compatibility as a formal goal.

5. Target Users

5.1 Primary Users

Rust backend developers who want:
  • typed database access
  • generated REST endpoints
  • schema-driven authorization
  • framework-level authentication integration
  • CBOR/COSE-capable HTTP APIs
  • low-boilerplate CRUD and business procedure support

5.2 Secondary Users

Teams building:
  • internal services
  • secure APIs
  • embedded or constrained-environment backends
  • public REST APIs requiring non-JSON formats
  • policy-heavy data services

6. Core Concepts

6.1 .cstack Schema

The .cstack schema is the source of truth for:
  • datasource configuration
  • auth context shape
  • database models
  • fields
  • relations
  • constraints
  • model-level permissions
  • CRUD exposure controls
  • field visibility rules
  • field filterability rules
  • model event emission directives
  • custom-field resolution declarations
  • custom input/output types
  • procedures
  • procedure-level permissions

6.2 ORM Client

CrateStack generates a typed Rust ORM client with model delegates:
Builder-style relation DSL example:

6.3 REST CRUD

CrateStack generates conventional REST endpoints for models:
Per-model exposure is schema-configurable. A model may expose all CRUD operations, only a protected subset, public read-only routes, or no generated CRUD at all. Raw HTTP examples for the current slice:

6.4 Procedures

Procedures are schema-defined custom business operations implemented by the application. Example:
Generated Rust trait:
REST endpoint:
All non-CRUD business behavior should be represented as schema-declared procedures. Procedures are not an escape hatch around the schema; they are part of the schema contract.

6.5 Permissions

Permissions are schema-defined authorization rules. Model example:
Procedure example:
Default rule:

6.6 Authentication Delegation

CrateStack does not authenticate users. The host application or framework is responsible for:
  • login
  • sessions
  • JWT verification
  • cookies

6.7 Custom Fields

Schemas may declare resolver-backed custom fields:
The framework generates a resolver trait for custom fields so that applications can derive values such as thumbnailUrl from internal infrastructure like imgproxy without making that wiring part of every handler.

6.8 Generated Clients

Generated HTTP routes are the normal API surface of a CrateStack service. The framework should generate:
  • a Rust async client first
  • a Dart client later, designed for Riverpod-based frontends
Generated Rust clients should expose both high-level typed operations and a lower-level request-builder escape hatch.
  • OAuth
  • user lookup
CrateStack only consumes a CoolContext that represents the already-authenticated request identity.

6.7 Codec

A codec converts typed Rust values to and from HTTP request/response bodies. Examples:
  • CBOR
  • JSON, optional only
  • MessagePack, possible later

6.8 Envelope

An envelope wraps already-encoded bytes for signing, verification, encryption, or MAC operations. Examples:
  • no envelope
  • COSE_Sign1
  • COSE_Encrypt0
  • COSE_Mac0
CBOR and COSE are intentionally separate:

7. Example Schema


8. Functional Requirements

8.1 Schema Parsing

CrateStack must parse .cstack files containing:
  • datasource blocks
  • auth blocks
  • model blocks
  • type blocks
  • procedure declarations
  • mutation procedure declarations
  • scalar fields
  • optional fields
  • list fields
  • model attributes
  • field attributes
  • permission expressions

Required scalar types for v0

Json is a database/value type only. It must not imply JSON as the HTTP wire format.

Required field attributes for v0

Required model attributes for v0

Current @@paged boundary:
  • @@paged is a bare model-level opt-in only
  • it only changes the generated top-level list route for that model
  • generated GET /{models} responses become Page<Model> instead of Model[]
  • generated Rust and Dart model list(...) and projection-driven listView(...) clients follow the same paged contract
  • paging is still offset-based through limit and offset
  • nested relation collections and detail routes are unaffected

Required procedure attributes for v0

Built-in container return type currently supported in procedure returns:
Current Page<T> boundary:
  • only supported on procedure return types
  • T must be a declared model or type
  • generated wire shape is items, totalCount, and pageInfo
Explicit non-goals for the current slice:
  • no user-defined generic schema types such as type Foo<T>
  • no scalar page items such as Page<Int>
  • no nested page containers such as Page<Page<Post>>
  • no optional or list-wrapped page containers such as Page<Post>? or Page<Post>[]
  • no Page<T> support on model fields, type fields, auth fields, or procedure arguments
  • no alternative built-in paged container variations beyond the canonical Page<T> envelope above

8.2 Semantic Analysis

CrateStack must validate:
  1. duplicate model names
  2. duplicate type names
  3. duplicate field names
  4. unknown field types
  5. invalid scalar types
  6. invalid relation references
  7. missing primary keys
  8. invalid policy actions
  9. invalid auth field references
  10. invalid model field references in policies
  11. invalid procedure input types
  12. invalid procedure return types
  13. duplicate procedure names
  14. unsupported database provider values
Semantic errors should produce compiler-style diagnostics that identify the file location when possible.

8.3 Macro Integration

The primary integration point is:
The macro must:
  1. read the schema file relative to CARGO_MANIFEST_DIR
  2. parse the schema
  3. analyze the schema
  4. generate Rust modules and types
  5. generate ORM delegates
  6. generate policy code
  7. generate procedure traits
  8. generate REST routes
  9. trigger recompilation when the schema changes
Generated module name for v0:
Generated surface:

8.4 ORM Requirements

8.4.1 Generated Delegates

For each model, generate a delegate:
Required methods:

8.4.2 Query Features for v0

Required:
  • equality filters
  • boolean filters
  • simple string filters
  • ordering
  • limit
  • offset
  • primary-key lookup
Optional but desirable:
  • contains
  • starts_with
  • ends_with
  • in
  • not_in

8.4.3 SQL Backend

CrateStack v0 must use SQLx behind the scenes. Recommended implementation:
  • sqlx::PgPool
  • sqlx::QueryBuilder<Postgres>
  • generated sqlx::FromRow structs
  • dynamic SQL for filters and policies
Compile-time SQLx macros are not required for generated dynamic queries.

8.4.4 Policy Injection

Read, update, and delete policies must be injected into SQL where possible. Example policy:
Should produce SQL behavior equivalent to:
Create policies may be checked before insert using the decoded input and auth context.

8.5 Permission Requirements

8.5.1 Supported Actions

Model permissions support:
Procedure permissions support:
The schema syntax may omit the explicit action for procedures:

8.5.2 Default Deny

If no applicable allow rule exists, the operation must be denied.

8.5.3 Multiple Allow Rules

Multiple allow rules for the same action are OR-combined. Example:
Equivalent:

8.5.4 Auth Context

Policies may reference:
auth() == null means unauthenticated. auth() != null means authenticated.

8.5.5 Procedure Permissions

Procedure-level permissions are checked before calling the application-provided procedure implementation. Example:
Generated handler behavior:

8.5.6 No System Bypass in v0

CrateStack v0 must not provide:
Procedures use the same policy-protected ORM methods as all other code.

8.6 Procedure Requirements

8.6.1 Schema Declaration

CrateStack supports:

8.6.2 Generated Types

CrateStack generates input and output types for procedure arguments and return values. Input types derive:
Model return types derive:

8.6.3 Generated Procedure Trait

CrateStack generates a trait that the application must implement. Example:

8.6.4 Procedure Registration

Applications register procedure implementations through the builder:

8.6.5 Procedure ORM API

CrateStack may also expose procedures through a typed local API:

8.6.6 Procedure REST API

Procedures are exposed as HTTP POST endpoints:
All procedures use POST in v0, including read-like procedures. Rationale:
  • avoids complex query encoding
  • works with CBOR bodies
  • works with COSE envelopes
  • avoids pretending arbitrary procedures are pure resources
  • keeps transport simple

8.7 REST Requirements

8.7.1 Model CRUD Routes

For every model with applicable permissions, generate:
Example:

8.7.2 Query Parameters

Required v0 query parameters:
Required simple filters:
Current implemented extended filters:
Representative curl examples:
Current relation filter shape is still intentionally narrow, but now distinguishes to-one and to-many traversal:
The route grammar is contract-stable for the current slice as:
Grouped where= expressions are canonical. not(...) binds tighter than ,, and , binds tighter than |; repeated , and repeated | chains are left-associative. Parentheses override the default grouping. For example, where=a=true,b=true|c=true parses as ((a=true,b=true)|c=true), and where=not(author.profile.nickname=Zulu,published=true) negates the full grouped subexpression. Legacy or= remains supported only as a compatibility convenience for a top-level OR list such as or=title__startsWith=Pub|author.email=owner@example.com. New contract text and examples should prefer canonical grouped where= because it is the only route form that supports nesting, mixed AND/OR grouping, and not(...). Current relation support requires explicit field metadata:
Projection rules for the current route contract:
  • fields is validated against the generated scalar model fields for the requested resource.
  • include is validated as a declared relation path such as author.profile.
  • includeFields[path] is validated against scalar fields on the already-included relation at that exact path.
  • includeFields[path] is rejected unless include=path is also present.
  • nested include trees now materialize on list and fetch routes through declared relation paths only.
  • includeFields[path] narrows each included path payload independently.
  • ordering support remains separate from include materialization and still does not extend to to-many sort paths.
Projection examples for the current route contract:
Recommended reading of the three projection knobs:
  • fields answers: which scalar fields should the primary resource return?
  • include answers: which declared relation paths should be embedded?
  • includeFields[path] answers: which scalar fields should that embedded path return?
Representative use-cases:
  • card/list views that only need id, title, and one related label
  • admin grids that need compact rows plus a related owner email
  • mobile detail previews where the embedded relation should stay small for bandwidth and cache efficiency
  • to-many includes where each embedded row only needs identifiers and display text
Generated Rust helpers for the same nested projection contract now follow this shape:
Current generated Rust selection boundary:
  • root Selection builders choose root scalar fields and nested include paths
  • nested IncludeSelection builders choose scalar fields and nested include paths under the already-included relation
  • projection wrappers decode projected payloads without changing the wire contract
  • exact type-level projection remains intentionally looser than the projection wrapper surface
Recursive relation filters and first-pass relation-aware ordering use those explicit @relation(fields:[...],references:[...]) declarations rather than foreign-key inference. To-many relations must use an explicit quantifier operator (some, every, or none) before the target field path. In this slice, relation-aware sort support follows nested to-one paths such as sort=author.email or sort=author.profile.nickname, missing related rows currently sort with explicit NULLS LAST behavior, and the model primary key is appended automatically as a deterministic tie-break when relation ordering is used without an explicit primary-key term. Any path that crosses a to-many relation remains unsupported for ordering. fields is validated against the generated scalar model fields for the requested resource, and include is validated as a declared relation path. Nested include paths now materialize on list and fetch routes, and includeFields[path] narrows the payload at each already-included relation path. Exact type-level projection and child-collection sort/pagination remain intentionally out of scope for this slice. Generated typed delegates expose the same supported relation filter and nested to-one ordering paths through both compatibility helpers such as cratestack_schema::post::author::email_desc() and cratestack_schema::post::author::profile::nickname_eq(...), and a builder-style DSL such as cratestack_schema::post::author().email().desc(), cratestack_schema::post::author().profile().nickname().eq(...), and cratestack_schema::user::sessions().some().label().contains(...) for where_expr(...) usage. Those builder expressions can now be combined directly with .and(...), .or(...), and .not()-equivalent grouping through FilterExpr, for example post::author().profile().nickname().eq("Zulu").and(post::published().is_true()).or(post::title().contains("Draft")). The route grammar remains narrower than the full generated delegate surface where true: the typed delegate DSL still exposes composition through where_expr(...) and typed builders rather than every delegate capability being representable as route query syntax. Legacy orderBy remains accepted as a compatibility alias for sort, but new contract text and generated clients should prefer sort. Practical relation query examples: Additional clarification for the current implemented boundary:
  • include is now validated as a declared relation path rather than only a top-level relation name.
  • Nested include paths now materialize on list and fetch routes.
  • includeFields[path] narrows the payload at each already-included relation path.
  • What remains out of scope is exact type-level projection and child-collection sort/pagination, not nested include trees themselves.
Projection note:
  • treat fields, include, and includeFields[path] as three separate controls rather than one overloaded selector
  • includeFields[path] narrows the payload at each already-included relation path
  • exact type-level projection and child-collection sort/pagination remain out of scope for this slice

8.7.3 Request and Response Body

Bodies must be encoded using the configured CoolCodec. JSON must not be assumed by the generated code.

8.7.4 Content Negotiation

For v0, CrateStack may enforce a single configured codec per router. Example:
If the request uses an unsupported content type, CrateStack should return:
If the response cannot satisfy Accept, CrateStack should return:
A relaxed v0 may skip full Accept negotiation and always return the configured codec content type.

8.7.5 Error Responses

Errors should also be encoded using the configured codec. Canonical error shape:
Example codes:

8.8 Codec Requirements

8.8.1 Codec Trait

CrateStack core exposes:

8.8.2 Required Codecs

v0 should provide:
JSON may exist as an optional crate:
But JSON must not be required by core.

8.8.3 CBOR Requirements

CBOR should support all generated model and procedure input/output types through Serde. Recommended implementation crate:

8.8.4 Generated Types

All generated transport types must derive:
This allows codecs to work without custom generated serializers.

8.9 COSE Envelope Requirements

8.9.1 Envelope Trait

CrateStack core exposes:

8.9.2 Required Envelopes

Core provides:
Optional crate provides:
Potential envelope implementations:
v0 may implement only CoseSign1Envelope initially.

8.9.3 Processing Order

Request processing:
Response processing:

8.9.4 Auth Interaction

COSE verification may enrich CoolContext, but it must not replace framework-level authentication unless the host application explicitly chooses that behavior. Example possible enrichment:

8.10 Authentication Context Requirements

CrateStack must expose a flexible context type:
Auth identity should support dynamic field lookup:
Convenience methods:
Framework integrations should extract or construct CoolContext from request extensions.

9. Architecture

9.1 Crate Layout

Recommended workspace:

9.2 Generated Code Structure

include_schema! generates:

9.3 Request Pipeline

Model CRUD request:
Procedure request:

9.4 SQL Generation Strategy

Use generated metadata plus SQLx query builders. Read example:
Update example:
Delete example:
Create example:

10. Public API Sketch

10.1 Application Setup

10.2 COSE Setup

10.3 ORM Usage

10.4 Procedure Usage


11. Error Handling

CrateStack should use one primary error type:
Mapping to HTTP status:
Errors must be encoded using the configured codec and sealed using the configured envelope if appropriate.

12. Security Requirements

  1. Default deny for all model and procedure operations.
  2. No generated route should bypass policy checks.
  3. Procedure implementations must use normal policy-protected ORM methods.
  4. No as_system API in v0.
  5. Policy checks should be SQL-injected for read/update/delete where possible.
  6. Procedure-level permissions must be checked before calling implementation code.
  7. Auth context must come from host application or framework integration.
  8. Codec errors must not leak sensitive internals.
  9. Database errors should be sanitized before transport response.
  10. COSE verification failure must reject the request before decoding application data.
  11. COSE metadata should be available for audit or policy extensions later.
  12. Generated code must avoid SQL injection by using bind parameters.

13. CLI Requirements

Even though the primary integration is include_schema!, a CLI is useful for validation and future migration support. Required v0 CLI commands:
Optional v0 commands:
Deferred commands:

14. Testing Requirements

14.1 Parser Tests

  • valid schemas
  • invalid schemas
  • diagnostic locations
  • procedures
  • policies
  • auth blocks
  • type blocks

14.2 Semantic Tests

  • duplicate names
  • unknown types
  • invalid auth references
  • invalid model references
  • missing primary keys
  • invalid procedure return types

14.3 Policy Tests

  • read policy SQL generation
  • update policy SQL generation
  • delete policy SQL generation
  • create policy checks
  • procedure policy checks
  • delegated DB-backed procedure auth checks
  • default deny behavior

14.4 ORM Tests

  • create
  • find unique
  • find many
  • update
  • delete
  • filtering
  • pagination
  • ordering

14.5 REST Tests

  • CRUD routes
  • procedure routes
  • CBOR request decoding
  • CBOR response encoding
  • unsupported content type
  • forbidden access
  • unauthorized access
  • not found

14.6 COSE Tests

  • valid signed request
  • invalid signature
  • missing signature
  • response signing
  • key resolution failure

14.7 Integration Tests

Use temporary PostgreSQL database or test containers. Test a full app flow:

15. Milestones

Implementation kickoff note: The current repo slice completes Milestone 0 plus a narrow parser/macro/codec/CLI spike from Milestones 1, 3, and 7. Current implementation note: The current repo slice now also starts Milestone 4 with cratestack-sqlx, generated model structs, generated type structs, generated create/update input structs, a schema-specific CrateStack wrapper, and delegate scaffolding for create, find_many, find_unique, update, and delete. Current limitations:
  • Generated SQL currently assumes snake_case column names and naive pluralized snake_case table names.
  • The current SQLx slice supports where_ with AND-combined scalar filters plus order_by with explicit ascending/descending clauses.
  • The current filter model is intentionally narrow and currently targets eq, ne, in, string contains, string starts_with, boolean convenience helpers, and is_null / is_not_null for optional fields.
  • Generated create inputs currently exclude @id and @default(...) fields.
  • Generated update inputs currently use optional patch fields and only include changed columns in SQL.
  • Read policy SQL injection now exists for @@allow(...) and @@deny(...) rules on find_many and find_unique, with deny taking precedence over allow.
  • The supported model-policy subset is intentionally tiny: auth() != null, auth() == null, boolean field checks, field == literal, field != literal, field == auth().field, field != auth().field, auth().field == literal, auth().field != literal, and all action expansion, with && / || support inside a single rule.
  • Policy parsing is still compile-time only inside the macro layer, and unsupported policy expressions fail code generation.
  • Write-policy enforcement now exists for create, update, and delete, but only for the same tiny predicate subset used by the current read-policy implementation.
  • Create policies are evaluated against generated input values plus auth context before insertion, while update/delete policies are injected into SQL row scoping; create-time auth-derived defaults now support the narrow @default(auth().field) form before create-policy evaluation.
  • DB-backed policy integration coverage now exists behind an env-gated test path using CRATESTACK_TEST_DATABASE_URL.
  • The current executable auth/policy capability matrix now lives in ../reference/auth-support-matrix.md.
  • Procedure-level policy execution now exists through generated runtime wrappers under cratestack_schema::procedures::* and reuses canonical policy types from cratestack-policy.
  • The current procedure-policy subset now supports grouped && / ||, auth() == null, nested args.<field> access, input-vs-auth comparisons, and input-vs-input comparisons, but it still does not expose a general DB-querying procedure policy language.
  • Generated procedure registry traits now exist and provide the first narrow execution contract for procedure implementations.
  • Model-level @@emit(...) directives now support the narrow created, updated, and deleted event set, generated typed subscription helpers under cratestack_schema::events, transactional outbox writes inside generated SQLx mutations, and explicit cratestack_schema::events().drain() style replay/drain hooks for in-process delivery.
  • Generated Axum procedure routes now exist for procedure invocation and enforce procedure policy checks plus delegated @authorize(...) checks before handler execution.
  • Generated Axum model routes now exist for GET /{modelPlural}, GET /{modelPlural}/{id}, POST /{modelPlural}, PATCH /{modelPlural}/{id}, and DELETE /{modelPlural}/{id} and reuse the same codec/context/policy pattern.
  • The generated HTTP layer now enforces a single configured codec per router for Accept and Content-Type, returning 406 Not Acceptable and 415 Unsupported Media Type for mismatches.
  • The current HTTP model slice is still intentionally narrow: list routes now expose canonical fields, shallow declared-relation include, relation-specific includeFields[relation], canonical sort, limit, offset, scalar equality filters, a small operator suffix set (__ne, __lt, __lte, __gt, __gte, __in, __contains, __startsWith, __isNull) with cuid, UUID, and DateTime parsing, canonical grouped where= expressions with not(...) > , (AND) > | (OR) precedence and left-associative chaining, and recursive relation filters backed by explicit @relation(fields:[...],references:[...]) metadata, with to-many traversal requiring explicit some / every / none quantifiers and any to-many sort path remaining unsupported. Legacy orderBy is still accepted only as a compatibility alias. The route grammar remains narrower than the full generated delegate/runtime filter surface, while write routes forward directly into the generated delegate/runtime layer without a richer request DSL.
  • cratestack/compose.yml now provides a local PostgreSQL 18 instance for driving DB-backed tests through CRATESTACK_TEST_DATABASE_URL.
  • Arbitrary policy functions, explicit impersonation/acting-as semantics, and a general DB-querying procedure policy language remain future work.

Milestone 0: Project Skeleton

Deliverables:
  • workspace setup
  • core crates
  • basic CI
  • formatter/linter
  • initial docs

Milestone 1: Schema Parser

Deliverables:
  • parse datasource
  • parse auth block
  • parse models
  • parse fields
  • parse attributes
  • parse type blocks
  • parse procedures
  • parse permissions

Milestone 2: Semantic Analyzer

Deliverables:
  • schema IR
  • validation errors
  • type resolution
  • policy expression validation
  • procedure signature validation

Milestone 3: Macro Codegen

Deliverables:
  • include_schema!
  • generated model structs
  • generated input structs
  • generated procedure trait
  • generated basic CrateStack type

Milestone 4: SQLx ORM

Deliverables:
  • PostgreSQL-only execution
  • create
  • find unique
  • find many
  • update
  • delete
  • simple filters
  • limit/offset
  • ordering

Milestone 5: Policy Enforcement

Deliverables:
  • model read policy
  • model create policy
  • model update policy
  • model delete policy
  • default deny
  • procedure allow checks

Milestone 6: Axum REST

Deliverables:
  • generated CRUD routes
  • generated procedure routes
  • path/query/body parsing
  • error responses
  • request context extraction

Milestone 7: Codec System

Deliverables:
  • CoolCodec trait
  • CBOR codec crate
  • optional JSON codec crate
  • generated handlers generic over codec

Milestone 8: Envelope System

Deliverables:
  • CoolEnvelope trait
  • NoEnvelope
  • initial COSE Sign1 envelope
  • request verification
  • response signing

Milestone 9: Hardening and Docs

Deliverables:
  • security review
  • integration examples
  • tutorial
  • API reference
  • migration notes
  • release candidate

16. v0 Acceptance Criteria

CrateStack v0 is successful when a developer can:
  1. Create a .cstack schema with models, policies, types, and procedures.
  2. Include it with cratestack::include_schema!("schema.cstack").
  3. Instantiate the generated CrateStack type with a SQLx PostgreSQL pool.
  4. Register procedure implementations.
  5. Use the generated ORM client in Rust.
  6. Mount generated Axum REST routes.
  7. Send CBOR requests to CRUD endpoints.
  8. Send CBOR requests to procedure endpoints.
  9. Receive CBOR responses.
  10. Enforce model permissions by auth context.
  11. Enforce procedure permissions by auth context.
  12. Confirm unauthorized operations are denied by default.
  13. Run without enabling JSON features.
  14. Optionally wrap requests/responses with a COSE envelope.

17. Open Questions

  1. Should procedure endpoints use /api/$procs/name, /api/_procedures/name, or /api/actions/name?
  2. Should read-like procedures ever support GET, or should v0 require POST for all procedures?
  3. Should the generated schema module always be named cratestack_schema, or should the macro allow custom names?
  4. Should codec selection be per-router, per-request, or compile-time feature based?
  5. Should COSE verification be allowed to populate CoolContext.auth, or only transport metadata?
  6. Should migrations be included in v0 or deferred?
  7. Should relation support be included in the first release or delayed until after scalar CRUD is stable?
  8. Should JSON be provided as an official optional crate from day one or delayed?
  9. Should policy expressions support custom functions in v0?
  10. Should generated handlers support pluralization customization?

For v0, make these decisions explicit:
  1. Use Axum only.
  2. Use PostgreSQL only.
  3. Use SQLx only.
  4. Use POST for all procedures.
  5. Use /api/$procs/{name} for procedures initially.
  6. Make CBOR the first official codec.
  7. Make JSON optional and not part of core.
  8. Make COSE an optional envelope crate.
  9. Keep authentication entirely external.
  10. Use default-deny permission semantics.
  11. Do not implement as_system.
  12. Generate a fixed cratestack_schema module in v0.
  13. Keep plugin architecture narrow: codec, envelope, auth/context extraction, and later hooks.

19. Risks

19.1 Macro Complexity

Large generated code from a procedural macro may produce difficult compiler errors. Mitigation:
  • strong schema diagnostics before codegen
  • readable generated identifiers
  • optional debug output later
  • CLI print-ir

19.2 SQL Policy Correctness

Incorrect policy injection could leak data. Mitigation:
  • default deny
  • snapshot tests for generated SQL
  • property-style authorization tests
  • avoid policy bypass APIs in v0

19.3 Codec/Envelope Coupling

Mixing CBOR and COSE incorrectly could make the transport hard to reason about. Mitigation:
  • keep codec and envelope traits separate
  • document strict processing order
  • test request and response pipelines thoroughly

19.4 Scope Creep

ORMs, REST generation, policy systems, and procedure systems can expand endlessly. Mitigation:
  • PostgreSQL only
  • Axum only
  • no separate service-description format requirement
  • no RPC
  • no GraphQL
  • no multi-db
  • no full migration engine in v0

19.5 Rust Type Generation Ergonomics

Generated APIs may become verbose or unidiomatic. Mitigation:
  • start with simple delegates
  • prioritize readable generated code
  • provide examples early
  • iterate with real app usage

20. Long-Term Roadmap

After v0:
  1. Relation loading.
  2. Nested includes.
  3. Nested writes.
  4. Migration engine.
  5. Additional databases.
  6. Actix integration.
  7. Poem integration.
  8. More codecs.
  9. More COSE envelope modes.
  10. Policy custom functions.
  11. Hooks.
  12. Soft delete plugin.
  13. Field-level encryption plugin.
  14. Generated admin UI metadata.
  15. Optional additional documentation/export surfaces.
  16. Optional client generation.
  17. More advanced procedure routing.
  18. Better diagnostics and editor tooling.

21. One-Sentence Product Definition

CrateStack is a Rust-native schema-first framework layer that turns .cstack files into a typed SQLx-backed ORM, policy-protected REST CRUD API, and custom REST procedure endpoints with pluggable CBOR/COSE-capable transport support.