Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Intelligent, Security-Conscious, and Designed for Effortless Productivity

Because With Rullst, We Rule! Rullst is a modular Rust framework suite built on Tokio, Axum, Tower, and SQLx for full-stack applications and the product workflows around them. Version 12 is under active development; use the capability and release documents to distinguish implemented behavior from roadmap work.

Welcome to the Rullst documentation. Build a small application, understand the code behind it, then explore the tools that fit your next idea.

New here? Start with the guided learning path →

Choose a generated application, a hand-written first route or a JSON API. Each path tells you what to run, what to expect and where to go next.

Documentation Hub

Learn the foundations, then explore

  • 🚀 Measured performance: Criterion suites and CI track regressions; application latency must be measured on the target workload.
  • 🧩 Server-rendered UI: html! and HTMX-oriented scaffolds are the audited default. LiveView and Wasm Island primitives exist, while turnkey Leptos/Dioxus adapters remain roadmap work.
  • 📖 Interactive Scalar Docs (/docs): Built-in OpenAPI UI via cargo rullst make:scalar.
  • ☸️ Kubernetes Native: Cloud-Native manifest scaffolding (cargo rullst make:k8s) with /health & /ready probes.
  • 📡 Rullst Radar & Prometheus: Process/Tokio observations and a Prometheus text exporter, with unavailable probes reported explicitly.
  • 🛡️ Local AI option: Use an Ollama endpoint when prompts should stay on infrastructure you control; network isolation remains a deployment duty.
  • 📦 Batteries Included: ORM (rullst-orm), Auth, Revenue Dashboard, Jobs, Mail, Scheduler, Cache, Security.

Your first idea, running in Rust

You do not need to understand every Rullst crate before writing your first application. Start small, make one thing work, and learn what each layer does as your product needs it.

You are exploring the v12 development preview. It is not yet a supported production release. Use the instructions for this source revision—not the older CLI installed by an unversioned crates.io command. The release program and current audit show the remaining gates.

Pick your starting line

What you want to doStart hereYour first visible result
“Give me an application I can explore.”CLI installation and blueprintsGenerated Rust, a local web page and a development loop
“Show me how the framework actually works.”Zero to Hello RullstOne complete typed route you write yourself
“I need a backend for another client.”Your first JSON REST APIA real HTTP response you can inspect with curl

For the simplest first experiment, choose Blank + SQLite, or Blank with --no-database if you do not need persistence yet. The larger LMS and SaaS blueprints have more moving parts and longer first builds. You can explore them after your toolchain and local workflow are working.

A small loop that teaches a lot

  1. Run it. Reach the local page or JSON endpoint before adding features.
  2. Find the code. Open src/main.rs, then the generated controllers, models and pages that exist in your chosen blueprint.
  3. Make one visible change. Edit a heading or response field. With cargo rullst dev, watch the rebuild and process restart; with plain cargo run, stop and restart it yourself.
  4. Break something safely. Introduce a small syntax error in your local experiment. Read the compiler diagnostic, correct it and save again. The supervisor keeps the old application running when compilation fails.
  5. Verify the result. Reload the page or repeat the request. A successful build is only the first check; the response should do what you intended.

See exact restart and state boundaries. Do not use production databases or credentials for this exercise.

Build your understanding, one layer at a time

Next questionGuide
Where do generated files go?CLI generators
How do I save and read data?Active Record CRUD and migrations
How does the page become interactive?HTML and HTMX
How do requests reach my handlers?Routes and middleware
Who is allowed to access a record?Ownership, RBAC and IDOR
How do I update the framework later?Assisted upgrades

You can use an AI assistant while learning. Ask it to explain the generated files, point to the exact APIs and show a failing test before a bug fix. Treat its output as a proposed change, not as proof. Rullst’s architecture specification is the common reference for both of you.

When the first run does not work

What you seeCheck first
cargo rullst is unknownInstall the matching CLI, then reopen your terminal if its binary directory is not on PATH.
The first build is taking a long timeSource builds compile Rust dependencies. Keep the output visible; elapsed time is not a reliable failure signal.
A database connection failsConfirm the selected backend and local .env values. PostgreSQL/MySQL/MariaDB need a running service; SQLite does not need a separate server.
The app cannot bind its portStop the conflicting process you own, or configure another port and restart the development command.
Optional persistence feels confusingSelecting none is valid. Add only the specialized stores your application actually needs.
A command differs from a screenshotUse the installed CLI’s --help and the matching source documentation. Screenshots are recorded examples.

If you are still stuck, share the command, operating system, Rust version, framework commit and a minimal reproduction in an issue or on Discord. Remove secrets and personal data first. Send security vulnerabilities privately using the security policy.

Before this becomes a real product

A generated app is a foundation, not a deployment approval. Review authentication, ownership, secrets, database backups and migrations, external provider setup and release status. Start with the security architecture, choose features using the capability status, and measure your own workload.

Ready? Create your first application →

Why Rullst?

Rullst is an opinionated, Axum-based full-stack framework for teams that want the productivity of a coordinated application platform without hiding Rust’s types or the underlying ecosystem.

Its strongest distinction is not that every individual feature is unique. It is the combination of compile-time-oriented APIs, explicit security boundaries, first-party backend capabilities, offline development contracts, and one coordinated CLI and release train.

Version status: Rullst v12 is under active development. This page lists implemented or explicitly bounded capabilities in the current source tree; it is not a production-readiness certificate. The framework specification, capability ledger, and v12 release program are authoritative when a shorter description and the code disagree.

The short answer

Choose Rullst when you want to build a backend-oriented Rust application with:

  • an explicit Axum, Tokio, Tower, and SQLx foundation;
  • compile-time-generated routes, HTML, and models, with compiler-visible diagnostics instead of runtime reflection;
  • a coordinated ORM, authentication, security, jobs, mail, AI, billing, administration, observability, and CLI toolchain;
  • secure, fail-closed defaults for privileged and production-facing surfaces;
  • deterministic offline paths for supported external-provider integrations;
  • server-rendered interfaces that do not require a project-local SPA bundle;
  • standard escape hatches when a framework abstraction is not the right fit.

What makes the combination distinctive

1. AI-native means explicit and inspectable

Rullst is designed so that both humans and coding agents can reason about an application from its source. Common routes, models, HTML trees, policies, and scaffolds are represented by typed Rust or macro input rather than runtime class scanning or hidden reflection.

This does not make generated code automatically correct. It makes important structure available to the compiler, review tools, and the CLI. Macro diagnostics, compile-fail tests, generated-project gates, and the AST-based IDOR scanner turn that structure into evidence.

The AI layer follows the same rule. Its bounded RAG pipeline makes tenant context, retrieval, context limits, source metadata, and audit explicit in one typed operation. It rejects differently tagged or unsafe passages and refuses ungrounded generation when retrieval is empty. The included cosine retriever is clearly process-local; production datastore authorization and durability are not hidden behind an “automatic AI” claim.

Conversational memory follows that explicit model too: a static-dispatch tenant-bound contract has a bounded offline store and an opt-in SQL adapter for SQLite, PostgreSQL, MySQL, and MariaDB. Each successful turn commits the user and assistant messages atomically, while revision compare-and-swap rejects stale cross-process writers instead of silently scrambling history or automatically repeating a billable model request.

2. A broad platform that keeps its foundations visible

Rullst coordinates a large backend surface in one versioned workspace, but it does not replace its foundations with proprietary runtime primitives:

  • applications can mount ordinary axum::Router values;
  • middleware remains compatible with Tower composition;
  • Tokio remains the asynchronous runtime;
  • SQLx pools and raw parameterized queries remain available beside the ORM.

The Axum and SQLx interoperability guide documents the supported escape hatches. Framework conveniences can still require migration work, so Rullst describes this as reduced lock-in rather than “zero lock-in.”

3. Security boundaries are part of framework design

The framework treats authorization and privileged tooling as architectural inputs rather than deployment footnotes. Current bounded contracts include:

  • fail-closed production environment and secret validation;
  • CSRF, secure headers, request heuristics, DLP, abuse controls, and security telemetry primitives;
  • route-scoped JSON Schema 2020-12 or OpenAPI 3.1-component enforcement with bounded offline compilation, local-only references and linear-time regexes;
  • explainable aggregate threat assessment plus an opt-in authenticated, subject-bound, expiring and locally one-shot proof-of-work gate;
  • Argon2id password hashing, expiring encrypted sessions, RBAC, model policies, and explicit owner-or-role guards;
  • a typed OAuth/OIDC session transaction that keeps PKCE verifiers and OIDC nonces server-side, expires them after ten minutes, and consumes them before callback validation;
  • a provider-generic automatic token coordinator that binds refreshes to the original user, redacts credentials, detects a bounded expiry window and prevents overlapping provider calls while waiters reuse a valid transition;
  • an authenticated Nexus admin surface and a loopback/debug-constrained Studio;
  • one bounded signed-payment-webhook verifier exposed through both Axum and opt-in Actix middleware adapters;
  • versioned AES-256-GCM ORM field encryption with authenticated context;
  • CI gates against panic!, unwrap(), and expect() in declared production targets.

These are defense-in-depth controls, not a claim that an application is secure without its own authorization model, deployment controls, reviews, and tests. The Sentinel is deliberately deterministic rather than marketed as autonomous AI: the host still owns aggregate collection, identity, accessible fallback, distributed replay state and enforcement. See the security architecture and v12 security claims.

4. Persistence depth without a fictional universal database API

The relational ORM provides generated Active Record and query APIs, transactions, migrations, relations, tenant scopes, policies, soft deletion, auditing, encrypted fields, typed pgvector helpers, structured telemetry, and an opt-in transactional outbox.

The primary relational matrix covers SQLite, PostgreSQL, MySQL, and MariaDB. Turso/libSQL has a separate typed primary profile. MongoDB, DuckDB, SurrealDB, Qdrant, Redis, and external search engines use capability-specific adapters so their different consistency and query models stay visible. MongoDB and SurrealDB share only a narrow identifier-preserving recovery boundary: a bounded encrypted snapshot can be rehearsed across both, while writer quiescence, schema provisioning, key custody and durable backup remain explicit application/operator work.

This is deliberate: Rullst prefers several honest, bounded APIs over one API that pretends documents, OLAP, graphs, vectors, key-value structures, and relational transactions have identical semantics. Read the polyglot persistence, transactional outbox, and Scout search guides.

The same boundary discipline now applies to messaging. rullst-messaging defines one versioned envelope, bounded topic/group metadata, idempotent publication, consumer groups, expiring acknowledgement leases, retry and dead-letter semantics, with a deterministic concurrent in-memory contract. A canonical bounded v1 envelope codec and allowlisted W3C trace-context boundary make byte compatibility and correlation behavior executable. The codec still does not open a remote connection or map broker-specific semantics. SQLite can explicitly protect header values and payloads with row-bound AES-256-GCM and a bounded rotation keyring while keeping visible metadata and operator duties honest. The opt-in relational outbox relay also proves its unavoidable publish-before-ACK crash window as an exact idempotent replay instead of calling two systems one atomic transaction. That makes offline tests and future adapter conformance explicit without pretending the local broker is Kafka, RabbitMQ, NATS, Redis Streams, or a cloud service. Applications still own topic authorization and idempotency of remote side effects. See Bounded Brokered Messaging.

5. External services remain usable during offline development

Supported AI, OAuth, mail, billing, search, and persistence adapters expose deterministic offline behavior when configured with their documented empty or mock_* credentials. This lets generated projects, tests, examples, and local sandboxes run without silently contacting a third party.

The Brazilian fiscal boundary applies the same evidence-first approach: its bounded DPS 1.01 builder, checksum-pinned official XSD catalogue, local PKCS#12 XMLDSig verification, deterministic issuance JSON, strict signed-authorization and structured-rejection codec, and mTLS client preparation are testable without calling SEFIN, while tax authorization remains explicitly disabled until the external trust and homologation gates pass.

Mail scheduling follows the same bounded approach: SQLite and Redis persist a due time and never claim it early, while unsupported real direct transports reject a future message instead of delivering it immediately. Offline fixtures may retain that timestamp for assertions. Polling delay, at-least-once delivery, and provider acceptance remain visible operational boundaries.

The optional native AWS SES v2 path follows the provider’s real authentication model instead of disguising a bearer request as AWS support. It delegates regional SigV4 and credential-provider rotation to the official AWS SDK while retaining the same pre-flight, HTML/text, attachment/CID and RFC 8058 message contract. Its local signed-request fixture proves integration shape, not AWS account readiness, reputation or inbox delivery.

Mocks prove the local application contract, not the live provider contract. Provider-specific production support still requires the applicable live or protocol tests, credentials, policy, and operational evidence.

6. Framework upgrades are a product feature

cargo rullst upgrade --dry-run inventories a project and reports a versioned migration plan. cargo rullst upgrade snapshots controlled files, updates the coordinated Rullst dependency train, applies supported compiler fixes, runs a Cargo check gate, and restores those files when the gate fails.

It intentionally refuses to invent database migrations, business rules, or security policy. The useful distinction is a bounded, recoverable upgrade transaction with human and machine-readable reports. See the assisted upgrade tutorial.

7. Server-first UI with optional escalation

The compile-time html! macro escapes supported dynamic text and attribute values, while RawHtml makes an unescaped boundary explicit. HTMX-oriented server rendering is the default path for applications that do not need a project-local SPA bundle.

LiveView, selected Wasm islands, Tera, and packaging helpers are optional strategies with separate maturity and operational requirements. Rullst does not claim that every frontend mode is interchangeable or bundle-free.

Omni extends that server-first model with a deterministic Tauri shell for desktop, Android, and iOS instead of creating a second business backend. Its portable versioned client envelope carries no local authority, and the opt-in native offline foundation adds bounded idempotent proposals, explicit conflict/resync state, account-bound encrypted snapshots, and a coordinator with request budgets, timeouts, and cursor checks over application transports. Platform secure storage, concrete HTTP/background transport, physical-device behavior, signing, and store acceptance remain separate evidence gates; see the web-first Omni guide and offline synchronization guide.

8. Local control surfaces use real application signals

Studio is a local developer control room with explicit unavailable states when a probe is not connected. Nexus generates an authenticated administration surface from registered model metadata and applies server-side field policy. Tenant-owned models can explicitly bind every built-in CRUD/search/batch path to a trusted Core tenant context, while an opt-in required audit row shares the mutation transaction and rolls it back on audit failure. The host still owns identity/membership and immutable external evidence; Nexus does not disguise a same-database mutable table as an append-only audit service. Studio’s relational browser can also perform deliberately narrow primitive row edits/deletions: the write surface exists only behind the verified local request capability, binds values, identifies one row by its inspected complete primary key and runs against four relational engines. It is not presented as a replacement for application tenant authorization or production administration. Studio flag toggles also invalidate already-warm database flag drivers in the same process without an unbounded key registry; cross-process invalidation remains an explicit application transport.

The queue monitor follows an explicit retention rule as well. SQLite removes successful payloads by default, while applications that need operational history can opt into a validated, atomically pruned limit and purge it from Studio. Rullst does not silently trade privacy for a more impressive dashboard.

The ORM emits secret-free structured spans for generated and raw query entrypoints, streams, transaction outcomes, and Rullst-owned pool acquisition timing. The host owns subscriber initialization, sampling, collector security, and retention. See the telemetry guide.

9. Scaffolding is treated as shipped code

The CLI can generate projects, routes, models, migrations, auth, mail, billing, deployment manifests, LMS foundations, and other bounded application slices. Rullst tests representative generated projects and maintains structural matrices instead of assuming that a template is correct because its source file compiles inside the CLI crate.

The Academy slice is already more than a landing-page mock: its generated SQLite journey exercises server-owned progress, assessment, score, leaderboard, automation, notifications and school boundaries. Its accessible lesson presentation supports bounded video/audio metadata, mandatory WebVTT captions for video and escaped transcripts. Its generic activity boundary also keeps points out of untrusted submissions: static-dispatch evaluators construct server-authored results, with tested single-choice, bounded pair-matching and typed-recall exercises as the first examples. Typed recall retains a policy-bound digest rather than raw learner input, with deliberately narrow normalization semantics. The complete starter then rederives the persisted activity policy and atomically projects that opaque result into a versioned score event, leaderboard and outbox. Its owner-only routes accept only an idempotency key plus a chosen option, pair IDs or bounded typed text, while the same transaction locks the exact evaluator configuration and retains an exact-replay attempt; cross-user, actor/evidence/policy mismatch and conflicting replay are materialized negatives. Opt-in rullst-box-v1 policies also update a durable, bounded learner/activity review schedule in that transaction; a replay cannot move it, and an owner-only due queue rechecks school membership, course scope and enrollment. The algorithm is deliberately inspectable and does not claim FSRS/SM-2 compatibility or learning efficacy. That is a useful foundation for a language-learning product, not a claim that Rullst generates pedagogy, content, speech recognition, native-device behavior or a complete Duolingo equivalent.

For example, make:billing --model Workspace now materializes distinct SQLx and Turso-primary persistence profiles. Both are generated, linted, migrated and exercised through subscription ownership and collision negatives; the test does not turn provider sandbox validation or distributed reconciliation into an automatic claim.

The billing facade follows the same bounded design: an explicit generic subscription handle delegates pause/cancel without erasing the provider type, while its grace-period value validates time bounds but leaves persistence, authorization and entitlement policy visible in application code. Its shared-quota boundary likewise derives one subject from trusted tenant state, reserves idempotently before creation and can commit the counter and a relational domain insert in the same caller-owned transaction. SQLite plus live PostgreSQL/MySQL/MariaDB contention contracts prove that concurrent workspace members stop at the configured limit; membership and tier reconciliation remain visible host responsibilities. Coupon and trial management follows the same evidence boundary: coupon IDs are bounded/redacted, Stripe binds the expanded applied discount, and 1–730-day relative trial updates have stable explicit-clock retries plus bound Stripe and Lemon Squeezy protocol fixtures. Providers without a reviewed live operation fail explicitly instead of presenting a local no-op as remote success.

The same CLI includes inspection, toolchain diagnostics, migration assistance, SBOM generation, and a bounded static route-access scanner. Generated output remains application code: review it, test it, and keep it under version control.

10. Ambition is separated from evidence

Rullst keeps ambitious ideas, but labels them as implemented, partial, experimental, offline mock, roadmap, or not recommended. Benchmarks are reported for their measured workload instead of becoming universal performance slogans. Hardware, provider, regulatory, and certification claims remain incomplete until the corresponding external evidence exists.

This distinction is essential for a broad framework. The capability ledger records the boundary; the technical comparison records both competitive strengths and areas where other frameworks are more mature.

Where Rullst is a particularly good fit

Rullst is worth evaluating for:

  • backend-heavy SaaS and multi-tenant applications;
  • server-rendered products that still need jobs, realtime, mail, billing, and administration;
  • Rust teams that prefer one coordinated release train over assembling every application subsystem independently;
  • projects that need relational data plus explicit document, analytics, search, vector, graph, or key-value capabilities;
  • organizations that value offline reproducibility, secure scaffolds, and machine-readable engineering evidence;
  • teams using coding agents that benefit from explicit APIs, bounded files, strong types, and a normative specification.

When another choice may be better

Rullst may not be the best choice when:

  • only a small HTTP router or middleware library is needed;
  • a browser-component ecosystem is the primary product architecture;
  • long production history, a very large plugin ecosystem, or commercial support matters more than Rust-native integration;
  • the application requires a capability that the ledger still marks partial, experimental, or roadmap;
  • the team does not want the maintenance surface of a batteries-included framework.

In those cases, using Axum directly, another Rust application framework, a frontend-centered framework, or a mature platform in another ecosystem can be the more responsible decision.

How to evaluate Rullst today

  1. Start with the Zero-to-Hero tutorial.
  2. Check the exact capability you need in the specification and capability ledger.
  3. Enable only the required Cargo features and inspect their dependency graph.
  4. Run the documented tests against your database, provider, proxy, and threat model.
  5. Pin an immutable release or commit for evaluation; do not deploy from a moving development branch.

This page will be reviewed again against the immutable v12 RC source and CI evidence. Until then, it is a concise map of the strongest implemented ideas, not a substitute for the detailed contracts.

Simple capability status

This is the compact view of Rullst’s canonical M1–M39 programme. It is derived from the root ROADMAP; that roadmap and the capability ledger retain the evidence and limitations. The labels here deliberately do not turn partial foundations into completed features.

v12 RC engineering snapshot — 5 September 2026

This functionality inventory is deliberately separate from release quality. All 15 non-IoT crates currently meet the approved A floor and rullst-iot meets its approved B exception. All 15 active crates have also reached their higher audited local ceiling: 1,509/1,509 local campaign points are backed by repository evidence (100%), with zero planning points remaining. The exact SHA still earns those dimensions only when its conditioning gates pass. The older 91.8% readiness estimate in the v12 release programme is a superseded planning snapshot, not the current percentage. The post-audit release report is authoritative and keeps the RC at NO-GO until its candidate gates and explicit approval are complete.

Coverage is a separate RC gate. Codecov measured candidate 27e81152 at 90.06% across the whole repository and 91.33% for the framework_libraries component, so both candidate views exceed their zero-tolerance 90% targets. This is valid candidate evidence, not evidence for a future commit or tag; the same gates must pass again on the exact frozen RC SHA.

Historical candidate 27e81152 completed all 22/22 applicable automatic push workflows successfully, including the full all-feature workspace suite on Linux, macOS and Windows. It predates the reopened audit and is therefore background evidence only. The post-audit candidate must independently pass its applicable hosted and manually dispatched release matrices.

The later manual campaign on 45fbdbe7 produced passing bounded Miri, Kani and sanitizer evidence, while fuzzing usefully exposed three stale harnesses and a real Unicode-boundary panic in database-URL redaction. Those findings have committed regressions and corrections; the repaired ORM parser plus both affected security targets passed 100,000 local libFuzzer/AddressSanitizer executions. This is remediation evidence, not a substitute for rerunning the complete heavy matrix on the frozen RC SHA.

Coverage viewAudited checkpointRC meaning
Whole repository90.06% (74,219/82,408) on 27e81152Historical passing checkpoint. This primary public number includes CLI and proc-macro production sources, but does not approve the post-audit candidate.
Framework libraries91.33% (56,119/61,446) on 27e81152Historical component pass; it does not replace either the repository aggregate or a fresh post-audit result.
v12 RC requirementat least 90% in both viewsMust be reproduced by Codecov on the exact frozen RC commit, together with at least 90% patch coverage.

The two percentages are neither conflicting measurements nor values to average: they answer questions about different path sets. Rullst must keep the whole-repository result primary and must not reuse this candidate’s passing result as proof for a later SHA that Codecov has not measured.

The latest ceiling gain is the umbrella rullst facade’s dedicated shared-local SQLite profile. It composes Auth revocation, Capital quota, encrypted Connect tokens, Mail suppression, encrypted Messaging and Core queueing behind aggregate lifecycle readiness, then proves restart/idempotency, plaintext secret exclusion and isolated fail-closed corruption. It deliberately does not claim a cross-subsystem transaction, whole-file online consistency, key/backup operations or multi-host coordination.

rullst-ai has now earned its audited 95/A local ceiling. In addition to strict opt-in OpenAI-compatible SSE/cancellation, AuditDeliveryClient supplies bounded HMAC-authenticated export and AdaptiveAiEvaluator<P> supplies bounded multi-turn feedback, explicit pass/fail/inconclusive results and a raw-content-free JSON report. Receiver operations, non-compatible provider protocols, exact live-model results and corpus quality remain external or v13 work rather than being mislabeled as v12 guarantees.

rullst facade versus rullst-core

PackageRoleTypical user choice
rullst-coreLow-level runtime engine: HTTP server, routes, lifecycle, queue/realtime, storage/cache and the default browser-security baseline. It deliberately does not aggregate every domain crate.Use directly when a library/application wants only the runtime primitives and explicit dependencies.
rullstErgonomic umbrella facade. Cargo features re-export Core plus selected ORM, Auth, Security, AI, Mail, Capital, Studio, Nexus, Messaging and IoT APIs through one dependency. It also exposes the browser/WASM surface used by web-first applications; Omni packaging itself is a CLI workflow, not a re-exported crate. Its maturity cannot exceed the crates selected underneath it.Use for most Rullst applications and enable only the required features.

Documentation release gate

Before the v12 RC is tagged, the complete repository documentation remains an explicit review gate: build the mdBook, compile the Rust snippets sourced from all public tutorials, validate local links and anchors, reconcile commands, features and version examples with the frozen manifests, and manually review the upgrade guides and external-provider boundaries. A green documentation build proves structural consistency, not that every external service or store workflow was homologated.

LabelMeaning
ImplementedThe stated bounded scope exists and has automated evidence.
🟡 Still to implement — partialA useful foundation exists, but the complete milestone does not.
Still to implement — not startedNo implementation sufficient for the milestone exists.
🚫 Impossible as promisedAn absolute outcome cannot be established by framework code alone or is not a responsible technical guarantee.

Canonical milestones

IDCapabilitySimple status
M1CLI and make:* generator matrix🟡 Still to implement — partial
M2Fast linkers and measured build-time improvements🟡 Still to implement — partial
M3Escape hatches, granular features, diagnostics, and ejection🟡 Still to implement — partial
M4make:resource and local error console✅ Implemented — scoped
M5mdBook, OpenAPI, and typed client generation🟡 Still to implement — partial
M6ORM parity and Turso/libSQL profile🟡 Still to implement — partial
M7Portable edge runtime, distributed data, and safe upgrades🟡 Still to implement — partial
M8Explain-and-approve index recommendations⏳ Still to implement — not started
M9Local auth, OAuth/OIDC, TOTP, passkeys, and WebAuthn🟡 Still to implement — partial
M10Mail, DTO validation, distributed rate limits, and Shield🟡 Still to implement — partial
M11Nexus, Omni, billing, and entitlements🟡 Still to implement — partial
M12Defence-in-depth security programme🟡 Still to implement — continuous/partial
M13Audited PQC protocols and sandboxed Wasm extensions⏳ Still to implement — not started
M14HTMX-first SSR and real Leptos/Dioxus interoperability🟡 Still to implement — partial
M15Runtime queues/cache/scheduler plus brokered messaging🟡 Still to implement — bounded local messaging foundation; remote adapters open
M16Wasm islands and #[client_component] protocol🟡 Still to implement — partial
M17Realtime, object storage, media, and packages🟡 Still to implement — partial
M18LiveView-style server-driven UI🟡 Still to implement — partial
M19Radar, agent schemas, spans, and Prometheus✅ Implemented — bounded
M20Persistent event stream and verifiable ledger semantics⏳ Still to implement — not started
M21Omni frontend protocol and mobile bridge🟡 Still to implement — partial
M22Human-reviewed agentic DevOps recommendations🟡 Still to implement — partial
M23Diagnostic auto-healing recommendations🟡 Still to implement — partial
M24no_std IoT frames/packet encoders, signed OTA gate, and durable-counter CAS boundary🟡 Still to implement — partial
M25Embassy-based async embedded integration⏳ Still to implement — not started
M26Guided PaaS/VPS deployment🟡 Still to implement — partial
M27Kubernetes scaffolding and health/readiness probes✅ Implemented — scaffolding scope
M28Compile-time DI and Inject<T>✅ Implemented — foundation
M29Scalar playground and complete OpenAPI generation🟡 Still to implement — partial
M30Tonic/gRPC and Protobuf support🟡 Still to implement — partial
M31Aerospace/autonomous/defence systems⏳ Separate safety-critical programme; outside the general framework suite
M32Axum/Tower escape hatches and proc-macro diagnostics✅ Implemented — bounded
M33Server-side declarative SaaS entitlements⏳ Still to implement — not started
M34Schema-driven TypeScript/React/Dart/Swift SDKs⏳ Still to implement — not started
M35Distributed OpenTelemetry waterfall in Studio🟡 Still to implement — partial
M36Read-only explainable natural-language SQL assistant⏳ Still to implement — not started
M37Reviewable one-click error-console patch workflow🟡 Still to implement — partial
M38Vendor-specific SQLite replica/synchronization profile⏳ Still to implement — not started
M39Optional self-hosted rullst-gateway load balancer⏳ Still to implement — separate v13 research/foundation; no managed-cloud parity claim

Current planning snapshot: 5 implemented, 24 partial, and 9 not started inside the 38-milestone web-framework horizon. M31 is excluded because it is a separately governed safety-critical programme. The weighted planning estimate is 44.7% complete and 55.3% remaining; this is not v12 release readiness and the 33 milestones without strict closure are not 33 blockers for v12.0. The v12 programme owns release gates, while the root roadmap assigns confirmed v12 defects to 12.0.x maintenance and all additive capability work, research or major contracts to v13.

Claims that are impossible as framework guarantees

No useful capability above is dismissed merely because it is difficult. The impossible label is reserved for absolute wording that code in this repository cannot honestly establish:

Absolute claimStatus
100% uptime or zero data loss in every deployment🚫 Impossible as a framework guarantee
Exactly-once arbitrary external side effects🚫 Impossible without destination-level idempotency/transactions
Zero latency, zero overhead, zero allocations, or universal sub-100ms builds🚫 Impossible as a universal guarantee
Total memory safety or universal panic-freedom across dependencies, FFI, generated apps, and every input🚫 Impossible to prove from this repository alone
Automatic fiscal, security, privacy, App Store, or hardware certification🚫 Requires independent authorities, environments, and evidence
Universal one-click zero-downtime deployment🚫 DNS, credentials, migrations, providers, and rollback remain operational inputs
“Best/fastest/most secure framework in the world”🚫 Not a technical property without dated, reproducible comparative evidence
Unattended production mutation that is always safe🚫 Approval, scoped authority, audit, recovery, and application policy cannot be removed

Use the quality scorecard for per-commit engineering evidence. It is intentionally separate from this functionality view.

Quality scorecard

Audit reopening (2026-09-05): the second-computer CLI handoff and new negative regressions revealed gaps outside the earlier test inventory. Scores and ceiling-completion statements below describe the previous campaign; they are not a current release approval. See the v12 release audit for reviewed scopes, corrections and gates that must pass before reconfirming those conclusions.

Rullst generates an evidence-bound quality scorecard for every push to main and every pull request. The report is attached to the corresponding Rust CI run as quality-scorecard-<commit SHA> and is also written to that run’s job summary.

The permanent source of a run score is therefore the commit plus its workflow run, not a mutable badge. The versioned expert-audit ceilings live in .github/quality-scorecard-policy.json; CI can reduce them when required evidence fails, but a green run cannot inflate them.

What the score measures

DimensionWeightEvidence
API and architecture20Audited explicitness, cohesion and public-boundary quality; awarded only while format/Clippy, feature and MSRV gates pass
Verification depth25Audited test depth constrained by the cross-platform all-feature workspace result
Security and failure design20Audited fail-closed/error/secret boundary constrained by Clippy and the applicable specialist gate
Documentation and DX15Audited user guidance, examples, feature/migration clarity and evidence links
Operations and release20Audited durability/live/recovery/release maturity constrained by feature, MSRV and specialist evidence

Specialist evidence includes database/Redis live matrices, AI evals, the threat minimum, release local-access negatives, provider matrices, the facade’s shared-local recovery composition, and Messaging’s wire/trace, encrypted SQLite and ORM outbox crash-replay cases. Failed, cancelled, or skipped applicable gates suppress the dimensions they prove; the report is still generated so a red push cannot hide its note.

Documentation/DX evidence also includes a Cargo-aware aggregation of the 52 public tutorials. It consumes the Markdown files directly during the normal all-feature doctest run, so a green workspace test proves the standalone Rust examples compile on that SHA; explicitly contextual fragments remain visible as ignored and do not count as compiled examples.

Grades use the following fixed bands: A+ 97–100, A 90–96, B 80–89, C 70–79, D 60–69, and F below 60.

The v12 RC quality objective is A (90) or better for every crate except rullst-iot, whose approved floor is B (80). A+ is an evidence threshold, not a value to assign by intent. This owner-approved gate is deliberately stricter than the earlier all-B floor and reopens bounded implementation work before the feature freeze. It does not pre-approve a commit: the exact SHA still earns each ceiling only when every constraining gate succeeds.

Current audited green-gate scores — 3 September 2026

These are the maximum current scores when every referenced Rust CI gate passes. They are not presumed results for a new commit; the exact per-SHA artifact applies the real gate outcomes and includes the full finding for every row. They are also not the highest scores that future repository-owned work can earn. Here, current audited score means the score supported by code and evidence already present; local campaign ceiling means a planning target that must still be earned. Keeping those columns separate prevents a desired score from being published as an achieved one.

CrateCurrent audited scoreGradePrincipal remaining evidence boundary
rullst-core96ADependency operations, distributed deployment and host authorization
rullst-orm96AOnline snapshot isolation, managed/PITR backup, vendor operations and application writer/tenant/key policy
rullst-security96ATrusted rollback checkpoints, external SIEM delivery, independent audit and certification
rullst-connect95ARemote-provider leases/reconciliation, key/directory/backup operations, multi-host refresh and provider conformance
rullst96AWhole-file recovery/backup operations, multi-host coordination and maturity inherited from opt-in domain crates
rullst-auth95AShared ceremonies, multi-host state, refresh workflow and normative WebAuthn conformance
rullst-mail95AAuthoritative malware/CDR inspection, multi-host operations and inbox/provider evidence
rullst-messaging96ARemote protocols/replication, full metadata encryption and provider operations
cargo-rullst95AProduction deployment, provider accounts and real-application acceptance
rullst-ai95AExact live-model results, non-compatible streaming/provider loops, durable audit receiver operations and external retrievers
rullst-studio94ADurable/OTLP storage, key operations and shared operator authorization
rullst-capital93ALive authorization, authoritative outbox/reconciliation and homologation
rullst-orm-macros95ACompiler/ecosystem compatibility beyond the tested matrix
rullst-nexus95AHost identity/domain policy, global/custom-route authorization, immutable audit delivery and production operations
rullst-macros94AReal browser/network ecosystems and host identity policy remain external
rullst-iot83BConcrete transport/hardware storage, flashing and bootloader evidence
Repository (equal-crate aggregate)94A1,509/1,600; exact score remains conditional on the SHA’s gates

Measured gap to the v12 quality gate

Every non-IoT crate now has an audited ceiling of A or better, while IoT meets its approved B exception. The gap to the required grade is therefore zero. This also closes the repository-owned ceiling campaign, but it does not authorize a release: the exact RC SHA must still make every conditioning gate green, including the dedicated facade composition job.

CrateCurrentGap to required gradeNext evidence cluster to audit
None0Every current audited ceiling meets its approved RC floor

Maximum-local v12 campaign

The release floor is not the stopping target. The table below records the provisional highest score that the current campaign can responsibly pursue with repository-owned implementation, deterministic fixtures, local services, CI and documentation. These targets do not alter the scorecard policy and must not appear as achieved scores until their evidence is implemented and green on the exact commit.

The campaign is scoped to the 15 non-IoT crates, v12 quality, and the historically promised [x] capabilities. IoT remains audited at its accepted 83/B evidence but is outside the remaining ceiling work. The campaign does not pull every open v13 idea into the RC. External provider acceptance, app-store/device testing, fiscal homologation, independent audit and production operation remain external even when a bounded implementation earns a high A.

All 15 active crates have now reached their audited local target: rullst-core, rullst-macros, rullst-orm-macros, rullst-messaging, rullst-capital, rullst-mail, rullst-auth, rullst-nexus, cargo-rullst, rullst-studio, rullst-orm, rullst-security, rullst-connect, rullst-ai, and the umbrella rullst facade.

CrateCurrent auditedProvisional local ceilingPoints remainingRepository-owned evidence clusterExternal boundary retained
rullst-core96/A96/A0Monotonic readiness/admission/drain, explicit supervisor shutdown and startup/concurrency/poisoned-state evidence complete for this campaignDependency operations, production topology, replica/load-balancer coordination and host domain authorization
rullst-orm96/A96/A0Authenticated bounded document recovery, fail-closed inventory semantics and real MongoDB → SurrealDB → MongoDB rehearsal complete for this campaignOnline snapshot isolation, managed/PITR backup, vendor operations and application writer/tenant/key policy
rullst-security96/A96/A0HMAC-chained local SIEM integrity, explicit key rotation and exact forgery/ordering/restart negatives complete for this campaignTrusted whole-tail checkpoints, external SIEM delivery/acknowledgement, independent audit, certification and real SOC operation
rullst-connect95/A95/A0Encrypted shared-local token state, immutable quota, transactional generation CAS, restart/contention/corruption evidence and public/facade integration complete for this campaignRemote-provider lease/reconciliation, key/directory/backup operations, multi-host replication, live-provider conformance and IdP operations
rullst96/A96/A0Six-subsystem shared-local SQLite composition, aggregate readiness, restart/idempotency, secret-exclusion and isolated corruption evidence complete for this campaignWhole-file backup/recovery operations, multi-host coordination and maturity inherited from external provider/device evidence
rullst-auth95/A95/A0Bounded shared local revocation/device lifecycle, restart and counter-CAS evidence complete for this campaignShared ceremonies, multi-host replication, refresh workflow and normative WebAuthn conformance
rullst-mail95/A95/A0Bounded inspection, durable shared-local suppression and minimized terminal observations complete for this campaignAuthoritative malware/CDR inspection, provider webhook conformance, multi-host operations, inbox placement, DNS reputation and live-provider acceptance
rullst-messaging96/A96/A0Encrypted local durability, canonical codec/trace and ORM outbox crash-replay contracts complete for this campaignRemote broker operation, replication, full metadata encryption and cloud acceptance
cargo-rullst95/A95/A0All 270 structural profiles, eight generated-test/runtime cases, seven public-CLI profiles covering all six blueprints plus polyglot axes, and v5/v6/v11 transactional upgrade/recovery fixtures complete for this campaignProduction deployment/account acceptance
rullst-ai95/A95/A0OpenAI-compatible SSE/cancellation, bounded authenticated audit export and static-dispatch adaptive evaluation with content-free reports complete the repository-owned campaignNon-compatible protocols need adapters; audit receiver operation, exact live-model behavior/results and corpus quality remain external
rullst-studio94/A94/A0Push-only authenticated trace ingestion, bounded query heuristics and metadata-only Memory/live-Redis inspection complete for this campaignDurable/OTLP storage, producer key operations, shared operator identity/RBAC/TLS and production topology
rullst-iot83/B83/B0Approved B exception retained outside the 15-crate ceiling campaignPhysical hardware, flashing/bootloader, broker/device interoperability and certification
rullst-capital93/A93/A0Signed-environment binding and bounded HMAC-chained local fiscal command audit/recovery complete the local targetLive gateway acceptance, authoritative multi-writer outbox/reconciliation and official fiscal homologation
rullst-orm-macros95/A95/A0Fail-closed structured parser, 24 exact UI diagnostics and generated runtime cross-evidence complete for this campaignCompiler/ecosystem compatibility beyond the tested matrix
rullst-nexus95/A95/A0Trusted-context tenant scope, transaction-coupled audit and bounded admin operation contracts complete for this campaignHost identity/domain policy, global/custom-route authorization, immutable audit delivery and production operation
rullst-macros94/A94/A0Bounded grammar/diagnostics, native server route, versioned Wasm transport, CSRF composition and generated-project evidence complete for this campaignReal compiler/browser/network ecosystem matrix and host identity policy beyond CI
Repository1,509/1,600 = 94.3 (rounded 94/A)1,509/1,600 = 94.3/A0Repository-owned ceiling campaign complete; the exact SHA gates remain authoritativeA+ remains outside this local planning ceiling

On this planning scale, 100% of the maximum-local v12 target is now backed by committed evidence and zero planning points remain. Awarding those points is still conditional on every applicable gate succeeding for the exact SHA; this completion is not a release decision or an external validation claim. rullst-iot is the only accepted campaign result below A; its approved B exception reflects missing physical/device evidence rather than lowering the release gate for the other 15 crates. This table must be re-audited whenever implementation reveals a stronger or weaker boundary.

The final point allocation may differ from these candidate clusters after code review. External provider acceptance, fiscal homologation, device testing, store publication, and independent audit must stay explicitly external even when enough repository-owned evidence exists to reach A.

What the score does not measure

The score is not:

  • feature completeness or roadmap percentage;
  • a claim that every crate has the same maturity;
  • provider acceptance, device/store validation, fiscal homologation, or a security/compliance certification;
  • a benchmark or proof that Rullst is better than another framework;
  • a substitute for the exact release gates on the candidate SHA.

Those questions belong to the capability status, the capability ledger, and the release evidence. Keeping these axes separate prevents a well-tested bounded foundation from being mistaken for a finished remote integration.

Interpreting changes between pushes

A score should change only when its evidence changes. The per-push review will call out:

  1. the previous and current SHA;
  2. repository score and changed crate rows;
  3. the exact gate responsible for a gain or loss;
  4. feature-completeness movement separately, when applicable.

No points are added for code volume, number of features, marketing claims, or raw test count alone.

v12 release audit follow-up

Status: in progress; RC is NO-GO while the findings and final gates below are open. This report supersedes blanket readiness interpretations of the earlier local-ceiling campaign. That campaign and hosted coverage measurements remain historical evidence for their recorded commits, not proof of the current tree.

Baseline and method

The integration baseline is 7743bab3 on fix/cli-logo-animation-speed, including the second-computer report in CLIFIX.md. Its earlier “uncommitted” wording describes the remote review session; that delivery is now committed and fetched here.

Review covers every published crate. IoT receives only a light triage under the owner’s explicit v12 exception. Each deep review traces public inputs through validation, state and side effects, compares documented behavior, and adds negative regressions for reproduced defects. Existing tests alone do not close a finding. Regressions are run before and after corrections when feasible.

This is repository-owned code review and testing, not an independent security assessment, provider certification, or a claim that every line or deployment configuration has been exhaustively analyzed. Tests are serialized on the memory-limited local machine. The final local workspace gate completed after the correction batch.

Evidence-bound repository grade

The authoritative scoring method remains the quality scorecard; this audit does not create a second, more flattering grading system. Under that policy, the reviewed implementation supports a maximum local aggregate of 94/A when all conditioning gates are green. Each of the 15 active non-IoT crates has an A ceiling, while rullst-iot retains the owner-approved 83/B exception because physical device and boot-chain evidence is outside the v12 campaign.

That is an evidence-bounded engineering grade, not a security certification, feature-completeness percentage, independent audit result, or release authorization. A candidate earns the recorded grade only when its exact SHA passes the applicable scorecard constraints. Until the final candidate’s automatic and manual gates, packaging checks, documentation review, and explicit GO decision are complete, the repository remains NO-GO regardless of its provisional grade.

Coverage ledger

Crate / surfaceReview scopeCurrent status
rullst-ormProjection identifiers, empty-set predicates, tenant/global scopes, transactions, policy mutations, nested queries and searchReproduced isolation/transaction defects corrected; focused default/strict-SQLite/Redis regressions and the final all-feature workspace gate are green; live external-backend matrices remain release evidence
rullst-orm-macrosGenerated SQL bindings, parser diagnostics, portable identifiers and scope generationCorrected generated contracts; 43 unit tests, one smoke test and 24 compile-fail cases green
rullst-coreHTTP security composition, CSRF, lifecycle and development state ownershipCSRF/security composition 21 tests green; four reload tests and actual Node client behavior tests green
cargo-rullstRemote CLI handoff, public profile accuracy, supervised restart, generated contractsSupervisor, dashboard, command-behavior, public-profile and materialized blueprint gates are green; snapshot launch now retries bounded transient Linux executable-busy races
rullst-authJWT expiry/revocation, encrypted sessions, role guards, passkey/SQLite cancellationCorrections green: 60 library tests and five durable JWT integrations
rullst-securityWebSocket origin enforcement, middleware readiness, bounded redaction, crypto/input policies159 library tests and two Tower tests green; final rate-limit run passed 11 tests including two added afterward (161 library cases now)
rullst-connectOIDC claims/nonce, refresh semantics, callback state and token lifetimesCorrections green: 204 library tests with Axum-session and SQLite features
rullst-capitalProvider side effects, pricing, charge binding, authenticated payload schema and signature protocols99 library plus 22 integration tests green with Actix; one later Actix duplicate-header regression also green
rullst-nexusAdmin transport/origin/authorization and tenant/audit boundariesCorrections green: 50 library plus 11 integration tests, including real SQLite tenant/audit cases; the default coverage pass now includes those cases and its shared-pool race was removed, with 10 consecutive parallel integration runs green
rullst-studioLocal operator boundary, handoff layout/telemetry changes, dynamic HTMLStatic boundary review found no additional reproduced defect; 48 library tests, integrations and the final workspace gate are green
rullst-macrosEscaping/raw HTML, generated handler/runtime contractsStatic trust-context review completed; no new reproduced defect; final all-feature workspace tests and doctests are green
rullst-aiProvider/mock separation, redirect handling, response limits and tool-policy boundaries102 library tests green, including real local HTTP regressions for all five native transports
rullst-mailProvider side effects, transport limits, attachments/headers, suppression and delivery evidence98 library tests green with SQLite, including actual HTTP and cancelled-write regressions
rullst-messagingPublication/lease/retry/idempotency, local durability and outbox compositionCancellation defect corrected; focused SQLite evidence and final all-feature workspace coverage, including the optional ORM outbox relay, are green
rullstFacade feature wiring, composed subsystem and tutorial contractsStatic facade/feature review; existing verified-TLS composition retained; final composed all-feature workspace tests and doctests are green
rullst-iotManifest/public capability honesty only; no hardware or deep auditLight review complete; README/manifest agree on helper/simulator/transport boundaries; approved scope exception

Counts above describe separate focused runs and overlap; do not sum them into an invented coverage metric. In addition, the complete workspace test suite passed with all features, and workspace Clippy passed with all targets, all features and -D warnings.

Reproduced defects and correction boundaries

AreaObserved failureCorrection and remaining boundary
ORM projectionsSafe-looking select/pluck accepted SQL expressionsValidate safe projection identifiers; deliberate raw SQL remains a caller-owned escape hatch
ORM membership and scopesEmpty IN matched all rows; OR escaped tenant/global/soft-delete constraintsEmpty sets are false predicates; group mandatory scopes separately and preserve nested query errors
ORM searchLocal and external Scout paths bypassed model scopes; empty external results could select ID zeroStart from the scoped query, reject missing context before provider calls and bind provider IDs through empty-aware membership
ORM transactionspluck, streaming/eager paths or mutation callbacks could bypass/wait on an already borrowed transactionUse the managed executor; release query locks before callbacks/eager work where supported; unsupported mutation-callback reentry returns a typed validation error instead of hanging
ORM mutations/macrosBulk delete bypassed model policy; keyset iteration could escape its cursor; unsupported identifiers reached malformed generated codeReject unauthorized bulk mutation, group keyset predicates, propagate errors and emit compile diagnostics for unsupported identifiers/scopes
Durable local storesCancellation during manual BEGIN left uncommitted state in a pooled connectionSQLx RAII transactions in Auth JWT/passkey, Mail suppression and Messaging; cancellation racing dispatched commit still requires reconciliation
AuthenticationExpired revoked JWT could become accepted during skew allowanceEnforce hard expiry independently of permitted clock skew; no distributed revocation claim
OAuth/OIDCMissing claims, nonce downgrade, refresh-token confusion, empty callback state and invalid lifetimesStrict provider-specific validation and checked positive bounded lifetimes; real accounts and distributed one-shot callback storage still need external evidence
Security middlewareHTTP/2 CONNECT bypassed WebSocket origin policy; cloned Tower services lost acquired readinessApply the origin guard to the extended method and call the ready service instance
Abuse controls/logsReset zero-limit admission, counter overflow, concurrent capacity escape and redaction suffix leakageChecked bounded admission, atomic capacity/reclamation and bounded fail-closed redaction; controls remain process-local
Core CSRFEmpty proofs accepted; valid split Cookie fields rejected; duplicate proofs ambiguousNonempty bounded unique tokens, multi-field cookie parsing and exact supported form media type; unsigned double-submit is not a session-signed CSRF scheme
Nexus operator accessAn absolute HTTPS URI impersonated verified TLS; local Host/Origin boundary incompleteRequire the private verified-transport capability and validate local browser Host/Origin; deployment proxies must supply the correct trusted adapter
Capital live operationsFabricated portals/no-op mutations, four hardcoded prices and undocumented mock aliasesExplicit unsupported errors for unimplemented live behavior; deterministic mocks only through documented mock credentials; consult the crate’s provider-method matrix
Capital receipts/webhooksIncomplete authenticated payloads inferred active/paid; charge identity insufficiently bound; Polar/MP body-only signatures did not represent their protocolsValidate required event/status/charge bindings; bounded Polar header-based Standard Webhooks verification; incompatible legacy live signature paths fail closed, including MP until its full provider verification is implemented
AI/Mail transportsRedirects forwarded private request content; AI JSON unbounded; suppression cancellation leaked statePooled redirect-disabled clients, connection/request budgets, bounded native AI responses and SQLx rollback ownership; native custom endpoints remain trusted operator configuration
Public DLL reloadWindows LMS loaded an independent ORM/runtime state and unsafe cross-runtime workarounds were proposedRemove public DLL generation and use directly linked supervised restart; retained legacy loader is experimental and not a stable Rust ABI
Release coverageThe earlier final-main LLVM artifact reported 78,962/87,941 lines (89.7897%) while the upload job itself stayed green; default Nexus SQLite/audit paths were omittedNexus is now included in the merged default-profile pass and exact 90% whole-repository and framework-library floors run before upload. PR #183’s hosted artifact reported 79,349/87,941 lines (90.2298%) overall and 59,767/66,004 (90.5506%) across 435 governed framework-library files; the frozen release SHA must repeat this gate
Fuzz campaignEarlier hosted runs found complete-tree rendering in unsupported-union and missing-ID model diagnostics. On 36411ea1, 39 of 40 targets completed their full 5.5-hour campaigns; fuzz_parser alone found a third valid derive tree where unconditional Field::span() validation exceeded the ten-second per-input limitAnchor model, relation, field and unsupported-type diagnostics to bounded identifiers instead of rendering complete syntax trees; retain all three discovered shapes in the parser corpus. The latest exact ASan reproducer improved from a repeatable 12.1-second timeout to about 30 milliseconds locally; a fresh five-minute local campaign completed 1,740,804 executions without a finding, and all 43 macro unit tests plus 24 compile-fail cases are green. Hosted diagnostic run 34495340300 then completed 1,541,970 executions in 301 seconds on corrected code commit 40c1b083, with no finding. The complete 40-target campaign remains required on the frozen candidate SHA

The parser correction also passed the exact local workspace gates: cargo test --workspace --all-features and cargo clippy --workspace --all-features -- -D warnings. This is local evidence only and does not replace the hosted final-candidate matrix.

The adversarial regressions use local databases, mock keys, signed synthetic tokens and loopback HTTP servers—not real credentials or real payment requests. Provider capability corrections are observable behavior changes: callers must handle explicit errors where previous code returned misleading success.

Website, README and first-run documentation

The organization root website and the framework Pages site were different deployments. The old organization site still described main as v5 and dev as v12, and its privacy page asserted unverified worldwide legal compliance. Both entry points now have prepared matching source, with separate deployment receipts still required. The new copy keeps v12 unreleased and v5 end-of-life.

The landing uses local CSS/JavaScript/images, finite reduced-motion-aware animation, thirteen owner-supplied social links and a concrete privacy notice. No analytics, social embeds or browser storage were added. Benchmark templates replace remote fonts with system fonts and pin Chart.js with integrity metadata; the remaining jsDelivr request is disclosed. The README preserves the top and bottom dedication, workflow dashboard, genuine coverage/Scorecard badges and evidence boundaries. It corrects the obsolete frontend-profile advertisement.

The beginner learning page links existing authoritative tutorials instead of creating another competing API reference. Initial guides clarify matching CLI installation, optional persistence, first-build time, actual generator paths and how to verify a visible result.

Verified locally: mdbook build docs, python3 .github/validate-site.py, node --check docs/site.js, and node .github/site-browser-smoke.mjs. The Chromium test passed desktop, 390/320-pixel layouts, keyboard/mobile menu behavior, clipboard success/denial, privacy disclosure, reduced motion, no-JavaScript navigation and no external landing requests or browser storage. The exported organization site also passed with --organization-site; this is not a WCAG or cross-browser certification.

Development reload decision

The public v12 development loop uses supervised process restart. Both cargo rullst dev and cargo rullst dash enable it automatically; plain cargo run remains ordinary execution. The wizard no longer asks for a DLL profile, and the legacy scaffold flag fails with migration guidance.

A successful build precedes stopping the existing child. Compilation errors leave that child serving. Each process runs an owned executable snapshot, so a Windows executable lock does not prevent the next Cargo build. The browser refreshes through a same-origin generation probe after a new server responds. State in memory resets; this does not promise zero-downtime deployment.

The tutorial explains the contract. The v13 decision is evidence-driven: compare measured reload time, failure recovery, process cleanup, memory and state ownership across databases and operating systems before considering a different architecture.

Evidence still required

  • Review all changed paths together and freeze the candidate; the broad review above is bounded repository-owned evidence, not an independent audit.
  • Repeat the real HTTP/Chromium acceptance pass for the representative Blog application on the final candidate; materialized compile/test contracts for every generated blueprint are already green.
  • Review CI/dependency/security alerts and run the applicable manual release matrices on the actual candidate commit.
  • Repeat the complete 40-target hosted fuzz campaign after the bounded parser diagnostic correction; the first campaign is evidence for 39 targets, not a pass that can be carried onto the replacement commit.
  • Repeat package/preflight, site/browser and documentation checks on that candidate.
  • Reassess quality/readiness using these results; do not carry forward 91.8% readiness or 100% local-ceiling completion as current audited facts.

Residual limitations for the next reviewer

  • HTML escaping does not make RawHtml, custom escaping implementations, JavaScript contexts or URL policies safe automatically. RPC parameters are untrusted inputs, not identity assertions.
  • Some direct session/passkey helpers depend on caller input-size bounds even when HTTP middleware limits requests. Cookie isolation matters for the unsigned double-submit scheme. Local rate controls are not distributed limits.
  • OIDC fixtures do not establish live-account acceptance, JWKS stampede resistance or multi-host atomic callback consumption.
  • NFS-e remains preparation/offline evidence, not official fiscal authorization. Payment fixtures do not homologate providers or fully model every event schema.
  • SQLite cancellation tests prove rollback ownership before commit; they cannot make cancellation during a dispatched commit into exactly-once knowledge.
  • Some older route smoke tests accept unavailable-database responses and prove route presence only. Real SQLite behavioral tests are identified separately.
  • IoT received only the agreed manifest/README review. No physical devices, browser WebAuthn ceremony, store signing, live provider or deployment proxy certification was performed.

Evidence handling

Confirmed security defects remain local until corrections and regression evidence are ready for a coordinated commit. Findings are classified by impact, not by whether the previous scorecard happened to be green. Provider behavior that cannot be completed within the current v12 contract must fail explicitly and be documented as unsupported; mock success is not live-provider evidence.

Repeatable focused verification receipts

These focused commands and the final local preflight succeeded during the September 5–6 correction batch. The September 8 PR #183 tree, merged as adb83c8b, independently repeated cargo test --workspace --all-features, strict all-feature workspace Clippy, formatting and diff checks successfully:

CARGO_BUILD_JOBS=1 cargo test -p cargo-rullst --lib -- --test-threads=1
CARGO_BUILD_JOBS=1 cargo test -p rullst-core --lib server::dev_reload -- --test-threads=1
node rullst-core/src/server/dev_reload/client_tests.cjs
CARGO_BUILD_JOBS=1 cargo test -p rullst-nexus --lib --tests -- --test-threads=1
CARGO_BUILD_JOBS=1 cargo test -p rullst-security --lib rate_limit::tests -- --test-threads=1
CARGO_BUILD_JOBS=1 cargo test -p rullst-ai --lib
CARGO_BUILD_JOBS=1 cargo test -p rullst-mail --features sqlite --lib
CARGO_BUILD_JOBS=1 cargo test -p rullst-connect --features axum-session,sqlite --lib
CARGO_BUILD_JOBS=1 cargo test -p rullst-capital --features actix --lib --tests
CARGO_BUILD_JOBS=1 cargo test -p rullst-capital --features actix --lib middleware_rejects_duplicate_standard_webhook_headers
CARGO_BUILD_JOBS=1 cargo test -p rullst-messaging --features sqlite -- --test-threads=1 --quiet
CARGO_BUILD_JOBS=1 cargo clippy -p cargo-rullst -p rullst-core --lib -- -D warnings
CARGO_BUILD_JOBS=1 cargo clippy --workspace --all-targets --keep-going -- -D warnings
CARGO_BUILD_JOBS=1 cargo test --workspace --all-features
CARGO_BUILD_JOBS=1 cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
git diff --check
bash .github/check-historical-roadmap-ledger.sh
bash .github/check-crate-architecture.sh

ORM default/strict-SQLite/Redis and macro compile-fail results are recorded in the coverage ledger. Live-service backend verification remains distinct from the passing all-feature compile, lint and local workspace gates.

Website maintenance and privacy boundaries

The public entry points have different deployment sources:

Entry pointRepository sourcePurpose
https://rullst.github.io/Rullst/Rullst.github.ioOrganization landing page and standalone privacy notice
https://rullst.github.io/Rullst/This repository’s pages.yml workflowMatching landing page, mdBook, images and benchmark dashboards
gh-pages branch in this repositoryBenchmark workflow dataCriterion history consumed by the Pages build; not a development branch

The landing design and copy have one editable source: docs/home_template.html, docs/site.css and docs/site.js. Preserve the footer dedication and the v12 preview/v5 end-of-life notice. Use actual source and release evidence for claims; do not hardcode aspirational coverage, scorecard, speed or certification values.

Validate before deployment

From the framework checkout, using Node 24 and a locally installed Chromium (google-chrome, or set CHROME_BIN):

mdbook build docs
python3 .github/validate-site.py
node --check docs/site.js
node .github/site-browser-smoke.mjs

The browser test checks desktop and 390/320-pixel layouts, keyboard and mobile navigation, clipboard success/denial, privacy disclosure, reduced motion, no-JavaScript navigation, resource failures, CSP errors, external requests and browser storage. It is not a complete accessibility audit or cross-browser certification. Optional --screenshots /absolute/output/directory records viewport previews without adding binary artifacts to the repository.

The source landing has no analytics, social embeds, remote fonts, cookies or local/session storage. CSS and JavaScript are local; ambient, hero, workflow and scroll-reveal motion is finite, progressively enhanced and disabled by the reduced-motion preference. Content stays visible when JavaScript is unavailable. Clipboard access follows an explicit button click and copies only the displayed command.

Hosting still processes requests. The privacy notice links GitHub’s statement and does not promise control of its logs or retention. Benchmark dashboards load integrity-pinned Chart.js from jsDelivr; documentation can store display preferences and contain external content. Do not extend the landing’s narrower description to those surfaces or to user-built applications. New tracking, embeds, forms or storage require a fresh privacy review before deployment.

Keep the organization website synchronized

Preserve any work in the separate website checkout before running the exporter:

node .github/export-organization-site.mjs /path/to/clean/Rullst.github.io
node .github/site-browser-smoke.mjs --organization-site /path/to/Rullst.github.io

The exporter refuses a dirty tree or wrong repository. It replaces only the five known website files, derives privacy.html from the landing notice and does not commit, push or deploy. Review the diff and deploy matching framework documentation first, then the organization website. Keep independent commit and deployment receipts for each repository. A framework push alone does not update the organization’s root landing page.

The footer’s thirteen destination links are the owner’s supplied community list. Instagram, TikTok and YouTube handles are normalized to full profile URLs. These are normal links, not embedded feeds or a claim that account availability has been independently verified. Recheck ownership and links when the owner changes that list.

Rullst Academy product programme

Status: proposed reference product, not a shipped framework capability. Academy must live in a separate repository and depend only on published Rullst packages. Its operation is evidence for the framework; its product content and deployment are not part of the rullst crate.

Rullst Academy is a web-first platform for learning Rust and Rullst through short explanations, authoritative interactive exercises, review sessions and small practical projects. The target is an enjoyable learning product, not a claim that a framework can automatically produce good pedagogy or a universally better alternative to an existing course.

Product boundary

The browser-accessible web application is canonical. Server-side code owns identity, school scope, authorization, course versions, grading, progress, achievements and audit. Omni shells may later add narrowly scoped native capabilities without becoming a second security or business model.

The Academy repository must:

  • pin one exact Rullst RC or stable release and contain no monorepo path dependencies;
  • use public CLI scaffolds and escape hatches instead of private framework modules;
  • keep curriculum, content, product UI, deployment and learner support in the application;
  • contribute only proven, reusable abstractions back to optional framework crates;
  • act as the first real upgrade, backup/restore and recovery consumer before a Rullst stable release is declared ready.

Focused first release

The first useful Academy release should complete one narrow journey well:

  1. A learner creates an account, joins the appropriate school and enrolls in a published course.
  2. The learner reads or watches an accessible lesson and resumes recorded progress.
  3. A server-authoritative quiz, matching or typed-recall activity records an idempotent result without accepting client-authored points.
  4. The review queue schedules a later activity and an achievement or certificate is derived from persisted rules.
  5. A practical project is submitted to a constrained runner and receives deterministic test feedback.
  6. An instructor drafts, reviews and publishes content; an administrator can audit the resulting events without crossing school boundaries.

Polish should concentrate on this journey: responsive SSR, keyboard operation, visible focus, reduced motion, clear errors, fast navigation and useful empty states. Payment, social features, advanced gamification, native-store releases, offline synchronization and a large course catalogue are not prerequisites for the first release.

Initial curriculum and projects

Two short tracks are enough to validate the product:

TrackInitial lessonsPractical outcome
Rust foundationsownership and borrowing, structs/enums, pattern matching, errors, iterators, async and testsa tested command-line application that persists bounded data
Rullst web developmentproject creation, routes/SSR, forms, ORM/migrations, authentication/authorization, queues and deployment preparationa secure small web application with owner-only CRUD and background work

Every exercise must have a versioned ruleset, bounded input/output and retained test identity. Curriculum authors, not an LLM, define the expected concepts, tests, hints and completion conditions.

Safe practical-code runner

Learner code is hostile input. It must never execute inside the Academy web process or through a mounted host Docker socket. The application-owned runner must use a disposable rootless container or stronger microVM boundary with:

  • no network by default and no cloud/application credentials;
  • read-only base images and a fresh writable workspace per attempt;
  • explicit CPU, memory, process, file, disk, output and wall-clock limits;
  • a reviewed Rust toolchain/dependency policy and immutable image digest;
  • bounded compile/test logs with control-character handling and secret redaction;
  • an idempotent submission identity, queued execution, cancellation and terminal retry/dead-letter policy;
  • cleanup after success, failure, timeout and worker restart;
  • adversarial escape, fork-bomb, filesystem, network and output-amplification tests before public use.

The runner returns structured test evidence. It does not grant points directly; the Academy service binds that evidence to the authenticated learner, project version and server-owned scoring policy.

Local AI mascot

The mascot can be a friendly tutor backed by the existing Ollama path in rullst-ai, with a deterministic offline fallback for tests. It should use a bounded RAG corpus containing version-pinned official Rust material, Rullst documentation and Academy-authored hints.

The mascot may explain an error, ask a guiding question, retrieve a relevant lesson or suggest the next exercise. It must not:

  • authoritatively grade code, invent completion or change persisted points;
  • execute arbitrary tools or learner code outside the sandbox boundary;
  • retrieve another learner’s conversation, submission or school data;
  • receive secrets, raw session values or unnecessary personal data;
  • present an uncited generated statement as official Rust or Rullst behavior.

Responses should carry source references and a visible “local AI tutor” label. Provider/model unavailability must leave the curriculum and deterministic grader usable. Prompt-injection regressions, context limits, tenant binding, PII masking and secret-minimized audit remain mandatory.

What exists and what remains application work

BoundaryCurrent reusable foundationAcademy must still prove
Learning domainGenerated curriculum, enrollment, progress, activities, quizzes, review, completion, certificates, leaderboard and automation foundationscoherent product UX, content quality, complete authorship and browser E2E
Identity and schoolsSession/RBAC helpers and persisted school-scoped LMS contractsaccount recovery, invitations, device/session policy and every cross-school negative
AI tutorguarded providers, Ollama fallback, bounded tenant-aware RAG and audit contractscurated corpus, pedagogy, model evaluation, capacity and user-facing failure behavior
Practical projectsqueues, outbox and bounded messaging foundationsisolated runner, immutable images, resource policy and escape testing
Mediabounded accessible lesson metadata, captions and transcriptsupload, storage, scanning, transcoding, caption quality and retention
Operationshealth/readiness, telemetry, deploy scaffolds and upgrade assistantproduction topology, TLS/proxy identity, backup/restore, rollback, alerts and incident response

Repository and release boundary

Academy will be developed in a separate repository and conversation. This document records only the intended boundary and the reusable framework foundation that already exists; it does not authorize further Academy work in the Rullst repository and it is not a framework release gate.

  • v12.0: close the framework independently through its coverage, CI/package/security, upgrade and release-candidate gates. Academy does not need to exist or run against the RC.
  • v12.0.x: maintenance only for confirmed framework defects and security fixes, without an Academy capability programme.
  • v13: the next feature line. Reusable improvements discovered by the future external Academy may be proposed with their own bounded contracts; research-heavy or breaking work remains explicitly governed by v13 criteria.

The 32 canonical milestones that are not fully closed belong to the long-term v13 horizon. They are not release blockers for v12.0, and Academy itself is not part of that milestone denominator.

Acceptance evidence

Before Academy can be treated as release evidence, record all of the following against immutable application and framework SHAs:

  • the complete learner/instructor/admin journey on PostgreSQL, with SQLite kept as a local profile only when its declared limits are acceptable;
  • anonymous, cross-user, cross-role and cross-school denial tests;
  • browser accessibility and responsive-layout checks for the primary journey;
  • sandbox abuse tests and bounded compile/test output;
  • backup restoration, forward migration, rollback and framework-upgrade drills;
  • dependency-only installation from crates.io with no Rullst path overrides;
  • load and failure tests with unavailable AI, mail, cache and worker services;
  • a documented human GO/NO-GO decision and remaining product risks.

Related reusable guides include accessible Academy media, server-authoritative activities, durable spaced review, tenant-bound RAG, and the assisted upgrade workflow.

The Rullst philosophy

Rullst exists to make ambitious software feel possible in Rust without hiding the language, the generated code, or the boundaries that keep an application safe. It aims to be productive and broad, but its convenience must remain inspectable: static dispatch, compile-time generation, typed errors, explicit middleware, and ordinary Rust escape hatches are deliberate choices.

A story that stayed with us

A widely reported account of Rust’s origin begins with a broken elevator in Graydon Hoare’s apartment building. Its software had crashed, leaving him to climb the stairs. Rust itself began as Hoare’s personal project in 2006 and grew into a language designed to combine systems-level performance with much stronger memory-safety guarantees.

The elevator account is an origin story reported by MIT Technology Review, not a claim that every elevator failure was a memory bug or that Rust can make all software infallible. The lesson Rullst takes from it is narrower and more useful: when an entire class of failure can be prevented by design, prevention is better than asking every developer to rediscover the same danger.

Why Venelouis started Rullst

Rullst also has its own practical origin. Long before modern AI systems became capable coding partners, Venelouis wanted to build an education platform. He first learned what was possible by operating a Moodle installation on a VPS. Later, Laravel and AI helped him create a far more ambitious education product than he had previously believed he could build.

That experience made Laravel’s greatest strength tangible: a framework can let one person concentrate on the product instead of repeatedly assembling its foundations. Venelouis then wanted to rebuild that kind of product in Rust to use fewer resources, gain performance, and inherit Rust’s stronger compile-time guarantees. He could not find a Rust framework that combined the particular Laravel-like product workflow, breadth, approachability, and explicit security boundaries he wanted. Rullst began as an attempt to build that missing bridge.

Rullst is not a Laravel clone, and Rust should not be forced to behave like PHP. The inspiration is the feeling that a complete product is within reach; the implementation follows Rust’s strengths rather than concealing them.

Why Rullst jumps from version 5 to version 12

The version jump records the history of the ecosystem rather than six hidden major releases of the unified framework. When Venelouis began recreating creio.eu in Rust with AI assistance, the Gemini model he was using at the time told him that there was no direct Rust counterpart to Laravel Socialite for the workflow he needed. Instead of abandoning that part of the product, he created the first independent Rullst crate. That project became rullst-connect, which still carries the identity and social-login mission of that original work.

The crates initially evolved in separate repositories and at different speeds. By the time the umbrella Rullst framework was at version 5, rullst-connect had already reached version 11. Bringing the projects into one monorepo made their dependency changes, cross-crate compatibility tests, and coordinated releases easier to maintain. Aligning every publishable package on one version line was the natural next step.

A clean reset to 1.0 might have described the beginning of this unified era, but a registry history cannot be reset that way: crates.io does not permit a published version to be overwritten or removed, and lower new versions would not erase the already published higher ones. Rullst therefore advances the whole ecosystem directly to version 12, monotonically beyond Connect’s version 11. From the version 12 line onward, coordinated releases can follow the usual Semantic Versioning sequence. The jump is thus an act of package alignment and honest continuity, not a claim that standalone Rullst versions 6 through 11 were released as complete framework generations.

Core tenets

  1. Productive, not magical. Rullst coordinates routing, data, identity, security helpers, background work, AI, and developer tooling through APIs and generated source that users can inspect and replace.

  2. Simple to begin, explicit when it matters. Good defaults should remove repetitive setup. Authorization, tenancy, provider behavior, persistence, deployment, and recovery must still expose their real application-owned decisions.

  3. Prevent failure classes by design. Typed errors, bounded inputs, parameterized queries, compile-time generation, and fail-closed behavior are preferred to conventions that only work when every caller remembers them. This reduces risk; it does not create a universal security guarantee.

  4. Built for humans and AIs. Stable vocabulary, focused modules, static contracts, executable examples, and documented limits give human developers and coding agents a shared map of the system. AI assistance never replaces review, testing, or accountable approval.

  5. Evidence before claims. A feature claim must name its implemented scope and residual boundary. A test belongs to the revision and environment it exercised. Benchmarks describe measured workloads, not universal rankings.

  6. Emotional productivity matters. The framework should make builders feel capable, curious, and supported. Removing boilerplate is valuable when it leaves more attention for users, learning, and the purpose of the product.

The ambition and the boundary

The long-term ambition is deliberately large: make Rullst a foundation from which people can build many kinds of applications without surrendering Rust’s performance or explicitness. That ambition is a direction, not a promise that one framework can finish every product, operate every provider, or replace the judgment of its developers.

Rullst will therefore grow through bounded, reviewable capabilities. Where the repository has proof, the documentation should show it. Where real devices, provider accounts, production operations, independent review, or application policy are still required, the documentation should say so plainly. The goal is not to look complete. It is to keep becoming more useful without losing the trust of the people building with it.

Getting Started

Welcome to the Rullst Getting Started guide!

Your goal: install the matching preview CLI, generate a small application, open it locally and make your first change. Prefer to write the first route yourself? Use Zero to Hello Rullst.

Rullst is a strictly typed Rust framework suite for full-stack applications, designed around explicit APIs, measurable performance, and defense-in-depth defaults.

1. Installation

First, ensure you have Rust installed. The official and recommended way is to visit rustup.rs.

For macOS and Linux:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

For Windows: Download and run rustup-init.exe from the website.

Next, install the Rullst CLI from the same release train as the framework. The registry command installs the latest published release; it does not install the unreleased v12 source documented by this branch:

cargo install cargo-rullst

To evaluate v12 before its first RC is published, clone this repository and install the CLI from that exact checkout instead:

git clone --branch main https://github.com/Rullst/Rullst.git
cd Rullst
cargo install --locked --path cargo-rullst
cd ..

During this source-only phase, the pre-release CLI reuses the exact checkout from which it was compiled, even when project creation is invoked from another directory, provided that checkout has not been moved or deleted. Generated manifests therefore contain absolute path dependencies and are not portable yet. Running from the repository root remains an explicit fallback. Once an immutable v12 RC exists on crates.io, install that exact CLI version and use its matching registry packages instead.

2. Creating Your First Project

We have completely redesigned the project creation experience. Instead of remembering complex flags, just run:

cargo rullst

The Rullst App Creator will launch an interactive wizard. The example below creates a Portfolio while v12 remains unpublished:

  1. Select Create New App.
  2. App Name: Provide a simple lowercase name (e.g., my_portfolio).
  3. Starter Blueprint: Choose Portfolio. Labels and decorative suffixes can change; use the blueprint name as your reference.
  4. Choose your primary database. SQLite is the simplest local first run; PostgreSQL, MySQL and MariaDB require their database service to be running.
  5. Leave optional persistence capabilities empty unless you need an add-on. That selector accepts zero or more choices; it is not another required database.
cd my_portfolio
cargo rullst dev

Open the local URL reported by the command. Find the generated page code, change a heading and save. Confirm that the changed page appears after the successful rebuild/restart. Stop development with Ctrl+C before moving or removing the project directory.

Prefer a small deterministic starter without navigating the wizard?

cargo rullst new first_app --default --blueprint blank --database sqlite \
  --skip-initial-migration
cd first_app
cargo rullst dev

The first source build can take several minutes. --skip-initial-migration defers the generator’s bootstrap work; the development command runs the initial migration before starting a generated database-backed app. Never point this learning project at a production database.

Tip

The cargo rullst dev command compiles the project and starts the local server. Saving changes triggers a real rebuild and supervised process restart; a failed build leaves the previous application serving. The same-origin browser client refreshes after the new process responds with its generation marker. In-memory state resets. No hot-reload scaffold option is required. See the CLI reference.

Rullst v12 deliberately generates one audited application profile: Active Record with server-rendered html! views and HTMX enhancement. The framework still exposes lower-level repository and client-runtime foundations for application-owned integration. See the Architecture Choices Guide.

3. Rullst Blueprints Showcase

The Rullst framework accelerates your development by providing Blueprints. A Blueprint is a highly-polished, pre-built application template that serves as the foundation for your project.

When you run cargo rullst, the wizard asks you to select a blueprint. The blueprints use the Rullst color scheme and server-rendered HTML/HTMX patterns; allocation and latency depend on the generated page and runtime.

1. Blank Starter

Use Case: Custom, from-scratch development. This is the minimal template powered by server-rendered html! views and HTMX without a project-local JavaScript bundle. It includes a simple reactive counter to demonstrate server-driven communication. Other frontend foundations remain available as application APIs, but v12 does not advertise them as equivalent generated blueprints.

2. Portfolio 🔥

Use Case: Developer showcases and personal branding. Status: HOT! A visually stunning, glassmorphic portfolio template designed specifically for Rullst/AI developers. It includes:

  • Profile Settings in Nexus CMS (/nexus): Edit your name, title, bio, email, website URL, avatar photo, GitHub, and LinkedIn links live without changing code.
  • A responsive sidebar and Hero section with glowing glassmorphism effects.
  • Interactive Experience timeline and Skills tags.
  • Project cards showcase with live external links.

3. LMS Platform Starter

Use Case: Online learning products and course platforms. The complete profile is a bounded learning-domain foundation featuring:

  • School-scoped curriculum, enrollment, progress and versioned publication.
  • Quizzes, learning activities, assignments/rubrics, completion records and database-verifiable certificates.
  • Roles, leaderboard updates, transactional outbox/workers, scheduling and localized in-app notifications.
  • Accessible server-rendered catalog, course and media-player shells with explicit source, caption and transcript admission rules.

It remains a starter rather than a finished education product. Upload hosting, media transcoding and signed delivery, advanced/localized search, billing-linked entitlements, distributed failover, native offline playback, real-browser/WCAG evidence and PostgreSQL/MySQL isolation evidence remain application or roadmap work. Smaller auth, auth,learning and auth,learning,assessment profiles are available when the complete domain scaffold is unnecessary.

4. SaaS App Starter

Use Case: Subscription-based products and billing. An opinionated SaaS starting point, pre-wired with:

  • User authentication (login, signup, session management).
  • Stripe pricing panels and subscription checkout views.
  • Secure user dashboard.

5. Blog / Press

Use Case: Content creation and articles. A database-backed, server-rendered blog/CMS blueprint. It features:

  • A beautiful article reading view with typography optimized for readability.
  • Article CRUD and a server-rendered reading view. Markdown parsing is not part of the current generated starter.
  • SEO-friendly metadata injection.

6. ERP Pocket

Use Case: Business management, stock, and inventory tracking. An inventory-oriented back-office starter. It features:

  • A complex relational database schema (Products and Orders).
  • Full CRUD operations with HTMX.
  • A sleek, split-pane dashboard for simultaneous product listing and order creation.

Tip

Blueprint evolution: Blueprints are continuously checked by generator smoke tests. Treat generated code as an application starting point: inspect its configuration and rerun cargo check and security tests after customization.

Local Studio and Nexus access

The Blog, Portfolio, LMS, ERP, and SaaS blueprints expose visible buttons for both control surfaces during local development:

  • Nexus is mounted at /nexus and accepts only a verified loopback peer in a debug build, so the first local click needs no placeholder password.
  • Studio starts as a separate debug-only service at http://127.0.0.1:5555.

This convenience cannot be enabled in a release binary through RULLST_ENV or the legacy APP_ENV alias. A release build does not start the generated Studio task and requires unique NEXUS_ADMIN_USERNAME and NEXUS_ADMIN_PASSWORD values before Nexus can be constructed. Put production Nexus behind a verified TLS boundary and explicit application authorization; do not expose Studio publicly.

5. Database configuration and specialized stores

The wizard deliberately makes two separate decisions:

  1. Choose the primary SQLx Active Record backend: SQLite, PostgreSQL, MySQL, or MariaDB. MariaDB uses the MySQL wire protocol but has its own executable container contract. The blank/API starter can instead select Turso/libSQL as its primary typed ORM.
  2. Optionally add Turso/libSQL edge SQL to a SQLx application, MongoDB documents, DuckDB analytics, or SurrealDB documents/graph reads. These use explicit capability APIs and do not silently replace the primary pool.

After generation, the default flow compiles the new application once and runs its initial migration. A clean first build can take several minutes for larger blueprints. Use --skip-initial-migration when the network database is not yet configured, then run cargo rullst db:migrate from the generated project.

The non-interactive flags mirror the second step:

cargo rullst new edge_app --default --database mariadb --turso --mongodb \
  --skip-initial-migration

Automation can pin the supported blueprint, database and optional runtime capabilities. The v12 application architecture is intentionally fixed to Active Record plus server-rendered html!/HTMX:

cargo rullst new learning_portal --default --blueprint lms \
  --database postgres --ai \
  --skip-initial-migration

For a blank application with no primary relational database, use the explicit --no-database flag. It cannot be combined with --database. Generated SQLx profiles disable Rullst’s umbrella defaults and select exactly one strict relational backend, so a chosen PostgreSQL/MySQL/MariaDB profile is not accidentally compiled through an implicit SQLite default.

To create a Turso-primary API using the current bounded blank starter:

cargo rullst new edge_app --default --api --database turso \
  --skip-initial-migration
cd edge_app
cargo rullst db:migrate

The generated .env selects a persistent, real-SQL offline fallback and does not invent a SQLx DATABASE_URL:

TURSO_DATABASE_URL=mock_local
TURSO_AUTH_TOKEN=
TURSO_OFFLINE_PATH=turso-development.db

To use Turso Cloud, put the token in its own variable rather than in the URL:

turso db create my-app-db
turso db tokens create my-app-db
TURSO_DATABASE_URL=libsql://my-app-db-username.turso.io
TURSO_AUTH_TOKEN=replace-with-a-secret-token

The familiar derive explicitly selects the Turso backend:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, rullst_orm::Orm)]
#[orm(table = "users", backend = "turso")]
struct User {
    id: i64,
    name: String,
}
}

User::all(), find, save, create, delete, count, filtering, ordering and pagination then execute through the primary TursoOrm store. make:model --migration, make:migration, db:migrate, db:status, and db:rollback preserve this backend. The current first-class scaffold contract is deliberately limited to the blank/API profile; SQLx-specific LMS, SaaS, blog, portfolio, and ERP blueprints reject --database turso until ported. Transparent embedded-replica synchronization is not claimed.

TursoStore remains available for prepared SQL, bounded result materialization, transactions and checksummed migrations in either primary or additive configurations. See Polyglot Persistence for complete setup, security constraints and examples for every optional store.

Architecture choices in generated projects

cargo rullst new records product capabilities that materially change the generated application. Version 12 intentionally exposes one audited application architecture instead of presenting incomplete compatibility markers as equal implementations. The framework specification and capability ledger remain authoritative.

ORM style

Version 12 database-backed projects are generated with Active Record. The CLI does not ask for a global ORM architecture and does not expose an --orm flag. The framework’s repository APIs remain available when an application needs an explicit persistence boundary.

Architecturev12/v13 directionTrade-off
Active RecordThe single generated v12 profile and a retained v13 optionConcise, but persistence concerns remain close to models.
Data Mapper / RepositoryApplication-owned in v12; planned as a separately complete v13 profileMore explicit persistence boundary and more code to maintain.
HybridNot a global project profileThe two styles may naturally coexist per module, but the application must define that boundary.

Earlier v12 prerelease menus offered three labels, but Repository and Hybrid shared generation branches and support varied by blueprint. Removing those selectors does not remove ORM APIs; it prevents the generator from claiming equivalent end-to-end profiles before the generated routes, services and tests support them consistently. All database operations still need parameter binding, authorization and transaction design.

For most CRUD-oriented applications, start with Active Record and introduce a repository around domains that genuinely need a separate persistence boundary.

Database engines and capabilities

The first database selector chooses exactly one SQL Active Record backend:

SelectionCurrent v12 boundary
SQLiteLocal SQLx pool, migrations and relational ORM contract.
PostgreSQLSQLx PostgreSQL pool and live container CRUD/schema contract.
MySQLSQLx MySQL pool and live MySQL CRUD/schema contract.
MariaDBThe same MySQL protocol implementation with a separate live MariaDB contract.
TursoTyped Turso/libSQL primary profile for the blank full-stack or API starter; SQLx-specific blueprints do not offer it.

A second multi-select adds zero or more independent capabilities and accepts Enter with no selection. Capabilities already selected by the primary profile or CLI flags are omitted. Turso supplies explicit edge SQL, transactions and checked migrations; MongoDB supplies portable document CRUD; DuckDB supplies bounded analytics; SurrealDB supplies document CRUD and bounded read-only graph queries; Qdrant supplies bounded dense-vector operations. Selecting one adds the precise Cargo features and environment keys, but does not make every model portable between different database families.

This split prevents a Turso, MongoDB, analytics, or graph label from generating an application whose SQL Active Record migrations cannot run. See the Polyglot Persistence guide for the APIs and limits.

Frontend profile

Version 12 generates server-rendered html! views with HTMX enhancement and does not ask for a frontend engine or expose a --frontend flag. The five labels shown by earlier prerelease wizards were not five complete, interchangeable renderers:

SelectionCurrent v12 boundary
HTMX + Tailwind SSRAudited default scaffold using server-rendered html! views. There is no project-local SPA bundle, but HTMX and styles are still browser assets that must be pinned and served.
LiveViewCore supplies LiveComponent, Live::mount and live_ws_handler. The application registers routes and supplies the HTMX WebSocket extension, authentication and reconnect policy.
Wasm Island#[island], make:island and build:client provide a dual-target hydration foundation. Asset delivery, CSP, state, routing and browser E2E remain application work.
Pico.cssRecords the compatibility profile; the application must add, pin and serve Pico.css and validate the resulting pages.
TeraAdds the Tera dependency in the generated manifest. A complete file-template renderer and migration of every blueprint view are not generated automatically.

Version 13 should model independent capabilities instead of one misleading global frontend selector: rendering (SSR or API), interaction (HTMX, LiveView or Wasm islands), styling (the bundled design or an explicitly materialized CSS system), and templating (html! or a fully generated file-template path). A combination should appear only after its assets, routes and browser behavior have equivalent tests.

Full-stack versus headless API

  • Full-stack blueprints include server-rendered routes and relevant local tool integration.
  • --api generates a JSON-oriented Blank project without HTML view rendering; product blueprints reject it instead of silently retaining their HTML routes.

Generated code is application code. Review its routes, auth boundaries, database schema and dependencies before deployment.

Optional AI

Selecting AI adds rullst-ai and its provider-agnostic client foundation. Providers still require explicit credentials or the documented deterministic mock mode. Ollama can keep prompt traffic on infrastructure you operate, but “local” alone does not establish an air gap. Measure compile time, binary size, latency, quality and cost for the chosen provider and model.

Cache and queues

Without Redis, Rullst offers process-local memory cache and local queue options. With Redis selected, the generated project can configure the feature-gated Redis adapters.

There is deliberately no silent production fallback from Redis to memory: a distributed deployment that loses Redis must not pretend that independent process-local state is equivalent. Choose the backend explicitly and test its failure policy. Core realtime broadcast/presence remains in-process; Redis cache and queue support does not turn it into distributed WebSocket pub/sub.

Studio and Nexus

  • Studio’s supported v12 launcher is debug-only, binds to loopback and verifies direct peer information. It shows only data supplied by real local probes.
  • Nexus uses a local-development policy or explicit release credentials. A dependency or generated route does not remove the need for application RBAC.

Neither interface should be exposed publicly without an application-owned identity, authorization, TLS and network policy.

Omni packaging

Rullst uses a web-first, platform-enhanced model. The web application is the canonical universally reachable product; the server remains authoritative for domain rules, authentication, authorization, persistence, realtime and security. Omni may package that interface and later add scoped native capabilities, but it must not fork the business model or trust a client-side decision as authority.

cargo rullst make:omni scaffolds a Tauri-powered shell and commands for desktop and mobile targets. Deterministic generation pins the local Tauri CLI, requires an explicit validated backend URL and application-owned identifier for mobile, derives or validates product metadata, generates real platform icon assets and fails when an explicitly requested platform cannot be initialized. The packaged bootstrap exposes no remote IPC. A native navigation policy allows only Tauri’s local origin and the configured backend’s exact origin, leaving OAuth/external links for a reviewed system-browser/deep-link contract.

rullst::client_contract supplies one shared rullst.client v1 JSON boundary for browser, server and future platform-enhanced code. It negotiates positive versions, rejects unknown outer fields and oversized bodies, and carries typed payloads, correlation, optional mutation idempotency, server time and bounded failure codes. It deliberately has no user/tenant/role claim: authentication, authorization, domain validation and durable replay handling remain server work. This is a transport foundation for rich clients, not offline sync.

The opt-in native offline-sync profile builds a bounded state machine on that transport boundary. It keeps cached server records separate from queued local proposals, requires unique idempotency keys, uses explicit server revisions and cursors, isolates conflicts from automatic replay, and makes incremental/full resync transitions atomic within the state value. Its encrypted v1 snapshot is AES-256-GCM authenticated to an exact account and rotation-key id. The framework adds a static-dispatch foreground coordinator with request budgets, mandatory timeouts and cursor-progress checks, but does not choose client-wins, accept cached authorization, own a platform key, or silently write/contact an arbitrary filesystem, browser store or endpoint. Keychain/Keystore, atomic platform persistence, concrete authenticated HTTP, retry/background scheduling and later schema migrations remain application/platform decisions until dedicated adapters have evidence.

Path-aware workflows generate disposable applications and check desktop on Linux/macOS/Windows, an Android debug APK and an iOS simulator target. All three passed for commit 755fbd61933bed04369e0eb5de50b11275db5e3d.

That gate proves reproducible generation and simulator compilation only. It does not automatically mount offline synchronization, secure remote networking, platform signing, privacy declarations, physical-device behavior, store publication, native updater policy or every frontend profile. Push, biometrics, OS secure storage, deep links and offline state must be opt-in, least-privilege additions with platform tests. Treat the generated shell as application-owned packaging code that must be reviewed, signed and tested on each supported target.

Engineering invariants

Repository policy requires typed failures rather than production panic!, unwrap() or expect(), explicit SQL parameterization/sanitization, and the workspace test/Clippy/format gates. These are review and CI rules, not a claim that arbitrary application code or third-party dependencies cannot panic.

Rullst exposes Axum/Tower integration so applications can add middleware and routes directly. That escape hatch also means the final security and observability composition belongs to the application.

Practical starting points

  • Single-process CRUD application: use the generated Active Record, html! SSR and HTMX profile with memory cache.
  • Domain-heavy service on v12: introduce repositories per module with explicit transaction boundaries. In v13, select a Data Mapper / Repository project profile only after its blueprint has full generated-route and test parity.
  • Multi-process application: a configured shared cache/queue plus deployment tests; do not assume realtime is distributed.
  • Rich browser interaction: opt into LiveView or Wasm foundations only with application-owned integration and browser tests until a complete v13 profile exists.

Revisit these choices as evidence changes. Do not infer performance, security or availability from a wizard label.

Rullst AI: Developing with Autonomous Agents

Rullst is designed as an AI-native Rust framework. Here that term describes inspectable code-generation and explicit compile-time contracts; it is not a claim of historical priority or proof that an AI will produce correct code.

Some framework designs use runtime reflection, string-based dependency injection, or conventions that are harder to discover through static source inspection. Rullst instead favors visible types and generated Rust, while still retaining ordinary runtime state and framework abstractions where appropriate.

Rullst favors strong typing and compile-time diagnostics. The compiler catches type and ownership errors in generated code, but it cannot validate requirements, security intent, or business correctness; AI-generated changes still require review and tests.

For runtime LLM integrations, consult the machine-readable and documented provider capability matrix. Streaming, native tools, built-in deadlines/retries, and explicit cancellation are not implied by the existence of a provider adapter.

Local Rust tools use a separate guarded execution boundary with an exact allowlist, principal authorization, closed JSON schema, payload limits, call budget and mandatory audit sink. Destructive and financial calls also require a one-use approval bound to the exact payload.

1. Repository instructions for coding agents

The Rullst repository maintains a root AGENTS.md for contributors. The current cargo rullst new scaffold does not copy AGENTS.md, .ai-rules, or tool-specific instruction files into an application. Add reviewed project-local instructions yourself when using an autonomous coding tool; do not assume the framework’s repository policy applies to generated application code.

cargo rullst generate:ai-context can generate .llms.txt from recognized project dependencies and source directories. That snapshot can help a coding assistant navigate the application, but it is not an instruction-policy file and should be regenerated and reviewed after structural changes.

Example of the default content:

1. **Static Dispatch over Dynamic**: Prefer static dispatch (`impl Trait` or generics) over `dyn Trait` to ensure explicit concrete types for AI context tracking and optimization.
2. **Explicit APIs**: Avoid hidden state. Every controller and middleware should be explicit in its arguments.
3. **HTML Macros**: Boolean attributes in the `html!` macro must be quoted (e.g., `required="true"`).
4. **No Panics**: Never use `unwrap()` or `expect()` in production routes.

2. Rullst’s AI-Friendly Patterns

Rullst’s API is designed to give coding tools more explicit source landmarks; this can reduce ambiguity but does not measure or guarantee hallucination rates:

  • Explicit Routes: The routes![ ... ] macro is visual and delimited. The AI knows exactly where to add a new route without having to search across scattered files.
  • Rullst ORM: Based on Pure SQL (via SQLx) + Derives. AIs are much better at writing pure, correct SQL queries than learning an obscure query builder. Rullst takes advantage of this by using the database in a pure relational way.
  • Clean Background Workers: The queue system does not require complex global registration; you simply create an async function.

3. How to Get the Best Results

When instructing an AI to add a feature in Rullst:

  1. Ask it to read the instruction files that actually exist in your application and consult the matching version of the Rullst documentation.
  2. Say: “Create a new Controller following the pattern established in auth_controller.rs”. Today’s AIs are brilliant at pattern matching. Rullst provides the skeleton, the AI fills in the meat.
  3. Use the generators! Ask the AI to use cargo rullst make:controller in the terminal (if it’s an autonomous agent), ensuring the correct file structure.

Rullst Studio: local development control room

Rullst Studio is a developer-facing Axum dashboard. It can run as a standalone server bound to 127.0.0.1 (port 5555 by default) or its router can be mounted explicitly by an application.

Studio’s built-in boundary is deliberately limited to debug builds and verified loopback peers. It is not a shared-environment authentication system; do not expose raw subrouters publicly without application-level authentication, authorization, TLS, and network policy.

Generated Blog, Portfolio, LMS, ERP, and SaaS applications start the standalone server only in debug builds and link to http://127.0.0.1:5555. Release builds do not start it; runtime RULLST_ENV or legacy APP_ENV values cannot override that compile-time boundary.

Running Studio

The CLI can launch the local server:

cargo rullst studio

The library entry point is also available:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    rullst_studio::run_studio(5555_u16).await
}

Studio::new().into_router(LocalStudioAccess::loopback_only()) builds the same debug-only router for explicit composition. The serving stack must preserve Axum ConnectInfo<SocketAddr> or requests fail closed. Optional OpenAPI and queue views are enabled with with_openapi and with_horizon. with_cache opts a supported cache into metadata-only local inspection, while with_distributed_traces supplies the bounded store shared with a separately mounted push-only ingestion router.

Current views

  • /studio: data browser and dashboard shell.
  • /studio/radar: runtime probes and recorded spans. Process CPU sampling is implemented for Linux and Windows and the KPI cards refresh from /api/radar every two seconds. The first delta-based CPU sample, unsupported platforms, and disconnected probes display Unavailable; Studio does not synthesize a healthy value.
  • /studio/security: counters and events emitted by the in-process security store. Audit-chain integrity displays Unavailable until a verifier is connected.
  • /studio/capital: the in-process revenue view; it is not an accounting ledger.
  • /studio/traces: local spans plus authenticated attribute-free distributed records and explicit slow/repeated SQL-label heuristics.
  • /studio/cache: metadata-only view of an explicitly supplied Memory/Redis cache, using opaque identifiers and one-entry invalidation.
  • /studio/migrations, /studio/ai, /studio/env, /studio/features, and /studio/er: development tools for their corresponding subsystems.

Some panels poll HTTP JSON endpoints and the request logger uses SSE. The current crate does not promise a separate WebSocket telemetry transport or zero runtime overhead.

Tooling boundaries

  • The data browser reads, searches, and paginates allowlisted SQLx identifiers. Inside the verified debug-loopback/same-origin boundary, it may edit one primitive non-key value or delete one complete-primary-key-selected row. Inputs are bounded and parameterized; exact deletion confirmation is required, backend-specific types remain read-only, and anything other than exactly one affected row fails. SQLite, PostgreSQL, MySQL, and MariaDB run separate executable contracts. This does not supply application tenant/RBAC, audit history, rollback, or a shared-production database administrator.
  • Swagger UI appears only when the application supplies its OpenApi document with Studio::with_openapi; Studio does not reverse-engineer arbitrary Axum routes.
  • The request SSE records method, URI, status and latency. It deliberately does not capture bodies or headers, which commonly contain credentials and PII.
  • The jobs view lists the bounded snapshot exposed by a supplied queue. SQLite deletes successful rows by default; an application can explicitly select Queue::sqlite_with_completed_history for bounded, transactionally pruned completion history and can purge that history from Studio. Retained payloads require host-controlled access and retention policy. Other drivers expose only the inspection/history contract they implement.
  • The ER view inspects SQLite, PostgreSQL, MySQL, or MariaDB metadata with bound lookup values and normalizes Mermaid identifiers. An unconfigured or unsupported source remains visibly unavailable.
  • The feature-flags page changes the database table used by DbFeatureDriver. A successful toggle invalidates already-warm drivers in the same process; other processes and direct writers converge by TTL unless the host distributes an invalidation signal.
  • The environment page redacts values by default and adds only a safe projection of process-global RullstConfig; URLs, filesystem paths, secrets, cookies and credentials are omitted.
  • Cache inspection returns at most 100 UI rows containing an opaque keyed identifier, value byte length and remaining TTL. Values and exact logical keys are never rendered, bulk flush is absent, and individual invalidation requires the verified local mutation marker. Custom cache drivers remain unavailable until they implement Core’s bounded metadata contract.

Telemetry contract

Studio reads runtime state exposed by RadarSnapshot, SpanCollector, the security store, queues, and configured database connections. A counter means only that the corresponding instrumentation path emitted it; it is not proof that all traffic passed through that control. Missing sources must remain visibly unavailable.

Remote producers do not connect to the viewer. The application separately mounts TraceIngestor::router, distributes a 32–128-byte TraceIngestionKey, and uses TraceBatchSigner for the exact body and four headers. Each ingestor binds one exact producer name to one key; multiple producers use separate endpoints/keys over the same store. The route accepts 1–128 closed v1 spans under 128 KiB, verifies HMAC-SHA256 plus a 60-second timestamp and one-time nonce, then commits to a bounded process-local store idempotently. It contains no Studio read or admin route. TLS/network policy, key custody and rotation, producer authorization, clock synchronization, label redaction, durable storage and OTLP integration remain deployment work.

Production boundary

Studio is an optional crate. Its supported run_studio and Studio::into_router paths reject credential-free use in release builds, but consumers should still exclude it from production features unless they are implementing and testing a separate authenticated administrator boundary. Built-in shared production access remains roadmap work, not a password environment-variable promise.

Rullst Nexus: Explicit Admin CMS

Rullst Nexus is a server-rendered administrative CMS for explicitly registered Rullst models.

NexusModel metadata defines the tables, fields, and widgets available in the panel. Rullst builds CRUD, search, pagination, and batch routes from that registration; it does not discover an arbitrary database schema automatically.

Derive and register a model

The Nexus derive generates NexusModel metadata for named-field structs. It infers booleans, numbers, dates and ordinary text; semantic widgets that Rust’s type alone cannot reveal are selected explicitly:

#![allow(unused)]
fn main() {
use rullst::db::{FromRow, Nexus, Orm};

#[derive(Debug, Clone, FromRow, Orm, Nexus)]
#[orm(table = "users")]
#[nexus(label = "Users", icon = "👥")]
pub struct User {
    pub id: i32,
    pub name: String,
    #[nexus(kind = "email")]
    pub email: String,
    #[nexus(kind = "textarea", label = "Biography")]
    pub bio: String,
    #[nexus(kind = "enum", options = "invited, active, suspended")]
    pub status: String,
    pub is_active: bool,
}
}

id is the default primary key. Use #[nexus(primary_key)] on a field or #[nexus(primary_key = "uuid")] on the struct for another key. Field options also include label, hidden, readonly, and the text, textarea, email, url, number, boolean, date, datetime, password, json, and enum widget kinds. Implementing NexusModel manually remains available when an application needs metadata that cannot be derived.

Then select an explicit access policy in your routing file (usually src/lib.rs or src/main.rs) and mount the resulting router:

let nexus_auth =
    rullst::nexus::NexusAuthPolicy::local_development_or_basic_from_env()?;
let nexus = rullst::nexus::Nexus::new()
    .with_auth_policy(nexus_auth)
    .with_brand("SaaS Admin")
    .register::<models::user::User>()
    .try_build()?;

// ... and add it to the final router:
let router = router.nest_axum("/nexus", nexus);

The helper is intentionally asymmetric: debug builds allow only requests whose ConnectInfo peer is loopback; release builds load and validate NEXUS_ADMIN_USERNAME and NEXUS_ADMIN_PASSWORD. Missing connection metadata is denied, and neither RULLST_ENV nor legacy APP_ENV can turn credential-free access on in a release binary. Applications can call basic_from_env() directly in debug when testing the production authentication flow.

Tenant-scoped administration

Use an explicit tenant column when a registered model contains tenant-owned rows. The column must be a text, non-primary-key field. The derive makes it hidden and read-only so browser form data cannot choose the tenant:

#![allow(unused)]
fn main() {
use rullst::db::{FromRow, Nexus, Orm};

#[derive(Debug, Clone, FromRow, Orm, Nexus)]
#[orm(table = "projects", tenant = "organization_id")]
pub struct Project {
    pub id: i32,
    pub organization_id: String,
    pub name: String,
    pub active: bool,
}
}

Authentication middleware must resolve membership and install a trusted rullst::security::TenantContext. Do not construct it directly from X-Tenant-ID, a query parameter or another client assertion. Nexus applies the exact scope to list/search/edit/create/update/delete and batch routes; missing context denies a scoped model. A model without the attribute remains global by design.

Require transaction-coupled audit

Install the fixed audit schema as an explicit deployment step, then enable the policy on the panel:

rullst::nexus::create_nexus_audit_table().await?;

let nexus = rullst::nexus::Nexus::new()
    .with_auth_policy(nexus_auth)
    .register::<Project>()
    .with_required_audit()
    .try_build()?;

Each successful mutation and its minimized rullst_nexus_audits row commit in one database transaction. Audit failure rolls the mutation back. The record contains actor, optional tenant, table/action, optional known key, affected-row count, committed outcome, optional bounded request ID, timestamp and format version. verify_nexus_audit_table() checks deployment readiness and recent_nexus_audits() reads at most 1,000 newest rows, optionally tenant filtered; the application must authorize that export separately.

The table is neither append-only nor protected from a database administrator. It does not persist denied attempts, and automatically assigned create keys are not recovered uniformly across all supported SQL dialects. Protect database permissions and send records to an independently operated immutable sink when that property is required.

👤 Example: Dynamic Profile Settings in Blueprints

Starter blueprints like Portfolio use explicit Nexus metadata to expose single-row or multi-row site configuration settings (such as developer name, title, bio, email, personal website, avatar photo, and social links).

#![allow(unused)]
fn main() {
use rullst::db::{Orm, FromRow, Nexus};

#[derive(Debug, Clone, FromRow, Orm, Nexus)]
#[orm(table = "profile")]
pub struct Profile {
    pub id: i32,
    pub name: String,
    pub title: String,
    pub subtitle: String,
    pub email: String,
    pub website: String,
    pub avatar_url: String,
    pub github_url: String,
    pub linkedin_url: String,
}
}

When registered in Nexus:

let nexus_auth =
    rullst::nexus::NexusAuthPolicy::local_development_or_basic_from_env()?;
let nexus = rullst::nexus::Nexus::new()
    .with_auth_policy(nexus_auth)
    .with_brand("Portfolio Admin")
    .register::<models::profile::Profile>()
    .try_build()?;

Administrators can edit the registered profile fields at /nexus. A blueprint that reads those fields on each request can show the persisted values without a code change or redeployment; cache policy remains application-owned.

Batch deletion is available for every registered model. Batch deactivation is shown only when the model declares a writable Boolean is_active or active field; Nexus never guesses which arbitrary status value means inactive.

Benefits of Nexus

  1. Small Front-end Surface: Nexus renders responsive tables, forms and actions with server-side HTML and HTMX.
  2. Fail-closed Construction: Nexus cannot build without a selected access policy. Being in the same binary is not itself a security guarantee; the application still owns TLS, trusted proxies, roles, ownership, field policy and database permissions.
  3. Server-side Field Policy: hidden and readonly metadata improve the UI, while authorization and write restrictions are also enforced on the server.

In a generated debug application, open /nexus from the same machine. In a release deployment, configure strong unique credentials and the verified TLS boundary before exposing the route.

Rullst Capital: billing and fiscal boundaries

Vision preserved: capabilities removed from the usable-today contract are still evaluated item by item in the capability ledger, including whether each one is worth implementing and why.

rullst-capital provides billing abstractions, revenue metrics, payment-provider adapters, payout helpers, and verified webhook plumbing. Provider capabilities are not uniform: consult the adapter API and its tests before depending on a particular checkout, refund, payout, or webhook operation.

RevenueDashboardManager is a bounded process-local presentation source. Applications call update_metrics with values reconciled from their durable billing database and call record_event after a verified webhook path. Recording an event deliberately does not invent a plan price, fee, currency or subscriber count from its event name.

Payment providers

Initialize only the provider required by the application and treat credentials as deployment secrets. Empty credentials are configuration errors for live operations. Credentials deliberately prefixed with mock_ select deterministic offline behavior where that adapter documents support for it.

Every reviewed live method uses the same pooled outbound client with a five-second connect timeout, twenty-second whole-request timeout, disabled redirects and ambient proxy discovery, and one-MiB JSON limit. Returned checkout locations must be bounded absolute HTTPS URLs without credentials or fragments. CapitalError::Provider exposes redacted static provider/operation labels, failure kind, optional HTTP status and bounded numeric Retry-After. Its permanent/transient/rate-limited class is evidence for application policy, not permission to repeat a mutation. Only retry after proving that the exact operation forwards a persisted idempotency key and retaining reconciliation.

Immediate charges without raw payment data

Billable::charge_with accepts only a provider-tokenized customer and payment method. Amounts are integer minor units and every attempt needs an application-owned idempotency key. The reviewed live adapter is Stripe Payment Intents; other billing adapters fail with UnsupportedOperation instead of pretending to charge.

#![allow(unused)]
fn main() {
use rullst_capital::{Billable as _, CapitalError, StripeProvider};

async fn collect_order<Account>(
    account: &Account,
    stripe: &StripeProvider,
) -> Result<(), CapitalError>
where
    Account: rullst_capital::Billable + Sync,
{
    let receipt = account
        .charge_with(
            stripe,
            4_990, // BRL 49.90 in cents
            "BRL",
            "cus_provider_owned",
            "pm_provider_tokenized",
            "order_42-attempt_1",
        )
        .await?;
    if receipt.is_succeeded() {
        // Reconcile durable order state; do not grant access from this alone.
    }
    Ok(())
}
}

The application must establish the customer’s authority over both provider IDs, retain the key with its order, reconcile signed webhooks and handle mandate/SCA rules. Empty/mock_* credentials produce a deterministic receipt with the distinct non-success ChargeStatus::Mock; it must never grant access or be booked as revenue. This API has no raw-card field and never guesses currency.

Provider-specific metered usage

Use the static-dispatch MeteredBillingProvider boundary for new code. StripeMeterEvent requires the authoritative cus_* customer, configured meter event name, positive quantity, timestamp and a unique visible identifier; the adapter sends the default stripe_customer_id/value payload and binds all of those fields in the accepted response. LemonSqueezyUsageRecord requires the provider’s numeric subscription-item ID plus the explicit Increment or Set action and binds item/quantity/action from the JSON:API response.

Both responses are capped at one MiB and offline credentials return a deterministic UsageStatus::Mock. Stripe provides only rolling identifier deduplication. The reviewed Lemon request has no provider event-key field, so the application must atomically claim event_key() in a durable outbox before sending. It must also configure the matching aggregation, reconcile invoices and derive quotas/entitlements from authoritative state. The old uniform BillingProvider::report_usage remains source-compatible for mocks but fails closed in live Stripe/Lemon configurations instead of guessing required fields.

Coupons and relative trial extensions

CouponCode validates/redacts the provider coupon identifier, while a statically dispatched SubscriptionHandle applies it. Stripe uses the current expanded discounts[0][coupon] update and binds the response to the requested subscription/coupon. Lemon Squeezy codes are checkout-only; Lemon and adapters without reviewed subscription-discount contracts return UnsupportedOperation for live credentials instead of silently succeeding.

handle.extend_trial(15) resolves a bounded 15-day expiration from the current UTC clock. Retryable workers should persist the command creation time and call extend_trial_days_at(15, command_created_at); this emits the same absolute expiration on every attempt. set_trial_end is available for explicit reconciliation. Stripe and Lemon Squeezy have bounded protocol/response-binding tests for trial updates. Authorization, command serialization, billing-cycle policy, webhook reconciliation and live account acceptance remain host/release responsibilities. See the billing tutorial.

Shared subscriptions and strict resource quotas

One Team/Workspace model can own Billable and its tier policy while every authorized member uses the same bounded BillingSubject. Derive that subject from trusted TenantContext, never from a client-selected owner ID. Billable::quota_request derives the limit from tier_limit; QuotaGate reserves before invoking a create operation, suppresses an exact retry and compensates an ordinary callback error.

InMemoryQuotaStore provides deterministic process-local behavior. Enable quota-sql directly or capital-quota-sql on the umbrella crate for a durable unique-claim and conditional-counter implementation on SQLite, PostgreSQL, MySQL and MariaDB. Use reserve_with_transaction and perform the domain insert in that same transaction whenever quota and creation must commit atomically. The complete tutorial shows this path and its replay semantics.

The application still owns membership, authoritative tier/webhook state, migrations, reconciliation of abandoned standalone reservations and adapters for Turso or non-relational stores. Rullst does not intercept writes performed outside the explicit quota boundary.

Payment-bound invoice PDF and delivery

Enable the umbrella capital-mail feature (or Capital’s invoice-pdf and Mail’s capital-invoice features separately). Invoice::bind_succeeded_charge rejects non-final/mock receipts and any recipient, minor-unit total or currency mismatch. PaidInvoiceDelivery::prepare then creates escaped HTML, a bounded native PDF attachment and a message that has already passed Mail pre-flight. It can use the configured facade, a tenant route or an explicit static driver.

The returned stable delivery_key is for a unique durable outbox record owned by the application. Rullst does not infer a webhook, claim that key atomically, guarantee attachment support at every provider or promise exactly-once delivery. See the SaaS billing tutorial.

Webhook endpoints must use the Capital verification middleware. Its Axum and opt-in Actix adapters call the same canonical verifier. For supported protocols it performs cryptographic verification, freshness checks, a two-megabyte body limit, and bounded replay protection before the application receives a normalized event. A webhook route may receive a narrowly scoped CSRF exemption only when this verifier remains mandatory on that exact route. See the payment guide for Actix setup.

The default store is process-local. The opt-in webhook-sql feature persists bounded provider-scoped payload digests or stable event IDs across SQLite, PostgreSQL, MySQL, and MariaDB processes. SQL-backed middleware is a replay firewall that claims before dispatch, not exactly-once delivery. For an atomic relational business transition, use the verified provider event ID with check_and_record_event_key_with_transaction in the domain transaction and do not pre-claim it through SQL middleware. Cross-system effects still require an outbox, idempotent consumers, and reconciliation.

#![allow(unused)]
fn main() {
use axum::{routing::post, Router};
use rullst_capital::verify_webhook;

async fn billing_webhook() {
    // Read the verified event inserted by the middleware in real handlers.
}

let router: Router = Router::new()
    .route("/webhooks/billing", post(billing_webhook))
    .layer(axum::middleware::from_fn(verify_webhook));
}

NFS-e Nacional: bounded homologation preparation

The fiscal module can construct a strict ordinary-service DPS 1.01 subset, validate it against checksum-pinned official schema sources with the one documented production regex normalization, sign its infDPS/@Id with a matching RSA key/certificate from PKCS#12, independently verify the local XMLDSig, and construct the bounded rustls mTLS client. These local properties do not constitute an authorization from the Brazilian National NFS-e service.

  • NfseEnvironment::Mock returns FiscalResponseKind::OfflineMock, status MOCK_NOT_AUTHORIZED, and is_officially_authorized() == false.
  • NfseEnvironment::Homologation and NfseEnvironment::Production fail closed with FiscalError::Unsupported.
  • sign_dps_xml rejects malformed, duplicate-ID, already-signed, non-RSA, and mismatched key/certificate inputs instead of returning partial signature XML.
  • NfseIssueRequest verifies the embedded DPS signature, produces the exact deterministic dpsXmlGZipB64 JSON object and parses bounded signed success or structured rejection material without performing network I/O.
  • transmission and the external homologation gates remain deliberately disconnected from the network path.
#![allow(unused)]
fn main() {
use rullst_capital::fiscal::{
    issue_nfse_direct, FiscalCertificate, FiscalCustomer, FiscalEmitter,
    FiscalResponseKind, NfseDps, NfseEnvironment,
};

async fn offline_preview(
    emitter: &FiscalEmitter,
    customer: &FiscalCustomer,
    dps: &NfseDps,
) -> Result<(), rullst_capital::fiscal::FiscalError> {
    let unused_certificate = FiscalCertificate::offline_mock();
    let response = issue_nfse_direct(
        emitter,
        customer,
        dps,
        &unused_certificate,
        NfseEnvironment::Mock,
    )
    .await?;

    assert_eq!(response.kind, FiscalResponseKind::OfflineMock);
    assert!(!response.is_officially_authorized());
    Ok(())
}
}

The offline protocol boundary is intentionally separate from transport:

#![allow(unused)]
fn main() {
use rullst_capital::fiscal::NfseIssueRequest;

fn prepare(signed_dps: &str) -> Result<Vec<u8>, rullst_capital::fiscal::FiscalError> {
let request = NfseIssueRequest::try_from_signed_dps(signed_dps)?;
let exact_json_body = request.to_json()?;
Ok(exact_json_body)
}
}

An application may retain this material for a reviewed homologation fixture, but the Rullst client does not send it. parse_response accepts only the documented 201/400/403/500 issuance outcomes, applies four-MiB/cardinality/text limits, binds the selected environment to the signed infDPS/tpAmb, and never turns a rejection or unsigned/tampered XML into authorization.

The same nfse feature exposes FiscalCommandJournal: a bounded, single-active-writer HMAC-chained file that synchronizes a prepared command before a caller-owned transport, records one bound terminal response, suppresses exact replay, and recovers minimized pending descriptors after restart. It stores no XML, access key, processing message, provider body, or certificate. The host must retain the actual request/outbox and the journal checkpoint separately, protect and rotate the 32-byte key, enforce an exclusive writer, and own reconciliation, retry, retention, and backup.

Live issuance remains disabled until full certificate/emitter and ICP-Brasil policy, deployment of the local journal and authoritative request/outbox, retained official fixtures, real A1 restricted-environment tests, independent review, and official end-to-end homologation are complete. Follow the NFS-e homologation-preparation tutorial and never account an offline fixture as an issued invoice.

Operational checklist

  • Derive tenant identity from an authenticated context, not an arbitrary client header.
  • Keep webhook secrets non-empty, rotate them, and retain replay state according to the provider contract.
  • Reconcile provider events idempotently in the application database.
  • Treat Studio revenue panels as observability views, not an accounting ledger.
  • Verify every adapter capability in a sandbox before enabling live traffic.

Integrating AI into Rullst

rullst-ai provides guarded adapters for OpenAI, Anthropic, Gemini, DeepSeek, Ollama, and explicitly configured OpenAI-compatible local/cloud endpoints. The high-level AiClient applies prompt-injection heuristics and PII masking before dispatch. Those controls reduce known risks; passing them does not prove that a prompt or model response is safe or correct.

Start with the provider capability matrix. It separates implemented transport paths from model-dependent behavior and lists unsupported streaming, tool, timeout, retry, and cancellation boundaries.

1. Enable the AI facade

[dependencies]
rullst = {
    version = "12.0.0-rc.1",
    default-features = false,
    features = ["ai"]
}
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Use the exact published v12 version being evaluated. The RC value above is a planned prerelease and must not be used before it exists on crates.io.

2. Create a guarded client

#![allow(unused)]
fn main() {
use rullst::ai::{AiClient, providers::openai::OpenAiProvider};

fn ai_client() -> Result<AiClient, std::env::VarError> {
    let api_key = std::env::var("OPENAI_API_KEY")?;
    Ok(AiClient::new(OpenAiProvider::new(api_key)))
}
}

Empty and mock_* keys intentionally select deterministic offline behavior. Requiring the environment variable, as above, prevents a live deployment from silently becoming a demo. Tests can construct OpenAiProvider::new("") explicitly when offline behavior is desired.

Other built-in constructors are available under:

  • providers::anthropic::AnthropicProvider;
  • providers::gemini::GeminiProvider;
  • providers::deepseek::DeepSeekProvider;
  • providers::ollama::OllamaProvider.

For a local server exposing an OpenAI-compatible endpoint, declare only the request shapes verified for the exact model:

#![allow(unused)]
fn main() {
use rullst::ai::{
    AiClient,
    providers::openai_compatible::{
        OpenAiCompatibleCapabilities, OpenAiCompatibleProvider,
    },
};

fn local_client() -> Result<AiClient, rullst::ai::AiError> {
    let provider = OpenAiCompatibleProvider::try_local(
        "http://127.0.0.1:8080/v1",
        "my-local-model",
    )?
    .with_capabilities(
        OpenAiCompatibleCapabilities::chat_only()
            .with_embeddings()
            .with_json_mode(),
    )
    .try_with_embedding_model("my-embedding-model")?;
    Ok(AiClient::new(provider))
}
}

try_local accepts HTTP only on literal loopback IPs and sends no authorization header. Use try_local_with_bearer for an authenticated loopback server or try_cloud(https_base_url, api_key, model) for a Bearer-authenticated cloud endpoint. None of these constructors discovers model capabilities; false declarations become provider errors, while omitted capabilities fail locally with UnsupportedCapability. A non-compatible API implements the public AiProvider trait instead.

3. Call it from an Axum handler

This bounded handler rejects oversized input before invoking the client and does not expose the provider’s full error details to the HTTP caller:

#![allow(unused)]
fn main() {
use rullst::{Server, ai::AiClient};
use rullst::web::axum::{
    Json, Router,
    extract::State,
    http::StatusCode,
    routing::post,
};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct ChatPrompt {
    prompt: String,
}

#[derive(Serialize)]
struct ChatResponse {
    answer: String,
}

async fn chat(
    State(client): State<AiClient>,
    Json(body): Json<ChatPrompt>,
) -> Result<Json<ChatResponse>, (StatusCode, &'static str)> {
    if body.prompt.len() > 8_192 {
        return Err((StatusCode::PAYLOAD_TOO_LARGE, "prompt is too large"));
    }

    let answer = client
        .prompt(&body.prompt)
        .await
        .map_err(|_| (StatusCode::BAD_GATEWAY, "AI request failed"))?;
    Ok(Json(ChatResponse { answer }))
}

async fn serve(client: AiClient) -> Result<(), rullst::ServerError> {
    let app = Router::new()
        .route("/api/chat", post(chat))
        .with_state(client);
    Server::new(app.into()).run(3000).await
}
}

Production applications should authenticate and rate-limit this route, bind tenant identity from authenticated state, cap response sizes, and record an audit event without logging prompts or secrets verbatim.

4. Inspect capabilities before optional operations

#![allow(unused)]
fn main() {
use rullst::ai::{AiClient, EgressFetcher, EgressPolicy, LocalImagePolicy, providers::openai::OpenAiProvider};
async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = AiClient::new(OpenAiProvider::new("mock_local"));
let capabilities = client.capabilities();

if capabilities.vision {
    // Bytes have already crossed the application's own admission boundary.
    let png = b"\x89PNG\r\n\x1a\n\x00";
    client.prompt_with_image("Describe this image", png).await?;

    // Local files must remain under an exact canonical allowlisted root.
    let local = LocalImagePolicy::new(["./private-uploads"])?;
    client
        .prompt_with_image_file("Describe this image", "./private-uploads/photo.png", &local)
        .await?;

    // URLs require an explicit HTTPS egress allowlist and resource budget.
    let remote = EgressPolicy::strict()
        .with_allowed_hosts(["images.example.com"])?
        .with_max_response_bytes(2 * 1_024 * 1_024)?;
    client
        .prompt_with_image_url(
            "Describe this image",
            "https://images.example.com/photo.png",
            &EgressFetcher::new(remote),
        )
        .await?;
}
Ok(())
}
}

Capability inspection prevents avoidable requests but is not a substitute for handling UnsupportedCapability and upstream model errors. Configuration can select a model that supports less than its provider transport. File and URL helpers check capability and guard the text before I/O, cap images at 10 MiB, sniff JPEG/PNG/WebP/GIF bytes and require a supplied remote Content-Type to match. The host must authorize the source and protect allowed local directories; Rullst does not infer tenant ownership from a path or URL.

5. Request structured output

json_prompt requests parseable JSON. It does not claim JSON Schema enforcement. Use structured_prompt_with_schema only when the reported provider capability and configured model support it:

use rullst::ai::StructuredOutputSchema;

let schema = StructuredOutputSchema::new(
    "answer",
    serde_json::json!({
        "type": "object",
        "properties": {"answer": {"type": "string"}},
        "required": ["answer"],
        "additionalProperties": false
    }),
)?;

let answer: serde_json::Value = client
    .structured_prompt_with_schema("Summarize the incident", &schema)
    .await?;

Validate business rules after deserialization. Schema-conforming model output can still be false, malicious, stale, or unauthorized.

6. Streaming and tools

The built-in v12 provider transports do not expose token streaming. Rullst can host ordinary Axum SSE responses, but an application that uses a third-party streaming SDK owns its authentication, guardrails, backpressure, deadlines, cancellation, error mapping, and dependency lifecycle. Do not present that escape hatch as native rullst-ai streaming.

ToolRegistry stores local tools but is not wired to provider function calling. Its guarded execution API requires an exact allowlist, principal authorization, closed JSON validation, size/call limits and an audit sink. Destructive and Financial tools additionally require a one-use human approval bound to the exact JSON payload. Treat model output as untrusted input; the application still authenticates the principal/approver, enforces domain ownership and supplies durable production auditing.

7. RAG boundary

Rullst supplies prompt construction and an in-memory vector index. Applications must still enforce document authorization before retrieval, prevent SSRF in any fetcher, bound document and prompt sizes, identify tenant provenance, and avoid sending secrets to a provider. Similarity is a ranking signal, not an access control decision.

8. Durable chat memory

Enable the dedicated SQL memory feature when the application wants the framework-owned fixed schema rather than generated models:

rullst = {
    version = "12.0.0-rc.1",
    default-features = false,
    features = ["ai-sql-memory"]
}

The same adapter supports SQLite, PostgreSQL, MySQL, and MariaDB URLs:

#![allow(unused)]
fn main() {
use rullst::{
    ai::{
        AiClient, ChatMemoryConfig, ConversationId, SqlChatMemory, StatefulChat,
        providers::openai::OpenAiProvider,
    },
    security::TenantContext,
};

async fn chat_service(
    database_url: String,
    api_key: String,
) -> Result<
    (StatefulChat<SqlChatMemory>, TenantContext, ConversationId),
    Box<dyn std::error::Error>,
> {
    let memory = SqlChatMemory::connect(database_url, ChatMemoryConfig::default()).await?;
    memory.prepare_schema().await?;
    let service = StatefulChat::new(
        AiClient::new(OpenAiProvider::new(api_key)),
        memory,
    );
    let tenant = TenantContext::try_new("tenant-42")?;
    let conversation = ConversationId::try_new("support:case-7")?;
    service.ensure_conversation(&tenant, &conversation).await?;
    Ok((service, tenant, conversation))
}
}

service.send(&tenant, &conversation, text).await loads only the configured recent message pairs, applies the ordinary AI guardrails, and stores the user and assistant messages in one transaction. If another process committed from the same observed revision, the slower StatefulChat call receives StatefulChatError::Memory(ChatMemoryError::RevisionConflict). Decide explicitly whether the UI asks the user to retry; the library will not repeat a potentially billable provider call.

The table stores raw message text. Production code must authenticate conversation ownership inside the selected tenant, decide encryption and key management, implement retention/erasure and backup policy, audit provider use without logging secrets, and manage schema changes through its release process. Use cargo rullst make:chat-session instead when you need application-owned models/migrations or the Turso-primary profile.

AI-friendly architecture and local model endpoints

Rullst favors explicit types, conventional file locations, bounded macros and generated .llms.txt context. These choices can make a project easier for a human or coding assistant to navigate, but the repository has no controlled evidence for a universal token-saving percentage. Prompt size depends on the task, tool, repository state and model.

What “AI-native” means here

  • routes! and html! provide recognizable syntax boundaries.
  • Public APIs prefer concrete types and static dispatch where practical.
  • cargo rullst make:* generates conventional, inspectable source files.
  • cargo rullst generate:ai-context records a compact structural summary.
  • External provider integrations have deterministic offline behavior for empty or mock_* credentials.

This does not guarantee that an assistant understands the application, chooses the right edit, uses fewer tokens or produces secure code. Keep source review, tests and application threat models authoritative.

The AI maintainability and project-building roadmap records the measurable post-v12-RC work needed to improve generated agent instructions, bounded task-oriented context and reproducible model evaluations. Those planned evaluation profiles are not current compatibility guarantees.

Ollama through AiClient::auto

The high-level client recognizes OLLAMA_HOST and OLLAMA_MODEL:

export OLLAMA_HOST="http://127.0.0.1:11434"
export OLLAMA_MODEL="llama3"
#![allow(unused)]
fn main() {
use rullst_ai::ai::AiClient;

async fn example() -> Result<(), rullst_ai::ai::AiError> {
let client = AiClient::auto()?;
let response = client.prompt("Summarize this bounded input").await?;
println!("{response}");
Ok(())
}
}

AiClient::auto() also recognizes the built-in cloud-provider API-key variables. With no configured provider it selects a deterministic offline mock; that fallback is not a live model.

OpenAI-compatible endpoints

An OpenAI-compatible server is configured explicitly rather than inferred from an arbitrary environment variable:

#![allow(unused)]
fn main() {
use rullst_ai::ai::{
    AiClient,
    providers::openai_compatible::{
        OpenAiCompatibleCapabilities, OpenAiCompatibleProvider,
    },
};

let provider = OpenAiCompatibleProvider::try_local(
    "http://127.0.0.1:1234/v1",
    "configured-model-name",
)?
.with_capabilities(OpenAiCompatibleCapabilities::chat_only());
let client = AiClient::new(provider);
Ok::<(), rullst_ai::ai::AiError>(())
}

The same adapter can be used with local runtimes such as llama.cpp server, LocalAI, LM Studio, or vLLM when the exact installed version and configuration expose the request shapes declared in Rullst. Those product names are examples, not compatibility certification; check the runtime’s API and model documentation. A server with a different protocol implements the public AiProvider contract instead.

Compatibility must be tested for the methods the application uses. An endpoint may implement chat while differing on embeddings, vision, JSON Schema, errors or streaming. Optional request shapes are disabled until explicitly declared. Use try_local_with_bearer for an authenticated loopback server and try_cloud for HTTPS/Bearer cloud endpoints. Consult the provider capability matrix.

Privacy boundary

Using a loopback endpoint can avoid sending model requests to a cloud provider, but it does not prove an air gap or zero leakage. The model runtime, host network, proxy variables, logs, tracing, crash dumps and application code still determine the real data path. The compatible adapter ignores ambient proxies and redirects, but that is only one transport boundary. The built-in prompt and PII checks are bounded heuristics, not authorization or a complete data-loss-prevention guarantee.

AI Maintainability and Project-Building Roadmap

Rullst aims to be understandable to humans and coding assistants without making model-independent quality claims. Explicit Rust types, conventional project structure and generated context can reduce ambiguity, but no framework can guarantee that an arbitrary model will produce correct, secure or maintainable software.

This document records the measurable work required to make Rullst excellent for AI-assisted framework maintenance and application development. It is a post-v12-RC roadmap, not a v12 release gate and not a description of capabilities that have already shipped.

Current foundation

The repository already provides useful controls:

  • AGENTS.md defines the framework architecture, coding invariants, security boundaries, validation commands and release order.
  • spec.md is the architectural Single Source of Truth.
  • capability-status.md and the quality-scorecard.md separate implementation claims from engineering evidence.
  • cargo rullst make:* emits conventional, inspectable Rust source instead of hiding application behavior behind runtime reflection.
  • cargo rullst generate:ai-context emits .llms.txt with selected application source and dependency context.
  • Generated-project tests, compile-fail tests, deterministic provider mocks and the workspace validation gates catch classes of mistakes independently of the assistant that proposed a change.

These controls are a good foundation. They do not yet prove that a smaller or less capable model can maintain the framework or build a complete application at a defined quality level.

Known limitations

The current AI context generator primarily concatenates Cargo.toml and Rust files from selected conventional directories. That format can become noisy, does not rank information by task relevance and does not provide a complete project contract for routes, configuration, migrations, tests, authorization or operational commands.

The framework documentation is extensive. Breadth helps difficult work, but a model with weaker retrieval or reasoning can select an obsolete example, miss a more specific invariant or consume too much context unless it receives a short task-oriented map first.

Maintaining framework internals is also substantially harder than building an application from stable public APIs. Cross-crate feature unification, procedural macros, database dialects, authentication, cryptography and release engineering require stronger review and broader gates than ordinary application CRUD.

Post-v12-RC workstreams

1. Project-specific agent instructions

Generate a concise AGENTS.md for each new application, derived from the selected blueprint and feature set. It must describe:

  • the chosen database, rendering mode and enabled Rullst subsystems;
  • the canonical locations for routes, models, controllers, policies, migrations, jobs and tests;
  • the exact format, lint and test commands for that application;
  • mandatory ownership, CSRF, headers, WAF and secret-handling boundaries;
  • which generated files are application-owned and which commands may refresh them; and
  • links to version-matched Rullst documentation.

Acceptance requires snapshot tests for every supported blueprint and a generated-project compile gate. The generated document must not claim that an optional integration is active merely because its crate is available.

2. Structured and bounded AI context

Evolve generate:ai-context from a source concatenator into a deterministic, versioned project map. The output should present summaries and file paths before including bounded source excerpts. It should cover:

  • dependency and feature selections;
  • routes and their authentication/ownership policies;
  • models, relationships and migrations;
  • controllers, middleware, jobs and external-provider boundaries;
  • configuration keys by name, never secret values;
  • tests and the commands that exercise each subsystem; and
  • freshness metadata sufficient to detect stale generated context.

The generator must enforce an explicit size budget, stable ordering, secret and binary exclusions, path confinement and deterministic output. Large projects should receive an index plus task-selectable context shards rather than one unbounded prompt payload.

3. Golden application tasks

Create small, executable reference tasks that represent common real work:

  1. add a validated CRUD resource with tenant ownership;
  2. add session authentication and a role-protected route;
  3. add a migration and repository query without SQL injection;
  4. enqueue an idempotent background effect through an outbox;
  5. verify a signed webhook and reject replay or invalid signatures;
  6. add a provider integration with a deterministic offline mock;
  7. diagnose and repair a deliberately broken generated application; and
  8. perform an assisted framework upgrade and verify rollback.

Each task needs a fixed starting fixture, an executable acceptance suite, security-negative cases and a reference solution. Tutorials alone are not evidence; the fixture must compile and its assertions must prove the behavior.

4. Reproducible AI evaluation harness

Evaluate candidate assistants using the same repository revision, task fixture, tool permissions, time budget and acceptance tests. Record at least:

  • functional correctness and hidden-test pass rate;
  • formatting, strict Clippy and test results;
  • security-invariant violations;
  • unsupported or hallucinated APIs and dependencies;
  • unnecessary diff size and changes outside the requested scope;
  • ability to recover from compiler and test failures;
  • documentation truthfulness; and
  • elapsed time, model/tool configuration and cost when publishable.

Results must name the exact model/version, agent harness, reasoning setting, date and commit SHA. A vendor description or one successful demonstration is not compatibility evidence. Preview and mutable model aliases must be reported as such.

The first matrix should distinguish at least two profiles:

  • application builder: works through documented, stable public APIs and generated projects; and
  • framework maintainer: changes crate internals, feature combinations, macros, security boundaries or release infrastructure.

A model may qualify for one profile without qualifying for the other.

5. Task-oriented documentation routing

Add a short machine-readable and human-readable entry map that answers “which document should I read for this task?” before exposing the full manual. It must route architecture changes to the SST, release work to the release programme, security changes to the relevant threat model and ordinary application work to the smallest applicable tutorial and API reference.

Where documents overlap, one must be declared authoritative and the others must link to it rather than restating mutable facts. Documentation examples should continue to be compiled or exercised wherever practical.

6. Governance and review boundaries

AI assistance never replaces contributor accountability or independent review. The disclosure and verification rules in CONTRIBUTING.md apply to every model and tool. Exact prompt disclosure remains optional context, not proof of authorship, completeness or safety.

Routine documentation, test and mechanical dependency work may use a faster model when the gates prove the result. Authentication, cryptography, ORM/macro contracts, release signing and cross-crate security changes require heightened human review regardless of the model used. No evaluation score authorizes unattended merge or production mutation.

Delivery sequence

PhaseDeliveryPromotion evidence
AGenerated application AGENTS.md and context format v2Blueprint snapshots, deterministic output, secret-exclusion and generated-project gates
BGolden task fixtures and evaluation runnerFixed inputs, hidden assertions, security-negative tests and reproducible metadata
CPublic model/harness resultsExact versions, commit SHA, limitations and repeatable commands
DContinuous regression programmeScheduled or manual reruns, versioned baselines and reviewed score changes

Phases begin after the v12 RC is cut. Compatible documentation corrections may land in v12 maintenance, but new generator formats, fixtures and public support profiles belong to the v13 feature line unless a separate release decision says otherwise.

Completion criteria

This roadmap may be called complete only when:

  • every supported blueprint produces accurate, tested agent instructions;
  • context generation is bounded, deterministic, secret-safe and task-routable;
  • the golden application and maintenance suites run from clean fixtures;
  • published results are tied to exact models, harnesses, commits and dates;
  • at least one lower-cost model completes the application-builder profile at the project-defined quality threshold without security-critical failures; and
  • failure cases and unsupported maintenance classes are documented as clearly as successful ones.

Until then, the honest claim is narrower: Rullst is intentionally structured to support AI-assisted work and provides useful safeguards, but model suitability must be evaluated for the concrete task and reviewed like any other contribution.

Non-goals

  • guaranteeing correct output from every current or future model;
  • detecting all AI-generated code or proving the complete prompt history;
  • replacing human ownership, security review or coordinated disclosure;
  • auto-merging changes based on a model name or benchmark score; or
  • claiming superiority over other frameworks without dated, reproducible comparative evidence.

Redis, Local Cache & Queue Drivers

Rullst v12 keeps cache and queue backends explicit. Enabling a Cargo feature only compiles the adapter; it does not inspect REDIS_URL, switch drivers, or silently fall back when Redis is unavailable.

Cache choices

Cache::memory() uses a process-local DashMap. Values disappear on restart and are not shared between replicas:

#![allow(unused)]
fn main() {
use rullst_core::cache::{Cache, CacheError};
use std::sync::Arc;

async fn load_profile(cache: &Cache) -> Result<Arc<String>, CacheError> {
    cache
        .remember("profile:42", 300, || async {
            Ok("serialized profile".to_string())
        })
        .await
}

async fn read_profile() -> Result<(), CacheError> {
let cache = Cache::memory();
let profile = load_profile(&cache).await?;
let _ = profile;
Ok(())
}
}

For a shared Redis cache, enable cache-redis (or the umbrella redis feature) and construct the adapter explicitly:

[dependencies]
rullst-core = { version = "12.0.0-rc.1", features = ["cache-redis"] }
#![allow(unused)]
fn main() {
use rullst_core::cache::Cache;

async fn cache_featured_catalog() -> Result<(), Box<dyn std::error::Error>> {
let redis_url = std::env::var("REDIS_URL")?;
let cache = Cache::redis(redis_url)?;
cache.put("catalog:featured", "[...]", Some(600)).await?;
Ok(())
}
}

Constructing the driver validates the Redis URL but does not establish a connection. Operations open a multiplexed async connection and return a typed CacheError if Redis is unavailable. Choose an application-specific policy: fail startup, retry with bounds, or explicitly select Cache::memory() for a documented single-instance development mode.

The built-in Redis cache prefixes keys with rullst:cache:. flush() scans and unlinks keys under that prefix; use dedicated credentials/database boundaries when multiple applications share a Redis service.

ORM .remember(...) queries

The ORM has a separate opt-in query-cache contract behind its redis feature:

[dependencies]
rullst-orm = { version = "12.0.0-rc.1", features = ["redis"] }
#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};

#[derive(Debug, Clone, FromRow, Orm)]
#[orm(table = "users")]
struct User {
    id: i32,
    active: bool,
}

async fn load_recent_users() -> Result<(), Box<dyn std::error::Error>> {
let redis_url = std::env::var("REDIS_URL")?;
Orm::init_redis_with_namespace(&redis_url, "academy-production").await?;

let recent = User::query()
    .where_eq("active", true)
    .remember(30)
    .get()
    .await?;
let _ = recent;
Ok(())
}
}

Use a stable, unique namespace for every application that shares a Redis database. Query keys bind that namespace, an opaque digest of the active tenant scope, table, generated SQL and typed bindings. They do not expose raw tenant identifiers. The older Orm::init_redis(url) API remains available and uses default; only use it with a dedicated Redis database.

The failure and consistency rules are explicit:

  • remember(0) is rejected.
  • Missing Redis initialization is a configuration error for a remembered query outside a transaction.
  • Redis command failures or corrupt JSON fall back to the authoritative database; a successful read is returned even if cache population fails.
  • Explicit and task-scoped ORM transactions always bypass query cache.
  • Generated model saves/deletes invalidate the active tenant/table’s remembered results only after a managed commit; rollback keeps existing cache entries. Raw SQL, bulk builders and writes from another process cannot be inferred. Keep defensive TTLs and do not cache authorization or reads that require a stronger distributed consistency contract.

The Core Cache facade and ORM query cache use different keyspaces and APIs; initializing one does not initialize the other.

Queue choices

Rullst provides explicit SQLite and Redis queue constructors:

[dependencies]
rullst-core = { version = "12.0.0-rc.1", features = ["queue-sqlite"] }
serde_json = "1"
#![allow(unused)]
fn main() {
use rullst_core::queue::Queue;
use serde_json::json;

async fn enqueue_receipt() -> Result<(), Box<dyn std::error::Error>> {
let queue = Queue::sqlite("sqlite://jobs.sqlite?mode=rwc").await?;
let job_id = queue
    .dispatch("send_receipt", json!({ "invoice_id": 42 }))
    .await?;
println!("queued {job_id}");
Ok(())
}
}

With queue-redis, construct Queue::redis(redis_url) instead. The Redis driver uses atomic Lua transitions for pending, processing, failed, and dead-letter state. Production validation must still cover Redis persistence, eviction policy, credentials/TLS, failover, monitoring, and worker recovery in the target topology.

There is no automatic interchange between the SQLite and Redis queues: they store independent state. Switching a live deployment requires an explicit drain/migration plan.

Real-time boundary

Core’s current WebSocket broadcast/presence helpers are process-local. Redis Streams, Redis Pub/Sub, Kafka, and RabbitMQ transports remain roadmap work; do not describe the cache or queue adapter as cross-instance real-time sync.

Deployment checklist

  • Choose the backend in application configuration and make fallback policy explicit.
  • Never commit Redis credentials; prefer TLS and least-privilege network access.
  • Namespace application/tenant keys above the built-in driver prefix where isolation is required. TenantCache supplies validated tenant namespaces.
  • Test disconnects, timeouts, retries, eviction, restart, and worker recovery.
  • Benchmark the deployed service. Rullst does not claim universal cache latency, memory usage, or infrastructure cost.

Tutorial 01: From Zero to Hello Rullst 🚀

What you will build: one complete web application with a typed route and server-rendered HTML. No database account or AI provider is needed. By the end, you will be able to point to the handler that produced the page in your browser.

Choose a different starting path · Next: CLI generators

This tutorial takes a new developer from installing Rust to a running Rullst web application. It uses the unreleased v12 development snapshot documented by this site. It is not a production recommendation. A future production adoption needs a supported release and reviewed immutable artifacts; neither moving main nor merely pinning end-of-life v5 satisfies that requirement.

1. Install Rust and Cargo

On Linux or macOS, use the official rustup installer:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

On Windows, download rustup from rustup.rs or run:

winget install --id Rustlang.Rustup

Restart the terminal if necessary, then verify the toolchain:

rustc --version
cargo --version

Rullst v12 requires the MSRV recorded in the compatibility policy.

2. Create a project

cargo new my_first_app
cd my_first_app

Every command below must run in this directory, where Cargo.toml lives.

3. Add the v12 preview

Until v12 is published, select the development source explicitly:

cargo add rullst --git https://github.com/Rullst/Rullst.git --branch main
cargo add tokio --features full

Cargo records the resolved Git commit in Cargo.lock. This makes one checkout repeatable, but a future dependency update can select a newer main commit. Do not use this mutable preview source in production.

Applications that must remain on end-of-life v5 should use its versioned API documentation instead; the v12 API below is intentionally different. That reference preserves the old API, not a promise of ongoing maintenance or a deployment recommendation.

4. Define the first route

Replace src/main.rs with:

use rullst::{html, response::Html, routes, Server};

async fn home() -> Html<String> {
    Html(html! {
        <div class="min-h-screen bg-slate-900 text-emerald-400 flex flex-col items-center justify-center font-sans">
            <h1 class="text-5xl font-extrabold mb-4">"Hello, Rullst! 📜🦀"</h1>
            <p class="text-slate-400 text-lg">"Your first typed route is running."</p>
        </div>
    })
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = routes![
        get("/" => home)
    ];

    Server::new(app).run(3000).await?;
    Ok(())
}

The fallible server startup uses ?, so bind and runtime failures are returned to the process instead of causing a framework-originated panic.

5. Run the application

Start the server:

cargo run

Open http://localhost:3000. Stop it with Ctrl+C.

Make it yours: change the heading to your project name, restart with cargo run, and confirm the response changed. The example’s utility class names do not install Tailwind by themselves; seeing an unstyled page is not a server failure. Use a generated HTML blueprint for a bundled local stylesheet.

If the port is already occupied, stop the conflicting process you own or change the .run(3000) port above. Keep the terminal output: it is the first place to look for a build or startup diagnostic.

6. Continue with the CLI

The v12 CLI can generate complete starters and project modules. While working from a source checkout, install the same revision locally:

git clone --branch main https://github.com/Rullst/Rullst.git
cd Rullst
cargo install --locked --path cargo-rullst
cargo rullst --help

The CLI’s new generator will target the CLI’s framework version. Until v12 is published, a pre-release CLI built from this checkout emits absolute path dependencies to that exact checkout, including when invoked elsewhere. Keep the checkout in place and review those sources before sharing the generated project. See the CLI reference for every command and boundary.

Key takeaways

  • The rullst::html! macro generates ordinary Rust string-building code and escapes dynamic values. Rendering still performs the allocations/work implied by the generated template.
  • All boolean HTML attributes inside html! must be explicitly quoted (e.g. required="true").
  • Fallible handlers can return Result<Response, YourAppError> using an application-defined error that converts the relevant typed framework/domain errors; server startup propagates ServerError with ?.
  • The v12 main branch is an evaluation source, not a stable release channel.

Create Your First Rullst REST API

Your result: a typed JSON response you can verify from another terminal. Start here when your frontend is a separate app, mobile client or integration. Explore all beginner paths.

This quickstart creates one runnable JSON endpoint without a database or an AI provider. It targets the unreleased v12 snapshot on main, for local evaluation only. Production adoption needs a supported release and reviewed immutable artifacts; pinning end-of-life v5 does not make it supported again.

1. Create the application

cargo new first_rullst_api
cd first_rullst_api
cargo add rullst --git https://github.com/Rullst/Rullst.git --branch main
cargo add tokio --features macros,rt-multi-thread
cargo add serde --features derive

Keep the generated Cargo.lock so every checkout resolves the same framework commit.

2. Add a JSON route

Replace src/main.rs with:

use rullst::{Server, ServerError, routes, server::Json};
use serde::Serialize;

#[derive(Serialize)]
struct HealthResponse {
    status: &'static str,
    framework: &'static str,
}

async fn health() -> Json<HealthResponse> {
    Json(HealthResponse {
        status: "ok",
        framework: "Rullst",
    })
}

#[tokio::main]
async fn main() -> Result<(), ServerError> {
    let app = routes![
        get("/api/health" => health),
    ]
    .layer(rullst::server::from_fn(
        rullst::security::headers_middleware,
    ));

    Server::new(app).run(3000).await
}

The response is serialized from a typed Rust value. The secure-header layer is included explicitly because transport, proxy, authentication, authorization, CSRF, and application-specific input policy remain deployment responsibilities; a JSON response alone is not a production security boundary.

3. Run and verify it

Start the application:

cargo run

From another terminal, request the route:

curl -i http://127.0.0.1:3000/api/health

The body is:

{"status":"ok","framework":"Rullst"}

Stop the server with Ctrl+C.

4. Prefer a generated API foundation?

After installing the matching v12 CLI, it can scaffold a headless API directly. Use a different directory from the hand-written example above:

cargo rullst new generated_api --default --api --no-database \
  --skip-initial-migration
cd generated_api
cargo run

This generates the blueprint’s own routes, not an exact copy of the health handler above. Read its controller and route registration before choosing a URL to test. Use cargo rullst make:controller project --api for additional JSON controllers. Generated parameterized data routes still require explicit ownership or tenant authorization; see RBAC and IDOR protection.

Tutorial 02: CLI Automation & Generators ⚡

Your goal: generate a small piece of the application, find the resulting files and understand what still needs your code. Run these commands from an existing generated project’s root, where Cargo.toml and Rullst.toml live. The commands below are independent examples; do not generate Product twice unless you have deliberately removed or renamed your own earlier fixture.

Rullst provides opinionated code generators (make:*) for controllers, models, migrations, and resources. The current generators register generated Rust modules when possible; they do not silently add application routes or rewrite AI context. Review every generated file and mount the intended routes yourself.


🛠️ Essential Scaffolding Commands

1. Generate a Controller

cargo rullst make:controller ProductsController

Creates src/controllers/products_controller.rs with placeholder index, show, store, update, and delete handlers and registers the module.

2. Generate a Model & Migration

cargo rullst make:model Product --migration

Creates the model in src/models/product.rs and a timestamped Rust migration in src/migrations/. Review its columns before applying it to a local database.

3. Generate a Full-Stack Resource Starting Point

cargo rullst make:resource Product

Scaffolds a model, Rust migration, controller, and HTML view placeholders (views/product/index.html and views/product/form.html) in one command. The generated controller is not a complete authorized CRUD implementation.


🔍 Static CLI Code Inspection

Inspect recognizable source declarations without launching a server:

# Inspect recognizable routes! declarations
cargo rullst inspect route

# Print ORM models and field types
cargo rullst inspect model

💡 Key Takeaways

  • Scaffolding generators maintain architectural consistency across team members.
  • cargo rullst inspect performs bounded source-text inspection. Route output recognizes explicit single-line routes! entries; model output lists public declarations from src/models. It is not complete macro expansion or a Rust semantic analysis.

Tutorial 03: Active Record CRUD Operations 🗄️

rullst-orm derives a typed query builder and persistence methods from a Rust struct. The SQLx-backed model uses an i32 primary key named id; a zero value means that save() inserts, while a non-zero value means that it updates.


Step 1: Define an Active Record model

In src/models/user.rs:

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};

#[derive(Debug, Clone, FromRow, Orm)]
#[orm(table = "users")]
pub struct User {
    pub id: i32,
    pub name: String,
    pub email: String,
}
}

The database table must contain matching columns. Run migrations before using the model.


Step 2: Perform CRUD operations

Create

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};
#[derive(Debug, Clone, FromRow, Orm)]
#[orm(table = "users")]
struct User { id: i32, name: String, email: String }
async fn create_user() -> Result<(), rullst_orm::Error> {
let mut user = User {
    id: 0,
    name: "Alice Developer".to_string(),
    email: "alice@example.com".to_string(),
};
user.save().await?;
// `user.id` now contains the inserted primary key.
Ok(())
}
}

Read and filter

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};
#[derive(Debug, Clone, FromRow, Orm)]
#[orm(table = "users")]
struct User { id: i32, name: String, email: String }
async fn read_users() -> Result<(), rullst_orm::Error> {
let user = User::find(1).await?; // Result<Option<User>, rullst_orm::Error>
let example_users = User::query()
    .where_like("email", "%@example.com")
    .get()
    .await?;
let _ = (user, example_users);
Ok(())
}
}

Builder values are bound as query parameters. Column names are validated as identifiers, but they should still be application-owned constants rather than untrusted request input.

Update

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};
#[derive(Debug, Clone, FromRow, Orm)]
#[orm(table = "users")]
struct User { id: i32, name: String, email: String }
async fn update_user() -> Result<(), rullst_orm::Error> {
if let Some(mut user) = User::find(1).await? {
    user.name = "Alice Smith".to_string();
    user.save().await?;
}
Ok(())
}
}

Delete

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};
#[derive(Debug, Clone, FromRow, Orm)]
#[orm(table = "users")]
struct User { id: i32, name: String, email: String }
async fn delete_user() -> Result<(), rullst_orm::Error> {
if let Some(user) = User::find(1).await? {
    user.delete().await?;
}
Ok(())
}
}

Soft deletes

A deleted_at: Option<String> field opts the model into the default soft-delete contract. For a different sentinel, configure it explicitly and ensure the migration uses the same representation:

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};

#[derive(Debug, Clone, FromRow, Orm)]
#[orm(
    table = "users",
    soft_delete(field = "is_deleted", value = "0", delval = "1")
)]
pub struct SoftUser {
    pub id: i32,
    pub name: String,
    pub is_deleted: i32,
}
}

query() applies the model’s configured scopes. unscoped() is an explicit administrative escape hatch and should not be used directly from request data.


Key takeaways

  • save, find, all, query, and delete are generated for SQLx-backed Orm models.
  • Creation uses a normal Rust struct, not a JSON map.
  • CRUD errors are returned as rullst_orm::Error; a missing row is Ok(None).
  • Use a caller-owned transaction and the generated *_with_tx methods when a business operation must commit multiple writes atomically.

Tutorial 04: Data Mapper & Repository Pattern 🏗️

For domain-heavy code, Rullst exposes a small Repository<T> contract. The framework does not invent SQL for this trait: your adapter owns its pool, queries, transactions, and error type. This keeps persistence behavior explicit and makes an in-memory implementation straightforward in unit tests.

GenericRepository<T> is currently only a zero-state marker/helper. It does not accept a pool and does not provide methods such as find_one_by.


Step 1: Implement a PostgreSQL repository

#![allow(unused)]
fn main() {
use rullst_orm::{async_trait, sqlx, FromRow, Repository};

#[derive(Debug, Clone, FromRow)]
pub struct User {
    pub id: i64,
    pub name: String,
    pub email: String,
}

#[derive(Clone)]
pub struct PgUserRepository {
    pool: sqlx::PgPool,
}

impl PgUserRepository {
    pub fn new(pool: sqlx::PgPool) -> Self {
        Self { pool }
    }

    pub async fn find_by_email(
        &self,
        email: &str,
    ) -> Result<Option<User>, sqlx::Error> {
        sqlx::query_as::<_, User>(
            "SELECT id, name, email FROM users WHERE email = $1",
        )
        .bind(email)
        .fetch_optional(&self.pool)
        .await
    }
}

#[async_trait]
impl Repository<User> for PgUserRepository {
    type Id = i64;
    type Error = sqlx::Error;

    async fn find_by_id(&self, id: i64) -> Result<Option<User>, Self::Error> {
        sqlx::query_as::<_, User>(
            "SELECT id, name, email FROM users WHERE id = $1",
        )
        .bind(id)
        .fetch_optional(&self.pool)
        .await
    }

    async fn find_all(&self) -> Result<Vec<User>, Self::Error> {
        sqlx::query_as::<_, User>(
            "SELECT id, name, email FROM users ORDER BY id",
        )
        .fetch_all(&self.pool)
        .await
    }

    async fn save(&self, user: &User) -> Result<(), Self::Error> {
        sqlx::query(
            "INSERT INTO users (id, name, email) VALUES ($1, $2, $3) \
             ON CONFLICT (id) DO UPDATE SET name = $2, email = $3",
        )
        .bind(user.id)
        .bind(&user.name)
        .bind(&user.email)
        .execute(&self.pool)
        .await?;
        Ok(())
    }

    async fn delete(&self, id: i64) -> Result<(), Self::Error> {
        sqlx::query("DELETE FROM users WHERE id = $1")
            .bind(id)
            .execute(&self.pool)
            .await?;
        Ok(())
    }
}
}

All values are parameterized. SQL identifiers and the query shape remain application-owned source code.


Step 2: Inject the concrete adapter through Axum state

use std::sync::Arc;
use axum::{extract::State, http::StatusCode, Json};

pub async fn get_user_by_email(
    State(repository): State<Arc<PgUserRepository>>,
    email: String,
) -> Result<Json<User>, StatusCode> {
    repository
        .find_by_email(&email)
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}

Production handlers should map internal database errors to an application error without returning query or credential details to clients.


Key takeaways

  • Use derived Active Record models for direct typed CRUD.
  • Use Repository<T> when the domain needs an explicit persistence boundary.
  • The repository implementation, not the trait, determines the supported backend and SQL dialect.
  • Add tenant/owner predicates inside repository queries where the resource is tenant- or user-scoped; dependency injection is not an authorization check.

Tutorial 05: Database Migrations & Seeders 🗄️

Rullst uses timestamped Rust migration modules for SQLx-primary projects. Turso primary projects receive explicit, reversible TursoMigration statements instead. This tutorial shows the SQLx path.


Step 1: Create a migration

cargo rullst make:migration create_products_table

The command creates src/migrations/m<timestamp>_create_products_table.rs and regenerates src/migrations/mod.rs. Edit the generated up and down methods:

#![allow(unused)]
fn main() {
use rullst_orm::{async_trait, schema::{Migration, Schema}};

pub struct MigrationImpl;

#[async_trait]
impl Migration for MigrationImpl {
    fn name(&self) -> &'static str {
        "m20260901000000_create_products_table"
    }

    async fn up(&self) -> Result<(), rullst_orm::Error> {
        Schema::create("products", |table| {
            table.id();
            table.string("name").not_null();
            table.integer("price_cents").not_null();
            table.timestamps();
        })
        .await
    }

    async fn down(&self) -> Result<(), rullst_orm::Error> {
        Schema::drop_if_exists("products").await
    }
}
}

Keep the generated timestamp/name in name(); the runner uses that stable value to record migration state.


Step 2: Run, inspect, and roll back migrations

cargo rullst db:migrate
cargo rullst db:status
cargo rullst db:rollback

db:rollback runs down() for the last recorded batch, in reverse order. The current SQLx migration runner does not wrap the whole batch automatically in one database transaction. Make every migration reversible, test both directions against each supported database, and use backend-appropriate transactional DDL inside the migration when atomicity is required.


Step 3: Define and register a seeder

use rullst_orm::{async_trait, Seeder};

pub struct AdminSeeder;

#[async_trait]
impl Seeder for AdminSeeder {
    async fn run(&self) -> Result<(), rullst_orm::Error> {
        let mut admin = crate::models::User {
            id: 0,
            name: "Admin User".to_string(),
            email: "admin@example.test".to_string(),
        };
        admin.save().await
    }
}

pub fn get_seeders() -> Vec<Box<dyn Seeder>> {
    vec![Box::new(AdminSeeder)]
}

Register migrations and seeders before starting the server:

rullst::artisan!(
    crate::migrations::get_migrations(),
    crate::seeds::get_seeders(),
);

Then run:

cargo rullst db:seed

Seeders execute sequentially. Make development/CI seeders idempotent if the command may run more than once. Never commit real passwords or provider secrets; for authentication records, hash a test password with rullst-auth or create a non-login fixture.


Key takeaways

  • Migrations are Rust modules, not split up/down SQL files.
  • Migration names and order are generated deterministically from timestamps.
  • Batch tracking exists, but batch-wide transactional rollback is not implied.
  • db:seed executes only seeders explicitly registered with artisan!.

Tutorial 06: HTMX-oriented server rendering 🎨

Rullst’s default scaffold renders HTML on the server and can use HTMX attributes for targeted requests and fragment swaps. It does not require a project-local SPA bundle, but HTMX itself is browser JavaScript and must be supplied, pinned, and permitted by the application’s CSP.


🛠️ Step 1: Render HTMX Components

In your controller or view:

#![allow(unused)]
fn main() {
use axum::response::Html;
use rullst::html;
use rullst::html::RawHtml;

pub async fn search_users() -> Html<String> {
    let results = vec!["Alice", "Bob", "Charlie"];
    let rows = results
        .into_iter()
        .map(|name| html! {
            <li class="py-2 text-slate-200">{name}</li>
        })
        .collect::<String>();

    Html(html! {
        <ul id="user-list" class="divide-y divide-slate-700">
            {RawHtml(rows)}
        </ul>
    })
}
}

RawHtml is appropriate here only because rows is composed exclusively from already-escaped html! fragments. Do not wrap untrusted request data directly in RawHtml.


💻 Step 2: Wire HTMX Attributes in Front-End HTML

<div class="max-w-md mx-auto p-6 bg-slate-800 rounded-xl shadow-md">
    <input 
        type="text" 
        name="query" 
        placeholder="Search users..." 
        class="w-full px-4 py-2 bg-slate-900 text-white rounded border border-slate-700 focus:outline-none"
        hx-post="/api/users/search" 
        hx-trigger="keyup changed delay:300ms" 
        hx-target="#user-list" 
        hx-swap="outerHTML" 
    />
    
    <div id="user-list" class="mt-4 text-slate-400">
        "Start typing to search..."
    </div>
</div>

💡 Key Takeaways

  • Small application-owned client surface: business logic can remain on the server while HTMX coordinates browser requests.
  • Partial rendering: handlers can return fragments instead of full pages. Measure page weight and latency for the actual application; no fixed size or load-time guarantee follows from the rendering style.

Tutorial 07: Forms & DTO Validation 📝

Rullst provides ValidatedForm<T> and ValidatedJson<T> extractors. They parse the request and run validator constraints before the handler is called. Invalid payloads become bounded 400 or 422 responses; HTMX requests receive an HTML error fragment and other clients receive JSON.


Step 1: Define a validated DTO

#![allow(unused)]
fn main() {
use serde::Deserialize;
use rullst::Validate;

#[derive(Debug, Deserialize, Validate)]
pub struct CreateUserForm {
    #[validate(length(min = 3, max = 100))]
    pub name: String,

    #[validate(email)]
    pub email: String,

    #[validate(length(min = 12, max = 72))]
    pub password: String,
}
}

The 72-byte upper bound matches the current password hashing contract. Add a request-body limit at the router/proxy boundary as validation happens after body extraction.


Step 2: Validate and hash before persistence

The following handler assumes the User model generated by cargo rullst auth:

use axum::{http::StatusCode, response::Html};
use rullst::{html, ValidatedForm};
use rullst_auth::hash_password_async;

pub async fn store(
    ValidatedForm(form): ValidatedForm<CreateUserForm>,
) -> Result<Html<String>, StatusCode> {
    let password_hash = hash_password_async(form.password)
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    let mut user = crate::models::User {
        id: 0,
        name: form.name.trim().to_owned(),
        email: form.email.trim().to_ascii_lowercase(),
        password_hash: Some(password_hash),
        oauth_provider: None,
        oauth_id: None,
        created_at: String::new(),
        updated_at: String::new(),
    };
    user.save()
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Html(html! {
        <div class="p-4 bg-emerald-900/50 text-emerald-300 rounded border border-emerald-500">
            <p>"User created successfully"</p>
        </div>
    }))
}

This is a continuation snippet because the generated model lives in the application crate. The checked extractor implementation itself is covered by rullst-core tests.

For a JSON endpoint, replace ValidatedForm with ValidatedJson; the DTO and handler body remain the same.


Key takeaways

  • Validation is not sanitization and not authorization; enforce all three at their respective boundaries.
  • Never store, log, or echo a plaintext password. Use the asynchronous Argon2id helper in request handlers.
  • Enforce a unique database index on normalized email rather than relying only on a pre-insert lookup.
  • Keep internal validation/database details out of production client errors.

Tutorial 08: Controllers, Routing & Middleware 🚦

Rullst’s Router wraps Axum’s router while preserving explicit Tower middleware composition. This example uses the Axum 0.8 request and Next types.


Step 1: Create a custom middleware

In src/middlewares/logger.rs:

#![allow(unused)]
fn main() {
use rullst::web::axum::{
    extract::Request,
    middleware::Next,
    response::Response,
};

pub async fn log_request(req: Request, next: Next) -> Response {
    let method = req.method().clone();
    let path = req.uri().path().to_owned();
    let response = next.run(req).await;

    // Prefer structured tracing in production; do not log query strings,
    // cookies, authorization headers, or request bodies by default.
    println!("[HTTP] {method} {path} -> {}", response.status());
    response
}
}

Step 2: Organize sub-routers

use rullst::{Router, Server, ServerError};
use rullst::routing::{get, post};
use rullst::web::axum::middleware;

async fn list_users() -> &'static str { "users" }
async fn create_user() -> &'static str { "created" }
async fn login() -> &'static str { "login" }

async fn log_request(
    request: rullst::web::axum::extract::Request,
    next: rullst::web::axum::middleware::Next,
) -> rullst::web::axum::response::Response {
    next.run(request).await
}

#[tokio::main]
async fn main() -> Result<(), ServerError> {
    let api = Router::new()
        .route("/users", get(list_users).post(create_user))
        .layer(middleware::from_fn(log_request));

    let auth = Router::new().route("/login", post(login));
    let app = Router::new()
        .nest("/api/v1", api)
        .nest("/auth", auth);

    Server::new(app).run(3000).await
}

Use nest_axum or merge_axum when integrating a third-party raw axum::Router.


Key takeaways

  • Middleware order is security-sensitive. Use the canonical production baseline for secure headers, CSRF, CORS, and WAF rather than assembling those controls ad hoc.
  • Authentication middleware establishes identity; handlers/repositories must still enforce resource ownership or role authorization.
  • Apply request-body and concurrency limits before handlers that parse expensive or attacker-controlled input.

Tutorial 09: Environment Management & Configuration ⚙️

Rullst provides a validated runtime environment shared by its subsystems. New projects use RULLST_ENV; APP_ENV remains a legacy compatibility alias.


🛠️ Step 1: Define Environment Variables in .env

RULLST_ENV=development
PORT=3000
APP_KEY=REPLACE_WITH_YOUR_32_CHAR_RANDOM_KEY
DATABASE_URL=postgres://postgres:password@localhost:5432/my_app

💻 Step 2: Access Configuration in Rust

#![allow(unused)]
fn main() {
use rullst_core::config::RullstConfig;

fn report_environment() -> Result<(), Box<dyn std::error::Error>> {
    let environment = RullstConfig::global().environment()?;
    let database_is_configured = std::env::var_os("DATABASE_URL").is_some();

    println!(
        "Booting Rullst in {environment} mode; database configured: {database_is_configured}"
    );
    Ok(())
}
}

Do not log DATABASE_URL: it commonly embeds a username and password. Report only whether configuration is present, and redact sensitive fields in structured telemetry.

The generated server bootstrap loads .env before applying its runtime policy. Standalone utilities must load a dotenv file themselves or receive exported process variables. Environment precedence is exact: RULLST_ENV, legacy APP_ENV, then [app].env in Rullst.toml. Unknown values are configuration errors rather than silently becoming development.


Step 3: Configure the Browser Security Baseline

Server applies the same public apply_security_baseline composition in staging and production. CORS is deny-by-omission: list exact origins without a trailing slash, and enable credentialed cross-origin requests only when the application genuinely needs them.

[security]
csrf_same_site = "Strict"
cors_allow_origins = ["https://academy.example"]
cors_allow_credentials = false
csp = "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'nonce-{NONCE}'; style-src 'self' 'nonce-{NONCE}'"

Wildcard, path-bearing, credential-bearing, queried or duplicate CORS origins are configuration errors. When credentials are enabled, Core still grants them only to an origin in the exact allowlist. Test the final policy behind the real TLS proxy because an intermediary can change headers and cookie behavior.


💡 Key Takeaways

  • Never hardcode credentials, database passwords, or JWT secrets in .rs source code.
  • Add .env to .gitignore and commit .env.example to document required keys for team members.
  • Prefer RULLST_ENV; keep APP_ENV only while migrating an existing project.

Tutorial 10: Static Assets & Pre-Compression 📦

The standard Server serves an existing static/ directory at /static. Production builds can create Brotli and Zstandard sidecars for eligible text and Wasm assets in that directory.


Step 1: Use the standard static directory

static/
├── css/
│   └── app.css
├── js/
│   └── app.js
└── favicon.svg

Reference those files through /static/..., for example /static/css/app.css. Server::run mounts the directory when it exists; no additional ServeDir layer is required for this standard path.

use rullst::{routes, routing::get, Server};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = routes![get("/" => || async { "Rullst" })];
    Server::new(app).run(3000).await?;
    Ok(())
}

If you mount a different directory manually through Axum/Tower, its routing and pre-compressed negotiation become application responsibilities.


Step 2: Build production sidecars

cargo rullst build

The release-mode command builds the application and creates .br and .zst siblings for html, css, js, json, svg, wasm, xml, and txt files under static/. The standard server negotiates Brotli through ServeDir and Zstandard through its static middleware.

Verify deployed behavior rather than assuming negotiation worked:

curl --compressed -I -H 'Accept-Encoding: br' \
  http://127.0.0.1:3000/static/css/app.css
curl -I -H 'Accept-Encoding: zstd' \
  http://127.0.0.1:3000/static/css/app.css

Check Content-Encoding, Content-Type, cache headers, and Vary through the actual TLS proxy/CDN. Pre-compression avoids compression work per request; it does not eliminate file I/O or network latency.


Key takeaways

  • Use static/ for the framework’s standard asset path and build integration.
  • Keep source files alongside generated sidecars in the deployed artifact.
  • Fingerprint immutable asset names and configure cache policy at the application/CDN boundary.

Tutorial 11: Authentication Scaffolding 🔒

The authentication generator creates a reviewable starting point for cookie-session login and registration. It is application source code, not a hosted identity service or a claim that every production policy is automatic.


Step 1: Run the generator

Run this from a Rullst project root:

cargo rullst auth

The current command creates or updates:

  • src/models/user.rs;
  • src/migrations/m<timestamp>_create_users_table.rs and the migrations module;
  • src/controllers/auth_controller.rs;
  • src/middlewares/auth_middleware.rs;
  • src/pages/auth.rs; and
  • the corresponding module declarations.

It does not support an auth --api flag, and it does not silently register application routes. Review the generated diff before editing or rerunning the command.


Step 2: Register the generated handlers

Wire the generated view, submit, logout, and authenticated routes in the application router. The exact route tree is an application decision; a typical mapping is:

use rullst::{routes, routing::{get, post}};
use crate::controllers::auth_controller;

let public_auth = routes![
    get("/login" => auth_controller::login_view),
    post("/login" => auth_controller::login_submit),
    get("/register" => auth_controller::register_view),
    post("/register" => auth_controller::register_submit),
    post("/logout" => auth_controller::logout),
];

The generated form pages expect the Core CSRF and CSP-nonce extensions. Apply the canonical security baseline and place authenticated routes behind the generated authentication middleware.


Step 3: Migrate and test

cargo rullst db:migrate
cargo rullst dev

Before production, exercise registration, login, logout, invalid credentials, duplicate email, expired/tampered cookies, CSRF failure, rate limiting, and key rotation. Configure a strong APP_KEY through a secret manager and HTTPS at the edge.


What the scaffold currently enforces

  • Passwords are hashed with the asynchronous Argon2id helper; plaintext is not written to the user model.
  • Registration accepts passwords from 12 through 72 bytes and normalizes email.
  • Login performs a dummy password verification for unknown users to reduce the obvious account-enumeration timing difference.
  • Session values use authenticated encryption and are emitted as cookie headers through rullst-auth helpers.

These controls do not replace application review. Add account verification, password reset/recovery, abuse controls, audit policy, MFA/passkeys, session revocation, and privacy/retention behavior according to the product’s threat model.

Tutorial 12: JWT and Cookie Sessions 🔑

Cookie sessions and bearer JWTs are transport choices with different operational trade-offs. Neither is automatically “for web” or “for mobile,” and neither removes the need for TLS, authorization, rotation, expiry, and revocation design.


Step 1: Generate JWT middleware

cargo rullst make:jwt

This creates src/middlewares/jwt_auth.rs, registers its module, and adds the direct jsonwebtoken, chrono, and serde dependencies when missing.

Configure a high-entropy secret plus exact issuer and audience values:

export JWT_SECRET="$(openssl rand -base64 48)"
export JWT_ISSUER="https://identity.example.test"
export JWT_AUDIENCE="rullst-api"

The generated HS256 validator requires sub, iss, aud, iat, and exp, checks expiry, and rejects secrets shorter than 32 bytes or with weak character diversity. Keep the secret out of source control and logs.


Step 2: Protect a route group

use rullst::{Router, routing::get};
use rullst::web::axum::middleware;
use crate::middlewares::jwt_auth::jwt_middleware;

pub fn protected_routes() -> Router {
    Router::new()
        .route("/profile", get(user_profile))
        .route("/orders", get(user_orders))
        .layer(middleware::from_fn(jwt_middleware))
}

Valid claims are inserted into request extensions. The handler must still map sub to a current account and enforce resource/tenant authorization. For long-lived systems, design key rotation and immediate revocation rather than assuming expiry alone is sufficient.


Choosing deliberately

  • An HttpOnly, Secure, appropriately SameSite cookie reduces direct token reads by browser JavaScript. It does not neutralize XSS: injected code can still act as the user, and cookie requests need CSRF protection.
  • A bearer token is convenient for interoperable API clients, but storage in a browser or native app is an application security decision. A stolen bearer token can be replayed until rejected or expired.
  • Cookie and JWT strategies can coexist at different boundaries, but keep one authoritative identity/session lifecycle and test logout/revocation behavior.

Tutorial 13: RBAC, Ownership, and IDOR/BOLA Protection 🛡️

rullst-security provides role, owner, and tenant checks over a trusted UserContext. The framework cannot infer ownership from a route parameter: the application must load the resource and pass its stored owner/tenant identifiers to the guard.


Step 1: Authorize a trusted user context

#![allow(unused)]
fn main() {
use rullst_security::{RbacGuard, SecurityError, UserContext};

pub fn authorize_admin(user: &UserContext) -> Result<(), SecurityError> {
    RbacGuard::authorize(user, "admin")
}
}

Construct UserContext only after authentication. Roles, permissions, and tenant membership must come from trusted server-side state, not request headers or JSON supplied by the caller.


Step 2: Check the resource record, not request ownership

#![allow(unused)]
fn main() {
use rullst_security::{RbacGuard, SecurityError, UserContext};

pub struct DocumentAccess {
    pub owner_user_id: String,
    pub tenant_id: String,
}

pub fn authorize_document_update(
    user: &UserContext,
    stored: &DocumentAccess,
) -> Result<(), SecurityError> {
    RbacGuard::authorize_tenant_owner_or_role(
        user,
        &stored.tenant_id,
        &stored.owner_user_id,
        "document-editor",
    )
}
}

Load DocumentAccess with a parameterized query by the route’s document ID. Do not accept owner_user_id or tenant_id from the update payload. The tenant guard is evaluated first and roles — including admin — do not bypass a tenant mismatch.

For particularly sensitive paths, make the database query itself tenant-scoped and then apply the guard as a second boundary. Return the same not-found/forbidden shape where revealing resource existence would leak information.


Key takeaways

  • authorize checks a role (and recognizes the framework’s admin role).
  • authorize_owner_or_role is safe only when the owner ID came from trusted resource state.
  • Use authorize_tenant_owner_or_role for tenant-bound resources.
  • A helper contributes to IDOR/BOLA prevention only when every parameterized resource route invokes it or an equivalent scoped repository policy.

Tutorial 14: RASP — Runtime Application Self-Protection ⚡

rullst-security::rasp applies bounded heuristic signatures to request targets, non-secret headers, and supported textual bodies. Its current signatures cover common SQL injection, path traversal, SSRF, shell/RCE, and JNDI indicators. It does not claim general exploit detection.


🛠️ Step 1: Mount RaspSecurityLayer in main.rs

use axum::Router;
use rullst_security::rasp::RaspSecurityLayer;
use rullst::Server;

#[tokio::main]
async fn main() -> Result<(), rullst::ServerError> {
    let app = Router::new()
        // ... routes
        .layer(RaspSecurityLayer::default());

    Server::new(app.into()).run(3000).await
}

🧪 Step 2: Test Malicious Attack Payload Interception

Send an attack payload in query string:

curl "http://localhost:3000/api/users?query=SELECT%20*%20FROM%20users;--' OR 1=1"

For a recognized bounded signature, the layer returns 403 Forbidden and adds a process-local event to SecurityStore. A Studio instance running in the same process can display that event at http://127.0.0.1:5555/studio/security.

The body inspector accepts identity-encoded UTF-8 text, JSON, form, and XML media types up to 1 MiB. It fails closed for oversized declared bodies and encoded textual bodies that it cannot inspect. Put an independent request-body limit outside this layer as well.


💡 Key Takeaways

  • Inspection has runtime cost and uses bounded pattern heuristics, with possible false positives and false negatives.
  • RASP is defense in depth; parameterized SQL, validation, authorization, body limits, and dependency review remain required.

Tutorial 15: Vault and ORM field encryption

Rullst v12 can encrypt String and Option<String> model fields before they reach the database and decrypt them when a generated ORM query loads the model. The implementation uses AES-256-GCM with a fresh random nonce and an authenticated, versioned envelope.

1. Configure a current key

Generate 32 random bytes and store them as a prefixed base64 value. Do not commit this value to source control:

export RULLST_ENCRYPTION_KEY="base64:$(openssl rand -base64 32)"
export RULLST_ENCRYPTION_KEY_ID="production-2026-01"

RULLST_ENCRYPTION_KEY accepts base64:<value>, hex:<value>, or a legacy raw value containing exactly 32 UTF-8 bytes. A secret manager or KMS-backed deployment adapter should inject it at runtime. Rullst does not provide key custody.

2. Mark model fields

#![allow(unused)]
fn main() {
use rullst_orm::FromRow;

#[derive(Clone, Debug, FromRow, rullst_orm::Orm)]
#[orm(table = "users")]
pub struct User {
    pub id: i32,
    pub email: String,
    #[orm(encrypted)]
    pub tax_id: String,
    #[orm(encrypted)]
    pub recovery_note: Option<String>,
}
}

Generated save, update_partial, find, all, query-builder loads and streams preserve plaintext in the Rust model while storing an envelope shaped like this in the SQL column:

RULLST:v2:<key_id>:<base64url_nonce>:<base64url_ciphertext_and_tag>

The table and column names are authenticated as additional data. Copying a ciphertext into a different annotated column therefore fails decryption.

3. Rotate a key without downtime

Set the new current key and keep old readable keys in a JSON keyring:

export RULLST_ENCRYPTION_KEY="base64:<new-32-byte-key>"
export RULLST_ENCRYPTION_KEY_ID="production-2027-01"
export RULLST_ENCRYPTION_KEYRING='{
  "production-2026-01": "base64:<old-32-byte-key>"
}'

Reads select the key named by the envelope. A normal full save() rewrites all annotated values with the current key. Keep every key needed by existing rows until a separately monitored migration has rewritten and verified them; then remove the retired key.

4. Understand query limits

AES-GCM encryption is randomized, so the same plaintext produces different ciphertexts. Generated queries reject encrypted fields in WHERE, ORDER BY, GROUP BY, and explicit SELECT clauses rather than silently returning wrong results. pluck_string supports non-null encrypted strings; load the model for nullable encrypted strings.

For lookup, add a separate application-designed blind-index column and assess its equality-leakage and key-rotation trade-offs. Rullst v12 does not generate a blind index automatically. Raw SQL is an explicit escape hatch and does not automatically encrypt bindings or decrypt arbitrary projections.

5. Reduce secret lifetime in memory

VaultSecret<T> redacts Debug/Display and calls Zeroize on the wrapped value when dropped:

#![allow(unused)]
fn main() {
use rullst_security::VaultSecret;

fn use_api_key() {
    let Ok(value) = std::env::var("EXAMPLE_PROVIDER_KEY") else {
        return;
    };
    let api_key = VaultSecret::new(value);
    send_request(api_key.expose_secret());
}

fn send_request(_: &str) {}
}

Zeroization is defense in depth, not secure memory. It cannot erase prior copies or prevent a debugger, core dump, swap, allocator behavior, or another process-memory capture while the secret is live.

Operational checklist

  • Back up the database and keyring together, with separate access controls.
  • Test restore and rotation before retiring any key.
  • Restrict environment and crash-dump access.
  • Never log plaintext model fields or expose them through serialization by accident; add #[orm(hidden)] when a field must be omitted from generated to_json() output.
  • Treat authentication failure as possible corruption, wrong context, wrong key, or tampering; do not replace it with an empty value.

Tutorial 16: LiveView Server-Driven UI (rullst::live) ⚡

Build a per-connection Rust component that receives JSON events and sends rendered HTML over WebSockets. The browser still needs the HTMX WebSocket extension (or a compatible client transport).


🛠️ Step 1: Scaffold a LiveComponent

cargo rullst make:live CounterComponent

This creates src/live/counter_component.rs.


💻 Step 2: Implement the Component Lifecycle

The following controller fragment expects the generated crate::live::counter_component module from Step 2:

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use rullst::live::LiveComponent;
use serde_json::Value;

#[derive(Default)]
pub struct CounterComponent {
    pub count: i32,
}

#[async_trait]
impl LiveComponent for CounterComponent {
    async fn mount(&mut self) {
        self.count = 10;
    }

    async fn handle_event(&mut self, payload: Value) {
        if let Some(action) = payload.get("action").and_then(|v| v.as_str()) {
            match action {
                "increment" => self.count += 1,
                "decrement" => self.count -= 1,
                _ => {}
            }
        }
    }

    fn render(&self) -> String {
        format!(
            r#"<div id="counter-component" class="p-6 bg-slate-800 text-white rounded-xl">
    <h2 class="text-xl font-bold">Counter: {}</h2>
    <button ws-send name="action" value="increment" class="px-4 py-2 bg-emerald-600 rounded">+1</button>
</div>"#,
            self.count
        )
    }
}
}

💻 Step 3: Mount Component in a Controller

This route fragment belongs in the same application module where CounterComponent is in scope:

use rullst::live::Live;
use crate::live::counter_component::CounterComponent;

pub async fn page_handler() -> String {
    Live::mount::<CounterComponent>("/ws/counter").await
}

Register the matching Axum WebSocket route:

use axum::{routing::get, Router};
use rullst::live::live_ws_handler;

let app = Router::new().route(
    "/ws/counter",
    get(live_ws_handler::<CounterComponent>),
);

💡 Key Takeaways

  • Event payloads travel over WebSocket connections; render() produces updated HTML fragments.
  • The current component state lives in one socket task. Authentication, authorization, reconnect/replay, backpressure and multi-process state remain application concerns.
  • Include and pin the HTMX WebSocket extension; Rullst does not inject that browser dependency automatically.

Tutorial 17: In-process realtime and presence

Rullst Core provides bounded in-process broadcast channels and presence state. These primitives do not create an HTTP WebSocket endpoint by themselves and do not synchronize independent server processes.

Subscribe before publishing

#![allow(unused)]
fn main() {
use std::sync::Arc;

use rullst::realtime::{BroadcastManager, RealtimeError, RealtimeMessage};

async fn local_exchange() -> Result<RealtimeMessage, RealtimeError> {
    let manager = Arc::new(BroadcastManager::new());
    let mut receiver = manager.get_or_create("room:general").subscribe();

    manager.publish(
        "room:general",
        "message.created",
        r#"{"sender":"alice","content":"hello"}"#,
    )?;

    receiver
        .recv()
        .await
        .map_err(|error| RealtimeError::BroadcastError(error.to_string()))
}
}

Channel uses tokio::sync::broadcast: a slow receiver can lag, and publishing without any receiver returns RealtimeError::BroadcastError.

Bind channels and presence to an authenticated tenant

Construct TenantContext only from trusted authentication/membership state. The wrappers validate logical names and ensure that identical room names use different backend namespaces:

#![allow(unused)]
fn main() {
use std::sync::Arc;

use rullst::realtime::{
    BroadcastManager, PresenceTracker, TenantPresence, TenantRealtime,
};
use rullst::security::TenantContext;

fn publish_for_school() -> Result<(), Box<dyn std::error::Error>> {
    let context = TenantContext::try_new("school-alpha")?;
    let realtime = TenantRealtime::from_context(
        Arc::new(BroadcastManager::new()),
        &context,
    );
    let presence = TenantPresence::from_context(
        Arc::new(PresenceTracker::new()),
        &context,
    );

    let mut receiver = realtime.subscribe("course/42")?;
    presence.user_joined("course/42", "learner-7")?;
    realtime.publish("course/42", "lesson.completed", r#"{"lesson_id":9}"#)?;

    assert_eq!(presence.count_online("course/42")?, 1);
    assert!(receiver.try_recv().is_ok());
    Ok(())
}
}

The tenant wrapper limits names and payloads (64 KiB), but the application still owns authentication, room-level authorization, connection lifecycle and replay. Use rullst::live::live_ws_handler for the separate per-connection LiveComponent flow, or build an Axum WebSocket handler around these primitives. A distributed transport is still roadmap work.

Tutorial 18: Wasm Island foundation

Rullst can generate a dual-target Island function: native builds emit a host element with serialized props, while wasm32-unknown-unknown builds export a hydration function.

Generate the component

cargo rullst make:island InteractiveChart

The generated src/islands/interactive_chart.rs uses the supported macro:

#![allow(unused)]
fn main() {
use rullst::island;

#[cfg(target_arch = "wasm32")]
use wasm_bindgen::{closure::Closure, JsCast};

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InteractiveChartProps {
    pub initial_value: i32,
}

#[island]
pub fn interactive_chart(props: InteractiveChartProps) {
    #[cfg(not(target_arch = "wasm32"))]
    {
        format!(
            "<button type=\"button\">Count: {}</button>",
            props.initial_value
        )
    }

    #[cfg(target_arch = "wasm32")]
    {
        let mut value = props.initial_value;
        element.set_text_content(Some(&format!("Count: {value}")));

        let button = element.clone();
        let closure = Closure::<dyn FnMut()>::new(move || {
            value = value.saturating_add(1);
            button.set_text_content(Some(&format!("Count: {value}")));
        });
        if element
            .add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
            .is_ok()
        {
            closure.forget();
        }

        String::new()
    }
}
}

The element binding in the Wasm block is supplied by #[island].

Build and load the artifact

cargo rullst build:client

The command parses Cargo.toml, adds cdylib to the existing [lib] crate-type array when needed, installs/checks the Wasm target and wasm-bindgen-cli, builds the library, locates the artifact using lib.name or package.name, writes bindings under static/, and generates a hydration orchestrator. Review these manifest, network, and toolchain side effects in CI and pin the required tools for reproducible releases. Load the generated ES module from the page as instructed by the command output.

This is a useful foundation, not a complete frontend framework: routing, application state, accessibility, CSP-compatible asset delivery, cache busting, error reporting and browser E2E remain application/release work.

Tutorial 19: SaaS billing with Rullst Capital 💳

This tutorial creates a checkout adapter boundary and explains how to feed the optional local Studio view without treating it as an accounting system.

1. Scaffold and configure the selected adapter

cargo rullst make:billing --model Workspace

The command detects a relational SQLx or Turso-primary project, adds the exact orm and capital facade features once, generates a reversible matching migration, registers the models/controller/page modules, and refuses to overwrite an earlier billing scaffold. The generated runtime supports the selected stripe or lemonsqueezy adapter. It deliberately does not imply support for every Capital adapter or mount application routes without review.

Set BILLING_ALLOWED_PLAN_IDS to the exact comma-separated provider price or variant IDs the server may accept. Production startup rejects a missing allowlist; a query-string plan outside it is denied before creating a billing customer.

Use the exact environment names emitted by the generated files and keep live credentials outside source control. Credentials beginning with mock_ select a documented deterministic offline path; they are not accepted by the production-safe webhook middleware.

2. Create a checkout session

#![allow(unused)]
fn main() {
use rullst_capital::{init_provider, provider, StripeProvider};

async fn checkout_url() -> Result<String, String> {
    let api_key = std::env::var("STRIPE_SECRET_KEY")
        .map_err(|error| format!("missing Stripe key: {error}"))?;
    let webhook_secret = std::env::var("STRIPE_WEBHOOK_SECRET")
        .map_err(|error| format!("missing webhook secret: {error}"))?;

    init_provider(Box::new(StripeProvider::new(api_key, webhook_secret)));
    let selected = provider().ok_or_else(|| "billing provider is not configured".to_string())?;
    selected
        .create_checkout_session(
            "customer@example.com",
            "price_pro_monthly",
            "https://app.example/billing/success",
        )
        .await
        .map_err(|error| error.to_string())
}
}

The scaffold requires an authenticated BillingIdentity for checkout/portal, enforces that server-owned plan allowlist, and rejects subscription reuse across owners. The application still owns the identity middleware, correct plan configuration, return-URL policy, durable provider-event idempotency/reconciliation, and provider sandbox validation.

2.1 Handle provider failure without blindly repeating a charge

Built-in live adapters return a redacted CapitalError::Provider for outbound request construction, transport, HTTP status, bounded-response, JSON, and response-contract failures. Inspect its stable class for telemetry or a durable job decision:

#![allow(unused)]
fn main() {
use rullst_capital::{CapitalError, ProviderFailureClass};

fn provider_disposition(error: &CapitalError) -> Option<ProviderFailureClass> {
    match error {
        CapitalError::Provider(failure) => Some(failure.class()),
        _ => None,
    }
}
}

Do not turn Transient or RateLimited into an unconditional loop. Persist the original command and idempotency identity, confirm that the selected operation actually forwards that identity, cap attempts with backoff/jitter, and reconcile signed provider events. Checkout creation in the legacy unified trait does not accept an application idempotency key, so reconcile before repeating it. The shared client already applies finite timeouts, disables redirects and ambient proxies, caps JSON to one MiB, and validates returned checkout URLs as absolute credential-free HTTPS.

3. Make a bounded immediate charge when checkout is not the right flow

For a payment method already tokenized and authorized for off-session reuse at Stripe, the model deriving Billable can perform one fully specified charge:

#![allow(unused)]
fn main() {
use rullst_capital::{Billable as _, CapitalError, StripeProvider};

async fn charge_saved_method(
    account: &(impl rullst_capital::Billable + Sync),
    stripe: &StripeProvider,
) -> Result<String, CapitalError> {
    let receipt = account
        .charge_with(
            stripe,
            2_500,
            "BRL",
            "cus_from_authoritative_state",
            "pm_from_authoritative_state",
            "order_2026_0001-attempt_1",
        )
        .await?;
    Ok(receipt.charge_id().to_string())
}
}

This deliberately is not charge(amount): currency, provider customer, tokenized payment method and retry identity cannot be inferred safely. The Stripe adapter uses Payment Intents with immediate off-session confirmation and the provider idempotency header. Only succeeded and processing are accepted; an amount/currency mismatch or a flow requiring customer action fails closed. Credentials beginning with mock_ return the same deterministic receipt with the distinct non-success ChargeStatus::Mock for an exact retry. The mock is not a mandate, durable idempotency store or live sandbox test. Other adapters return UnsupportedOperation until reviewed individually.

4. Render and deliver the invoice only after final success

Enable capital-mail on the umbrella crate (or invoice-pdf on Capital plus capital-invoice on Mail). Build the invoice from authoritative order state, then bind it to the returned receipt:

#![allow(unused)]
fn main() {
use chrono::Utc;
use rullst::capital::{ChargeReceipt, Invoice, InvoiceItem};
use rullst::mail::PaidInvoiceDelivery;

async fn deliver_invoice(receipt: &ChargeReceipt) -> Result<(), Box<dyn std::error::Error>> {
    let invoice = Invoice {
        invoice_id: "INV-2026-0001".to_string(),
        customer_email: "customer@example.com".to_string(),
        date: Utc::now(),
        items: vec![InvoiceItem {
            description: "Pro subscription".to_string(),
            amount: 25.00,
        }],
        total: 25.00,
        currency: "BRL".to_string(),
    };

    let paid = invoice.bind_succeeded_charge(receipt)?;
    let delivery = PaidInvoiceDelivery::prepare(&paid)?;

    // In production, atomically claim this stable key in a durable outbox.
    let _delivery_key = delivery.delivery_key();
    delivery.send().await?;
    Ok(())
}
}

The binding rejects Processing, Mock, a mismatched recipient, amount or currency. The default PDF is paginated, bounded to sixteen MiB and supports WinAnsi text (including common Portuguese characters); pass a checked TTF/OTF to Capital for other scripts. Mail applies its mandatory pre-flight before the facade queues or sends the HTML message and attachment.

This helper does not subscribe to webhooks by itself. Reconcile the provider event, build the authoritative invoice and insert delivery_key under a unique database constraint in the same application workflow. Mail delivery remains at least once: a crash and retry can still require provider/application deduplication.

5. Report metered usage without confusing provider identities

Use the provider-specific static trait for new code. Stripe Meter Events need a customer ID and configured event name, not a subscription-item ID:

#![allow(unused)]
fn main() {
use rullst_capital::{
    CapitalError, MeteredBillingProvider as _, StripeMeterEvent, StripeProvider,
};

async fn report_ai_exercises(stripe: &StripeProvider) -> Result<(), CapitalError> {
    let event = StripeMeterEvent::new(
        "cus_from_authoritative_state",
        "ai_exercises",
        3,
        "usage:school-7:attempt-99",
    )?;
    let receipt = stripe.report_metered_usage(&event).await?;
    assert_eq!(receipt.quantity(), 3);
    Ok(())
}
}

Lemon Squeezy instead needs its numeric subscription-item relationship and an aggregation action:

#![allow(unused)]
fn main() {
use rullst_capital::{
    CapitalError, LemonSqueezyProvider, LemonSqueezyUsageAction,
    LemonSqueezyUsageRecord, MeteredBillingProvider as _,
};

async fn report_lesson_minutes(
    lemon: &LemonSqueezyProvider,
) -> Result<(), CapitalError> {
    let record = LemonSqueezyUsageRecord::new(
        "42",
        "lesson_minutes",
        15,
        LemonSqueezyUsageAction::Increment,
        "usage:school-7:lesson-session-123",
    )?;

    // Atomically claim record.event_key() in a durable outbox before this call.
    let receipt = lemon.report_metered_usage(&record).await?;
    assert_eq!(receipt.quantity(), 15);
    Ok(())
}
}

Use Increment only with a sum-of-usage aggregation and Set only with the matching latest-value aggregation. Stripe receives the identifier but enforces it only within a rolling window. Lemon’s request does not receive the application event key at all, so durable application deduplication is mandatory. The adapters cap and bind responses, while provider sandbox/live acceptance, retry, invoice reconciliation and entitlement updates remain release and application work. Empty or mock_* API keys produce a deterministic UsageStatus::Mock, never billable evidence.

6. Verify webhooks before business processing

Mount rullst_capital::verify_webhook on the exact provider callback route as shown in the Capital crate guide. Apply any CSRF exemption only to that exact signed route. Never update access or subscription state from an unverified request.

The default replay store protects one process. For multiple processes, rullst-capital/webhook-sql provides a bounded SQL ledger for SQLite, PostgreSQL, MySQL, and MariaDB. Its middleware form claims the signed payload before dispatch and therefore does not guarantee exactly-once processing after a crash. When a subscription mutation must be atomic, use the provider’s verified stable event ID with SqlWebhookReplayStore::check_and_record_event_key_with_transaction in the same database transaction as that mutation; do not also pre-claim that event through SQL middleware. See the payment guide for setup and operational boundaries.

7. Use a bounded subscription handle and grace period

#![allow(unused)]
fn main() {
use rullst_capital::{Billable as _, CapitalError, GracePeriod, StripeProvider};

async fn pause_with_local_policy(
    workspace: &impl rullst_capital::Billable,
    provider: &StripeProvider,
) -> Result<(), CapitalError> {
    let grace = GracePeriod::new(1_900_000_000, 1_900_604_800)?;
    let handle = workspace
        .subscription_with(provider)?
        .with_grace_period(grace);
    handle.pause().await
}
}

The grace value does not schedule the pause or grant access by itself. Persist it with authoritative subscription state, evaluate it against a trusted clock inside the entitlement check, and confirm the selected adapter’s live pause or cancel semantics.

The same statically dispatched handle validates coupon IDs and gives the historical relative-trial API its intended meaning:

#![allow(unused)]
fn main() {
use rullst_capital::{Billable as _, CapitalError, StripeProvider};

async fn grant_a_retention_offer(
    workspace: &impl rullst_capital::Billable,
    stripe: &StripeProvider,
    command_created_at: i64,
) -> Result<(), CapitalError> {
    let subscription = workspace.subscription_with(stripe)?;
    subscription.apply_coupon("RETENTION_25").await?;

    // Fifteen whole days. Persist command_created_at and reuse it on retries.
    subscription
        .extend_trial_days_at(15, command_created_at)
        .await
}
}

extend_trial(15) uses the current UTC time for interactive convenience. Workers should persist their command time and use extend_trial_days_at so a retry sends the identical expiration; set_trial_end is the explicit absolute timestamp operation. Stripe has the reviewed live coupon path. Lemon Squeezy discount codes belong to checkout and therefore fail explicitly when applied to an existing live subscription; both Stripe and Lemon Squeezy have reviewed trial-update protocol fixtures. Authorize the subscription owner before building the handle, serialize conflicting updates, and reconcile the signed provider webhook because trial changes can affect billing anchors and charges.

8. Enforce one shared workspace quota before creation

Enable quota-sql directly, or capital-quota-sql on the umbrella crate. The authenticated middleware must first establish the active TenantContext; do not build a billing subject from an arbitrary header or request field.

Billable::quota_request reads the limit from the subscription owner’s tier_limit implementation. Give every attempted creation a stable event key, normally the ID of the application command/request rather than a random value generated on every retry.

#![allow(unused)]
fn main() {
use rullst::{
    capital::{Billable as _, BillingSubject, QuotaError, SqlQuotaStore},
    security::TenantContext,
};

async fn create_project(
    workspace: &impl rullst::capital::Billable,
    tenant: &TenantContext,
    quotas: &SqlQuotaStore,
    project_id: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
    let subject = BillingSubject::from_tenant(tenant)?;
    let request = workspace.quota_request(
        subject,
        "projects",
        format!("create-project:{project_id}"),
        1,
    )?;

    let mut transaction = quotas.pool().begin().await?;
    let grant = match quotas
        .reserve_with_transaction(&mut transaction, &request)
        .await
    {
        Ok(grant) => grant,
        Err(QuotaError::LimitExceeded { .. }) => {
            transaction.rollback().await?;
            return Ok(false);
        }
        Err(error) => {
            transaction.rollback().await?;
            return Err(error.into());
        }
    };

    if grant.is_replay() {
        transaction.rollback().await?;
        return Ok(true);
    }

    let inserted = rullst::orm::sqlx::query(
        "INSERT INTO projects (id, workspace_id) VALUES (?, ?)",
    )
    .bind(project_id)
    .bind(tenant.tenant_id.as_str())
    .execute(&mut *transaction)
    .await;
    if let Err(error) = inserted {
        transaction.rollback().await?;
        return Err(error.into());
    }
    transaction.commit().await?;
    Ok(true)
}
}

The placeholder above is SQLite/MySQL syntax; use $1, $2 for a raw PostgreSQL insert, or use the ORM operation that participates in the same transaction. SqlQuotaStore uses a unique event claim plus a conditional counter update, so concurrent members cannot both pass the last available unit. An exact retry returns is_replay() without consuming again; reusing the same key with different units or a different limit fails closed.

For work that cannot share the SQL transaction, QuotaGate::execute still blocks the callback before an over-limit/replayed operation and releases the reservation after an ordinary callback error. A process crash between a standalone reservation and the external side effect is intentionally conservative and needs application reconciliation; the framework never risks exceeding the quota to guess whether that external effect happened.

9. Supply an optional local revenue snapshot

RevenueDashboardManager does not derive money or subscribers from event names. After durable reconciliation, the application may call update_metrics with its authoritative snapshot and record_event with a bounded inspection record. The standalone Studio can display that process-local source at /studio/capital when it is explicitly connected. It neither auto-discovers webhook routes nor auto-syncs the application database.

Provider-hosted checkout normally keeps card collection away from the application, but the final data boundary depends on the selected provider flow, application logs, analytics, and deployment.

Tutorial 20: Background Jobs & Task Queues ⚙️

Rullst Core provides named JSON jobs, SQLite and Redis storage drivers, and a bounded-concurrency worker. Job handlers are closures registered against a stable name; there is no Job trait or process-global Queue::dispatch API.


Step 1: Scaffold a handler module

cargo rullst make:worker EmailWorker

This creates src/workers/email_worker.rs, registers it in src/workers/mod.rs, and provides start_workers. Replace the generated log-only body with the application operation. Avoid logging the complete job payload, because it can contain personal or secret data.


Step 2: Create a queue and keep its worker handle alive

#![allow(unused)]
fn main() {
use rullst::queue::{Queue, QueueError, Worker, WorkerHandle};
use serde_json::json;

fn start_email_worker(queue: &Queue) -> Result<WorkerHandle, QueueError> {
    let mut worker = Worker::new(queue)
        .max_concurrency(8)
        .poll_interval(250);
    worker.register("email", |payload| async move {
        let recipient = payload
            .get("recipient")
            .and_then(|value| value.as_str())
            .ok_or_else(|| {
                std::io::Error::new(std::io::ErrorKind::InvalidData, "missing recipient")
            })?;

        // Call an idempotent mail service with a stable delivery key here.
        let _ = recipient;
        Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
    });
    worker.run()
}

async fn configure() -> Result<(Queue, WorkerHandle), QueueError> {
    let queue = Queue::sqlite("sqlite://queue.db?mode=rwc").await?;
    let handle = start_email_worker(&queue)?;
    queue
        .dispatch(
            "email",
            json!({"recipient": "learner@example.test", "delivery_key": "welcome:42"}),
        )
        .await?;
    Ok((queue, handle))
}
}

Dropping WorkerHandle stops processing. On graceful shutdown, call handle.shutdown().await and inspect its typed error.

Use Queue::redis(redis_url) with the queue-redis feature when independent processes must share work. SQLite is durable local state and supports atomic claims, but it is not a distributed queue.


Failure, retry, and delivery semantics

  • A handler error, panic, or timeout is recorded as failed. Core does not automatically retry it with exponential backoff.
  • retry_failed_job(job_id) explicitly returns a failed job to pending state.
  • SQLite and Redis recover stale processing leases and support scheduled jobs, but execution time is the first worker poll after the due timestamp.
  • Queue processing is at least once around crashes and external side effects. Give provider operations stable idempotency keys and reconcile ambiguous outcomes.
  • Successful payloads are deleted by default. SQLite completed history is an explicit bounded opt-in with its own privacy/access-control obligations.

See the Redis Architecture Guide for deployment boundaries.

Tutorial 21: Explicit Local AI with Ollama 🤖

Use an explicit OllamaProvider when a workload must not fall through to a configured cloud provider. AiClient::auto() is a convenience fallback chain; it is not an isolation policy when cloud API keys are also present.


Step 1: Configure the local endpoint

OLLAMA_HOST=http://127.0.0.1:11434
OLLAMA_MODEL=llama3:8b

Bind Ollama to loopback or a controlled private interface. Pull and license the chosen model through a reviewed provisioning step rather than silently during a request.


Step 2: Construct the provider explicitly

#![allow(unused)]
fn main() {
use rullst_ai::ai::{AiClient, AiError, providers::ollama::OllamaProvider};

pub async fn summarize_lesson(text: &str) -> Result<String, AiError> {
    let host = std::env::var("OLLAMA_HOST")
        .map_err(|_| AiError::ConfigError("OLLAMA_HOST is required".to_string()))?;
    let model = std::env::var("OLLAMA_MODEL")
        .map_err(|_| AiError::ConfigError("OLLAMA_MODEL is required".to_string()))?;
    let client = AiClient::new(OllamaProvider::new(host, model));

    client
        .prompt(&format!(
            "Summarize this lesson in three factual bullet points:\n{text}"
        ))
        .await
}
}

The high-level client applies Rullst’s mandatory prompt/PII guardrails before dispatch. Model output remains untrusted: escape it for HTML, validate structured data, and never use an LLM verdict as the only authentication, authorization, malware, or abuse-control decision.


Isolation checklist

  • Confirm the resolved endpoint, host firewall, container network, DNS, proxy, and telemetry configuration.
  • Remove cloud-provider credentials from the process when policy requires local only; an explicit provider prevents fallback but least privilege still helps.
  • Review application/Ollama logs, model caches, backups, crash dumps, and swap for sensitive data.
  • Empty or mock_* Ollama hosts select deterministic offline mock behavior; a mock response is test evidence, not evidence that a live model ran.

“Local” does not prove an air gap. Verify the complete deployed data flow.

22. RAG Systems & Vector Search

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

Rullst provides three deliberately separate vector paths: a deterministic in-memory index in rullst-ai, parameterized PostgreSQL pgvector queries, and a bounded Qdrant HTTP adapter in rullst-orm. None silently invent tenant authorization, context budgets, an embedding model, or a production RAG policy.

In-memory retrieval

VectorIndex is useful for bounded local datasets and tests:

[dependencies]
rullst = { version = "12.0.0-rc.1", default-features = false, features = ["ai"] }
serde_json = "1.0"
#![allow(unused)]
fn main() {
use rullst::ai::VectorIndex;

let mut index = VectorIndex::new();
index.add(
    "rullst",
    vec![1.0, 0.0, 0.0],
    serde_json::json!({"text": "Rullst is a Rust framework suite."}),
);
index.add(
    "other",
    vec![0.0, 1.0, 0.0],
    serde_json::json!({"text": "An unrelated document."}),
);

let matches = index.search(&[0.9, 0.1, 0.0], 3);
assert_eq!(matches[0].1.id, "rullst");
}

The caller supplies the embedding and the limit. This process-local index is not durable, distributed, tenant-aware, or an approximate-nearest-neighbor service.

PostgreSQL + pgvector

Enable the typed vector and concrete PostgreSQL paths:

[dependencies]
rullst = { version = "12.0.0-rc.1", default-features = false, features = [
  "orm-pgvector",
  "strict-postgres",
  "ai",
] }

Install the extension through a reviewed PostgreSQL migration and choose the dimension used by your embedding provider:

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE knowledge_chunks (
    id SERIAL PRIMARY KEY,
    tenant_id TEXT NOT NULL,
    content TEXT NOT NULL,
    embedding vector(1536) NOT NULL
);

The model can use the re-exported typed Vector:

#![allow(unused)]
fn main() {
use rullst::orm::{FromRow, Orm, Vector};

#[derive(Clone, Debug, FromRow, Orm)]
#[orm(table = "knowledge_chunks", tenant_column = "tenant_id")]
struct KnowledgeChunk {
    id: i32,
    tenant_id: String,
    content: String,
    embedding: Vector,
}
}

Generate the query embedding through a configured guarded AI client, establish the authenticated tenant scope, and then use the native operators:

#![allow(unused)]
fn main() {
use rullst::ai::AiClient;
use rullst::orm::{FromRow, Orm, Vector, with_tenant};

#[derive(Clone, Debug, FromRow, Orm)]
#[orm(table = "knowledge_chunks", tenant_column = "tenant_id")]
struct KnowledgeChunk {
    id: i32,
    tenant_id: String,
    content: String,
    embedding: Vector,
}

async fn retrieve(
    client: &AiClient,
    question: &str,
) -> Result<Vec<KnowledgeChunk>, Box<dyn std::error::Error>> {
let embedding = client.embed(question).await?;
let query: Vec<f64> = embedding.iter().map(|value| f64::from(*value)).collect();

let chunks = with_tenant("tenant-42", async move {
    KnowledgeChunk::query()
        .where_similar("embedding", query.clone(), 0.8)
        .order_by_cosine_distance("embedding", query)
        .limit(5)
        .get()
        .await
})
.await?;
Ok(chunks)
}
}

Vector and distance values are SQL bindings, not interpolated literals. Column names use the normal identifier validation, vectors must be finite/non-empty, and the distance must be finite and non-negative. The pgvector feature also supplies SQLx encode/decode for the typed field. Rullst’s live contract creates the extension, inserts typed vectors, and executes L2 and cosine queries against a digest-pinned pgvector/pgvector container.

Qdrant dense-vector store

Use Qdrant when the application deliberately chooses a specialized external vector service rather than keeping vectors in PostgreSQL:

[dependencies]
rullst = { version = "12.0.0-rc.1", default-features = false, features = [
  "orm-qdrant",
  "ai",
] }
serde_json = "1.0"
#![allow(unused)]
fn main() {
use rullst::orm::{
    QdrantConfig, QdrantStore, VectorCollectionName, VectorDimensions,
    VectorPoint, VectorQueryLimit, VectorRepository,
};
use serde_json::{Map, Value};

async fn qdrant_example() -> Result<(), Box<dyn std::error::Error>> {
let config = QdrantConfig::new(
    std::env::var("QDRANT_URL").unwrap_or_default(),
    std::env::var("QDRANT_API_KEY").unwrap_or_default(),
);
let vectors = QdrantStore::connect_or_mock(config)?;
let collection = VectorCollectionName::new("knowledge-v1")?;
vectors
    .create_collection(&collection, VectorDimensions::new(3)?)
    .await?;

let mut payload = Map::new();
payload.insert("chunk_id".into(), Value::String("chunk-42".into()));
vectors
    .upsert(
        &collection,
        VectorPoint::new(42, vec![1.0, 0.0, 0.0], payload)?,
    )
    .await?;
let matches = vectors
    .search(
        &collection,
        &[0.9, 0.1, 0.0],
        VectorQueryLimit::new(5)?,
    )
    .await?;
let _ = matches;
Ok(())
}
}

Empty or mock_* endpoint/API-key values select the deterministic in-process backend. An explicit QdrantConfig::unauthenticated_local is available only for loopback self-hosting. The live contract is deliberately limited to one unnamed dense cosine vector per numeric point. It bounds identifiers, dimensions, finite/non-zero-norm vectors, 1 MiB object payloads, top-k, request and response memory, deadlines and redirects. It does not claim named, sparse or multivectors, arbitrary filters, hosted availability, ANN index tuning or tenant authorization. A digest-pinned Qdrant lifecycle proves the supported operations.

Orchestrate one bounded RAG operation

The compatibility build_rag_prompt helper only formats already-authorized text. Prefer RagPipeline when the application needs one typed operation for embedding, retrieval, context budgets, guarded generation, source metadata, and mandatory auditing:

#![allow(unused)]
fn main() {
use rullst::ai::rag::{RagPipeline, RagRetriever};

fn compose<R, A>(client: rullst::ai::AiClient, retriever: R, audit: A)
where
    R: RagRetriever,
    A: rullst::ai::rag::RagAuditSink,
{
let pipeline = RagPipeline::new(client, retriever, audit);
let _ = pipeline;
}
}

The retriever receives a trusted tenant context and must enforce authoritative tenant and ownership predicates in its datastore query. The pipeline also rejects differently tagged documents and refuses ungrounded generation when no safe context remains. Follow the complete Tenant-Bound RAG tutorial for the offline index, production adapter boundary, and secret-minimized audit contract.

The application still owns embedding dimension/model compatibility, durable ingestion and deletion, citation evaluation, index tuning, authorization, output policy, observability, and recovery.

Tutorial 23: Process Telemetry & Prometheus Exporter 📡

Monitor process RSS memory, Tokio runtime tick latency, and active tasks using Rullst Radar (rullst::radar) and Prometheus (GET /metrics).


🛠️ Step 1: Mount Prometheus Exporter

In src/main.rs:

use axum::Router;
use rullst_core::radar::radar_metrics_router;
use rullst::Server;

#[tokio::main]
async fn main() -> Result<(), rullst_core::server::ServerError> {
    let app = Router::new()
        .merge(radar_metrics_router()); // Exposes GET /metrics

    Server::new(app.into()).run(3000).await
}

📊 Step 2: Prometheus Metrics Scrape Output

Query GET /metrics:

# HELP rullst_memory_rss_bytes Process RSS memory consumption
# TYPE rullst_memory_rss_bytes gauge
rullst_memory_rss_bytes 24510464

# HELP rullst_tokio_latency_microseconds Tokio runtime tick latency in microseconds
# TYPE rullst_tokio_latency_microseconds gauge
rullst_tokio_latency_microseconds 42

Visual dashboard available in Studio: http://localhost:5555/studio/radar.

/metrics is not authenticated by radar_metrics_router(). Restrict it with a private network, service-mesh policy, or reviewed authentication middleware; process and runtime measurements can disclose operational information.


💡 Key Takeaways

  • The response is a point-in-time local snapshot and allocates its text body.
  • Linux and Windows expose supported process RSS/CPU probes. Tokio task data is available only inside a Tokio runtime; unsupported probes are omitted.
  • The scheduler-yield observation is not a universal request-latency target.

Tutorial 24: Interactive Scalar API Documentation 📖

Scaffold a Scalar OpenAPI documentation router served at /docs.


🛠️ Step 1: Scaffold Scalar Docs

cargo rullst make:scalar

This generates src/controllers/docs_controller.rs. The application must merge the returned router; the command does not edit route registration automatically.


💻 Step 2: Mount in Application

use rullst::{Router, Server};
use rullst::scalar::scalar_docs_router;

#[tokio::main]
async fn main() -> Result<(), rullst::ServerError> {
    let app = Router::new().merge_axum(scalar_docs_router("/openapi.json"));

    Server::new(app).run(3000).await
}

Open http://localhost:3000/docs in your browser to test endpoints interactively!


💡 Key Takeaways

  • The current page loads a version-pinned Scalar asset from jsDelivr. A failed CDN load shows only a link to the OpenAPI JSON; it is not an offline interactive UI.
  • The status-only fallback prints the configured OpenAPI location as text; it does not create an executable link from the configured value.
  • A strict CSP may block the remote and inline assets. Vendor the asset and integrate the page with the application’s nonce/hash policy before using it outside local development.
  • The router reads openapi.json; if it is missing or malformed, the endpoint fails with 503 Service Unavailable instead of fabricating an empty specification. Validate the release artifact in CI.

Tutorial 25: Kubernetes Manifests & Health Probes ☸️

cargo-rullst can generate a Kubernetes starter set. The files contain project name/port defaults and must be reviewed for the target cluster, registry, secrets, storage, network policy, ingress, workload identity, and security policy before deployment.


Step 1: Generate the starter manifests

cargo rullst make:k8s

The command writes deployment.yaml, service.yaml, configmap.yaml, hpa.yaml, ingress.yaml, and all-in-one.yaml under k8s/. It may overwrite files with those names, so run it in a clean worktree and review the diff.

Replace the placeholder image: <project>:latest with an immutable registry reference (preferably a digest). The generated ConfigMap contains non-secret settings only; use a Kubernetes Secret/external secret manager for credentials.


Step 2: Mount lifecycle-aware health routes

use rullst::{ApplicationLifecycle, Router, Server};
use rullst::health::{health_router_with_lifecycle, init_health_boot_time};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    init_health_boot_time();
    let lifecycle =
        ApplicationLifecycle::with_required_components(["database", "queue"])?;

    // Set these only after the application's own bounded checks succeed.
    lifecycle.set_component_ready("database", true)?;
    lifecycle.set_component_ready("queue", true)?;

    let app = Router::new()
        .merge_axum(health_router_with_lifecycle(lifecycle.clone()));
    Server::new(app)
        .with_lifecycle(lifecycle)
        .run(3000)
        .await?;
    Ok(())
}

/health remains process-only so an external database outage does not create a restart loop. /ready returns 503 during startup, while any registered component is unavailable, during drain, and after stop. The JSON carries only aggregate counts; it never emits the component labels or their error messages.

The component registry is immutable, accepts at most 32 validated labels, and does not execute probes by itself. The application must perform bounded, timeout-protected checks and update each bit. Server marks startup complete after binding, closes new application admission before graceful shutdown, and waits through Axum for accepted requests. run_with_shutdown accepts a supervisor future when OS signals are not the desired trigger.

This is one process contract. Kubernetes removes an unready Pod from service according to its own timing; Rullst does not coordinate replica consensus, load-balancer propagation, dependency failover, preStop, or the Pod’s terminationGracePeriodSeconds. Measure those together in staging. The legacy health_router() remains the simpler process-only pair when dependency-aware admission is not requested.


Step 3: validate before applying

kubectl apply --dry-run=client -f k8s/all-in-one.yaml
kubectl diff -f k8s/all-in-one.yaml
kubectl apply -f k8s/all-in-one.yaml

The generated HPA uses autoscaling/v2 and requires a working resource metrics pipeline. Validate against the actual cluster version and admission policies.


Release checklist

  • add pod/container security contexts compatible with the reviewed image user;
  • define CPU/memory requests and limits from measurements;
  • add PodDisruptionBudget, topology spread/anti-affinity, NetworkPolicy, and service account/workload identity as required;
  • terminate TLS with a configured issuer and real hostname;
  • provision durable storage only where application state truly needs it; and
  • verify probes, graceful termination, migrations, rollback, and autoscaling in a staging cluster.

Tutorial 26: Guided Cloud Deployment & Foundry SSH Pipeline 🚀

Rullst provides two deployment scaffolds that require provider credentials, application-specific review, and rollback planning:

  1. cargo rullst deploy: guided PaaS manifest/CLI helper (Fly.io, Railway, Render, or local Docker Compose).
  2. cargo rullst foundry:deploy: reviewed SSH pipeline for a compatible systemd-based Linux VPS.

⚡ Comparison: deploy vs foundry:deploy

Featurecargo rullst deploy (PaaS)cargo rullst foundry:deploy (Foundry SSH)
Primary TargetManaged Cloud (Fly.io, Railway, Render)Cloud VPS / Bare-Metal (Hetzner, DigitalOcean, AWS, Linode)
MechanismPlatform CLI (flyctl, railway up) and manifestsSSH + SCP + systemd + an existing Caddy installation
Setup RequiredPlatform account, credentials, and CLIReviewed root or passwordless-sudo SSH access, systemd, Caddy, DNS and firewall policy
Config Filefly.toml, railway.json, render.yamlFoundry.toml (auto-gitignored)
MigrationsApplication/platform configurationNot executed by the current Foundry command
TLS CertificatesProvider configurationRequested by Caddy when DNS/network prerequisites are satisfied

🛠️ Strategy 1: PaaS Cloud Deploy Wizard (cargo rullst deploy)

Launch the interactive PaaS deployment wizard:

cargo rullst deploy

Or target a specific platform directly:

# Deploy to Fly.io (Global Edge Containers)
cargo rullst deploy --platform=fly

# Deploy to Railway (Zero-config PaaS)
cargo rullst deploy --platform=railway

# Deploy to Render (Managed Cloud Services)
cargo rullst deploy --platform=render

# Scaffold Local VPS Production Stack (Docker Compose + Caddy SSL)
cargo rullst deploy --platform=vps

🏭 Strategy 2: Rullst Foundry SSH Pipeline (cargo rullst foundry:*)

Rullst Foundry is a bounded deployment helper for compatible systemd-based Linux servers. Its current provisioning commands require root or passwordless non-interactive sudo; it is not portable to every SSH host and does not support IPv6 SCP targets.

Step 1: Initialize Foundry.toml

cargo rullst foundry:init

This generates Foundry.toml at your project root and automatically adds it to .gitignore to protect sensitive server credentials:

# Foundry.toml — Rullst Deployment Manifest
[app]
name = "my_rullst_app"
domain = "api.mycompany.com"

[server]
host = "203.0.113.50"
user = "root"
ssh_port = 22
ssh_key = "~/.ssh/id_ed25519"

[env]
RULLST_ENV = "production"
PORT = "3000"
DATABASE_URL = "sqlite:///opt/rullst/my_rullst_app/data/db.sqlite"
APP_KEY = "REPLACE_WITH_A_STRONG_RANDOM_KEY"

Step 2: Run the reviewed deployment command

cargo rullst foundry:deploy

What the current foundry:deploy does

  1. Builds the selected profile and optional target locally.
  2. Connects over SSH, checks the preinstalled curl, systemctl, and caddy executables, creates /opt/rullst/<app>/{bin,config,data}, and fails if that step fails. Foundry does not install operating-system packages and never pipes an unpinned network script into a shell.
  3. Uploads the application binary with scp to a staging path. It does not currently upload static directories or perform a separate remote checksum comparison.
  4. Writes staged environment, Caddy, systemd and binary files, validates the candidate Caddy configuration, renames each staged file, then restarts the services. A validation/reload/restart failure aborts the command. The prior binary, environment, systemd unit, and global Caddyfile are retained as .previous, but rollback is manual and the application restart is not zero-downtime. This version manages one global /etc/caddy/Caddyfile; review that replacement before using the server for multiple independently managed sites.
  5. Requires GET /health to succeed within ten bounded attempts before printing that the remote process answered locally. It does not prove public DNS, TLS, firewall, proxy, or external reachability.

SSH uses StrictHostKeyChecking=accept-new: verify the host fingerprint through an independent channel before the first connection. The command does not compare a separate remote checksum, run database migrations, back up/restore data, coordinate multiple instances, or automatically roll back a failed release.


💡 Summary & Best Practices

  • Use cargo rullst deploy when hosting on serverless container platforms (Fly.io, Railway, Render).
  • Use cargo rullst foundry:deploy only after reviewing the generated SSH, systemd, Caddy, secret, migration, backup, and rollback plan for the target VPS.

Tutorial 27: Typed Dependency Injection (rullst::di) 💉

Rullst DI uses Rust types as keys and avoids runtime reflection metadata. It is not literally zero-cost: registration uses a map, resolution performs a type-indexed lookup/downcast, and each injection clones an Arc.


Step 1: Register services without embedding secrets in source

#![allow(unused)]
fn main() {
use std::sync::Arc;
use rullst::di::Container;
use rullst::security_runtime::VaultSecret;

pub struct PaymentGateway {
    api_key: VaultSecret<String>,
}

impl PaymentGateway {
    pub fn is_configured(&self) -> bool {
        !self.api_key.expose_secret().is_empty()
    }
}

pub struct UserService {
    pub gateway: Arc<PaymentGateway>,
}

pub fn configure_di() -> Result<Arc<Container>, std::env::VarError> {
    let key = std::env::var("PAYMENT_API_KEY")?;
    let mut container = Container::new();
    let gateway = Arc::new(PaymentGateway {
        api_key: VaultSecret::new(key),
    });

    container.register_arc(Arc::clone(&gateway));
    container.register(UserService { gateway });
    Ok(Arc::new(container))
}
}

security_runtime requires the umbrella security feature. A secret manager should inject the real value in production; VaultSecret reduces accidental formatting and zeroizes its owned allocation on drop, but it is not key custody or secure memory.


Step 2: Attach the container and extract a service

use axum::{Extension, Json, routing::post};
use rullst::{Router, di::Inject};

pub async fn process_payment(
    Inject(user_service): Inject<UserService>,
) -> Json<&'static str> {
    // Use a bounded provider adapter; never log the key or full request payload.
    let _configured = user_service.gateway.is_configured();
    Json("Payment request accepted")
}

let container = configure_di()?;
let app = Router::new()
    .route("/payments", post(process_payment))
    .layer(Extension(container));

The continuation is application code because its error type and provider adapter belong to the project. If the requested type or container extension is missing, Inject<T> fails closed with a 500 rejection.


Key takeaways

  • Services are registered as type-safe Arc<T> singletons.
  • DI controls construction and lookup; it does not provide authorization, transaction boundaries, or secret management.
  • Prefer ordinary constructors when a small service graph does not benefit from a container.

Tutorial 28: gRPC Microservices with Tonic 🌐

Generate a Tonic/Protobuf starting point using cargo rullst make:grpc. The command does not start a gRPC server, add every build dependency, or define production transport/authentication policy for the application.


🛠️ Step 1: Generate a gRPC Service

cargo rullst make:grpc UserService

This generates:

  • proto/user_service.proto (Protobuf definition)
  • src/grpc/user_service.rs (Tonic service implementation)

Names ending in Service remain a single service suffix: UserService produces the user_service_server::UserService trait rather than UserServiceService.


💻 Step 2: Implement the gRPC Handler

In src/grpc/user_service.rs, after the generated application’s build.rs has compiled proto/user_service.proto and made the Tonic dependencies available:

use tonic::{Request, Response, Status};

pub mod proto {
    tonic::include_proto!("user_service");
}

use proto::user_service_server::UserService;
use proto::{HelloRequest, HelloResponse};

#[derive(Debug, Default)]
pub struct UserServiceImpl;

#[tonic::async_trait]
impl UserService for UserServiceImpl {
    async fn say_hello(
        &self,
        request: Request<HelloRequest>,
    ) -> Result<Response<HelloResponse>, Status> {
        let reply = HelloResponse {
            message: format!("Hello {} from Rullst gRPC!", request.into_inner().name),
        };
        Ok(Response::new(reply))
    }
}

💡 Key Takeaways

  • The generator creates a starting point for tonic and prost; inspect the generated service and add/review the required build.rs, dependencies, reflection/health endpoints, and server bootstrap before deployment.
  • Apply TLS or mTLS, authentication, per-method authorization, deadlines, message-size/concurrency limits, and proxy policy explicitly.
  • Network, serialization, handler, and proxy latency must be measured in the target environment. Rullst does not claim a universal latency bound.

Tutorial 29: IoT data and frame helpers (rullst-iot)

rullst-iot provides telemetry/state models and protocol frame builders that compile without std. Some APIs use alloc, so a bare-metal application must supply an allocator.

The crate does not currently read hardware registers or provide MQTT, OPC-UA, Sparkplug B, HSM, or post-quantum implementations.

Build a telemetry model and Modbus request

#![allow(unused)]
fn main() {
use rullst_iot::{AnomalyDetector, ModbusFrame, SensorTelemetry};

let telemetry = SensorTelemetry::new(
    "sensor-01",
    "temperature",
    38.5,
    1_700_000_000,
);
let detector = AnomalyDetector::new(25.0, 5.0);
let state = detector.evaluate(telemetry.value);

// This builds bytes for a request. Platform code must send them over a real
// serial/TCP transport and handle timeouts, retries, and the response.
let request = ModbusFrame::read_holding_registers(1, 0, 10);
assert_eq!(request.len(), 8);
let _ = state;
}

Signed firmware artifacts

Use OtaManifest and OtaManager::new_with_trusted_key to verify a firmware artifact before selecting an inactive partition. See the crate guide for the trust, persistence, and bootloader requirements that remain the integrator’s responsibility.

Experimental fixtures

The opt-in experimental-simulators feature contains explicitly named Simulated* fixtures. They are deterministic test data generators, not hardware or protocol implementations.

Tutorial 30: Axum Escape-Hatch Snapshot (cargo rullst eject) 🔓

eject generates a minimal Axum/Tokio entry point that can begin a manual migration away from Rullst’s server wrapper. It does not statically expand macros, copy the application’s route graph, convert middleware, or remove Rullst dependencies automatically.


Step 1: Generate a separate starting point

cargo rullst eject

The default output is src/ejected_main.rs; the existing src/main.rs remains unchanged. The generated server contains only a demonstration root route:

use axum::{routing::get, Router};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = Router::new()
        .route("/", get(|| async { "Ejected Axum server" }));
    let listener = TcpListener::bind("127.0.0.1:3000").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

Move application routes one bounded group at a time and retain equivalent security headers, CSRF/CORS/WAF order, limits, telemetry, graceful shutdown, health behavior, state, and authorization tests.


Step 2: Treat --force as a deliberate replacement

cargo rullst eject --force

The hardened command first preserves the original entry point as src/main.rs.rullst-backup and refuses to overwrite an existing backup. Keep a normal version-control commit as the authoritative recovery path. Custom output paths are restricted to relative Rust files under src/ and existing targets are not overwritten implicitly.


Key takeaways

  • Ejection is a migration aid, not a semantics-preserving compiler transform.
  • The application remains responsible for dependency cleanup and replacements for ORM, auth, queues, Studio, Nexus, Capital, AI, and other selected crates.
  • Run cargo fmt, strict Clippy, the complete test suite, and application security/operational checks after every migrated route group.

Tutorial 31: SaaS deployment preparation for AWS or GCP

This guide prepares a generated SaaS application for a cloud deployment. It is not an end-to-end production certification: identity, network policy, database operation, secrets, billing and recovery remain deployment responsibilities.

1. Materialize and verify the SaaS starter

While v12 is unreleased, use a reviewed main checkout pinned by Cargo.lock. After a prerelease ships, install the matching versioned CLI and generate deterministically:

cargo rullst new my_cloud_saas --default --blueprint saas --docker
cd my_cloud_saas
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features

Review every generated route, access policy, migration and environment placeholder before supplying live credentials.

2. Supply secrets outside source control

Use AWS Secrets Manager, Google Secret Manager or an equivalent deployment boundary for values such as DATABASE_URL, provider keys, webhook secrets and the Rullst field-encryption key. Do not bake .env into an image.

Set RULLST_ENV=production and make startup fail when a required production adapter or credential is missing. Empty and mock_* provider credentials are for deterministic offline development, not live operation.

3. Build and scan the exact image

docker build --pull --tag my-cloud-saas:<git-sha> .
docker inspect my-cloud-saas:<git-sha>

Pin the deployed image by digest. Run the application’s tests and a container scanner against the exact candidate. The CLI’s deploy command scaffolds or invokes Fly.io, Railway, Render and VPS paths; AWS App Runner/ECS and Google Cloud Run configuration remains explicit cloud work.

4. Configure the cloud boundary

For AWS or GCP, define and review:

  • private database connectivity, TLS and least-privilege credentials;
  • trusted proxy handling and the application’s external origin policy;
  • ingress authentication, rate limits, body limits and request timeouts;
  • readiness/liveness behavior and a bounded shutdown grace period;
  • immutable image rollout and a tested rollback procedure;
  • logs, metrics and alerts that avoid secrets and unnecessary PII.

Managed services have provider-specific scaling floors, quotas, cold starts and costs. Verify current provider documentation and load-test your selected region and topology rather than assuming scale-to-zero or a request rate.

5. Exercise stateful recovery

Before production traffic, rehearse migrations, backup, restore, webhook replay, field-encryption key rotation, a failed rollout and database unavailability. Health endpoints only report the checks implemented by the application; they do not create zero-downtime deployment by themselves.

Tutorial 32: Bounded security telemetry in Studio

Rullst’s honeypot, RASP and Studio security page provide local defense-in-depth signals. They are not an autonomous SOC, a universal blocker, an AI incident responder or a durable SIEM integration.

1. Compose local request controls

#![allow(unused)]
fn main() {
use axum::Router;
use rullst_security::{CspSecurityLayer, HoneypotLayer, HoneypotState, RaspSecurityLayer};
use std::net::SocketAddr;

async fn run() -> Result<(), Box<dyn std::error::Error>> {
let app = Router::new()
    // Add application routes first.
    .layer(RaspSecurityLayer)
    .layer(CspSecurityLayer)
    .layer(HoneypotLayer::new(HoneypotState::default()));

let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
axum::serve(
    listener,
    app.into_make_service_with_connect_info::<SocketAddr>(),
)
.await?;
Ok(())
}
}

ConnectInfo supplies the accepted socket peer used by the honeypot. Deployments behind a proxy must establish and test a trusted client-identity boundary; the middleware deliberately does not trust arbitrary forwarding headers.

2. Understand each signal

  • Honeypots match configured exact synthetic paths and keep bounded, expiring process-local bans.
  • RASP performs bounded heuristic inspection for selected patterns and can have false positives and negatives.
  • CSP and headers depend on the final rendered page, proxy, browser and TLS deployment.
  • SecurityStore is local telemetry. An event is not HMAC-verified or durably delivered merely because it appears in Studio.

No built-in path asks an LLM to ban a peer or automatically mounts a blocking policy. The opt-in ThreatSentinel can classify three bounded, caller-supplied aggregate patterns and issue an HMAC-authenticated, subject-bound, expiring, one-shot process-local proof-of-work challenge. The application must supply trustworthy observations and subject identity, translate the outcome into an HTTP protocol, and decide where that gate belongs. It is not AI attribution, a distributed replay store, or an autonomous ban. Sensitive automated actions still require authenticated policy, limits, durable audit and human approval where appropriate.

3. Inspect the local Studio view

Mount Studio only through its documented access capability and open /studio/security. The page renders current local telemetry and keeps unavailable sources visibly unavailable. A multi-instance deployment needs a shared, authenticated event pipeline with retry, acknowledgement, retention and dead-letter handling before it can be described as an operational SIEM.

See the Threat Radar and SOC guide and the v12 security evidence ledger for exact boundaries.

Tutorial 33: Auth-bound multi-tenancy

Rullst Core can select a tenant from a request hint only after application authentication has inserted a trusted TenantMembership. A hostname, header, query parameter or request body is never sufficient proof of membership.

1. Configure the selection layer

#![allow(unused)]
fn main() {
use axum::{Extension, Router, routing::get};
use rullst_core::{
    multitenant::{TenantConfig, TenantLayer, TenantStrategy},
    security::TenantContext,
};

async fn tenant_dashboard(Extension(tenant): Extension<TenantContext>) -> String {
    format!("Selected workspace: {}", tenant.tenant_id)
}

fn tenant_routes() -> Router {
    let selection = TenantLayer::new(TenantConfig::new(TenantStrategy::Subdomain));
    Router::new()
        .route("/dashboard", get(tenant_dashboard))
        .layer(selection)
}
}

An outer, application-owned authentication layer must first validate the session/token and insert TenantMembership::try_new(...) from trusted identity claims. Without that extension, TenantLayer returns 403 Forbidden. If the requested subdomain is not in the authenticated membership set, it also returns 403. In Axum, remember that subsequently added layers run first; test the final middleware order in-process.

TenantStrategy::Header and TenantStrategy::Parameter are also available, but they remain untrusted selection hints. Query parameters additionally leak more easily through history, referrers and access logs. The built-in subdomain parser selects the first label only for hostnames with at least three labels; custom-domain ownership and trusted-proxy normalization remain application and deployment work.

2. Bind every database query

Tenant selection does not rewrite arbitrary SQL or automatically add a tenant predicate to every ORM query. Bind the authenticated tenant explicitly:

#![allow(unused)]
fn main() {
use rullst_core::security::TenantContext;
use sqlx::{FromRow, PgPool};

#[derive(FromRow)]
struct Invoice {
    id: i64,
    tenant_id: String,
    total_minor: i64,
}

async fn list_tenant_invoices(
    pool: &PgPool,
    tenant: &TenantContext,
) -> Result<Vec<Invoice>, sqlx::Error> {
    sqlx::query_as::<_, Invoice>(
        "SELECT id, tenant_id, total_minor FROM invoices WHERE tenant_id = $1",
    )
    .bind(&tenant.tenant_id)
    .fetch_all(pool)
    .await
}
}

Use database constraints and, where appropriate, database-native row-level security as additional defense. Negative tests must prove that one tenant cannot read, update or delete another tenant’s records through every relevant route, repository, background job and administrative surface.

3. Operational boundaries

  • TenantContext is server-selected state, not a claim accepted from JSON.
  • current_tenant_id() is task-local convenience; passing an explicit TenantContext to domain/storage APIs is easier to audit.
  • TenantCache, TenantStorage, TenantRealtime and TenantPresence provide validated namespaces, but applications still own business authorization and remote provider policy.
  • Cross-process membership updates, custom-domain verification, database policy, audit retention and incident response are not automatic framework guarantees.

Tutorial 34: Live Analytics Dashboard (rullst::live & WebSockets) 📈

Build a per-connection analytics view with rullst::live. Feed it values from your own authoritative metrics source; the example below uses explicit state only to demonstrate the component lifecycle.


🛠️ Step 1: Create Analytics LiveComponent

This fragment expects the crate::live::analytics_dashboard module created in Step 1 to be registered by the generated application:

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use rullst::live::LiveComponent;
use serde_json::Value;

#[derive(Default)]
pub struct AnalyticsDashboard {
    pub revenue_mrr: f64,
    pub active_users: usize,
}

#[async_trait]
impl LiveComponent for AnalyticsDashboard {
    async fn mount(&mut self) {
        // Replace these initial values with an application-owned metrics query.
        self.revenue_mrr = 0.0;
        self.active_users = 0;
    }

    async fn handle_event(&mut self, payload: Value) {
        if let Some(event) = payload.get("event").and_then(|v| v.as_str()) {
            if event == "refresh" {
                self.revenue_mrr += 100.00;
                self.active_users += 1;
            }
        }
    }

    fn render(&self) -> String {
        format!(
            r#"<div id="analytics-dashboard" class="p-8 bg-slate-900 text-white rounded-2xl shadow-2xl border border-slate-800">
    <h2 class="text-2xl font-bold mb-6">Live Analytics Stream</h2>
    <div class="grid grid-cols-2 gap-6 mb-6">
        <div class="p-4 bg-slate-800 rounded-xl">
            <p class="text-slate-400 text-sm">MRR</p>
            <p class="text-3xl font-mono text-emerald-400">${:.2}</p>
        </div>
        <div class="p-4 bg-slate-800 rounded-xl">
            <p class="text-slate-400 text-sm">Active Users</p>
            <p class="text-3xl font-mono text-cyan-400">{}</p>
        </div>
    </div>
    <button ws-send name="event" value="refresh" class="px-6 py-3 bg-indigo-600 hover:bg-indigo-500 font-semibold rounded-xl">
        ⚡ Refresh Live Stream
    </button>
</div>"#,
            self.revenue_mrr, self.active_users
        )
    }
}
}

💻 Step 2: Render in View Page

use rullst::live::Live;
use crate::live::analytics_dashboard::AnalyticsDashboard;

pub async fn analytics_page() -> String {
    Live::mount::<AnalyticsDashboard>("/ws/analytics").await
}

Register /ws/analytics with axum::routing::get(rullst::live::live_ws_handler::<AnalyticsDashboard>) and load a pinned HTMX WebSocket extension in the page.


💡 Key Takeaways

  • Rullst owns the server-side component lifecycle; a browser transport is still required.
  • The current implementation re-renders an HTML fragment after each valid JSON event. It does not provide distributed state, replay, authorization or a metrics source automatically.

Tutorial 35: Performance measurement and resilient operation

Performance is a property of a concrete application, build, host and workload. Rullst includes Criterion microbenchmarks and production helpers, but does not promise a universal latency, throughput or availability number.

1. Build an application-specific release binary

[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
strip = "debuginfo"

Alternative linkers such as mold or lld can improve developer link time on supported hosts. target-cpu=native can improve a host-specific binary but makes it less portable; do not put it in a generally distributed artifact without an explicit CPU baseline.

2. Configure and measure the database pool

#![allow(unused)]
fn main() {
use rullst_orm::Orm;

async fn configure() -> Result<(), rullst_orm::Error> {
Orm::init_with_options(
    "postgres://app:password@127.0.0.1/app",
    20, // maximum connections
    10, // acquire timeout in seconds
)
.await?;
Ok(())
}
}

Choose limits from database capacity and measured concurrency. More connections can increase contention and resource use instead of increasing throughput.

3. Run reproducible benchmarks

cargo bench --workspace

Record at least:

  • exact commit, Rust version, features and release profile;
  • CPU, memory, operating system and power/virtualization settings;
  • database/provider version and topology;
  • warm-up, sample count, concurrency and payload distribution;
  • median and tail latency, throughput, errors and resource use.

Criterion microbenchmarks detect regressions in selected functions. They do not model a production network, database, proxy or user journey. The separate cross-framework repository currently exercises historical Rullst 4.x and is not v12 evidence until refreshed.

The public benchmark hub links the eight published groups: facade/HTTP, Core primitives, ORM, Auth, Connect, Security, AI and Capital. Nine Criterion binaries back those groups because ORM has both local and cross-ORM inputs. A crate without a microbenchmark is not automatically less mature: protocol vectors, generated-project compilation, restart/failure contracts, no_std target builds or whole-request tests can fit its dominant risk better. Add a new benchmark only with a stable operation, input, unit and interpretation.

The repository now also contains a v12 cross-ORM SQLite harness:

cargo bench -p rullst-orm --features strict-sqlite \
  --bench orm_comparison

It pins Diesel and SeaORM in the lockfile and gives all three ORMs one typed SQLite connection, separate database files, the same schema/unique index, 100 equivalent rows and identical WAL/synchronous/busy-timeout policy. Criterion measures primary-key lookup, indexed filtered lookup, count, ordered list-ten and insert/delete. This makes the input inspectable; it does not make the architectures identical. Diesel’s synchronous call path and the async Rullst/SeaORM executor paths remain part of what is measured.

The first local smoke run contradicted the old “negligible overhead versus Diesel” wording: Diesel led these five SQLite shapes. Rullst was competitive with SeaORM on reads but did not lead every operation. Keep the per-commit CI history as regression/comparison evidence and never generalize it to networked PostgreSQL/MySQL, concurrency, memory use or complete applications.

4. Test failure behavior separately

Availability requires deployment exercises: health/readiness semantics, timeouts, overload, cancellation, database failover, backup/restore, migration rollback and secret rotation. Miri, sanitizers, fuzzing and Kani are target-specific correctness tools; none measures availability or proves the absence of every memory error.

36. Assisted framework upgrades

Rullst v12 introduces a bounded upgrade transaction for existing applications. The goal is to make the safe, repeatable part a single command while refusing to guess about application data or security policy.

v12 is currently unreleased and NO-GO for production. The examples use 12.0.0-rc.1 as a placeholder for the planned first RC. Install or request that version only after it exists on crates.io.

What the command can guarantee

cargo rullst upgrade can inventory the Cargo workspace, update the Rullst release train, apply compiler suggestions, require cargo check, and restore the files it controls after a failure. It cannot prove that a database upgrade, authorization rule, provider integration or deployment still behaves correctly.

The automatic transaction owns only:

  • versioned Rullst dependencies in exact Cargo workspace manifests;
  • the root Cargo.lock produced by Cargo resolution;
  • Rust edits proposed by cargo fix;
  • a cargo check --workspace --all-targets gate using the application’s selected features.

It never runs migrations, changes secrets, invents tenant/ownership policy, opens Nexus or Studio, contacts application providers, or marks the result production-ready.

1. Prepare the application

Create a branch, make the worktree reviewable, record the old test result, and back up every database. Prove the database backup can be restored before changing the framework.

Install the exact CLI from the same release train as the target framework:

cargo install cargo-rullst --version 12.0.0-rc.1 --locked --force

The framework command does not update its own executable. This matters for v5: the already-published v5 CLI cannot gain the new v12 migration engine retroactively. Install the v12 CLI first, then run the command inside the application.

2. Inspect without writing

cargo rullst upgrade --dry-run

The plan shows every dependency edit and source finding. BLOCKER means a known old API requires attention; REVIEW means the CLI found a boundary that must be revalidated. Neither label means that unreported code is automatically safe.

For automation, request JSON:

cargo rullst upgrade --dry-run --json > upgrade-plan.json

The root object uses schema_version: "rullst.upgrade-plan.v1" and identifies the rule catalog, exact target, manifest changes, detected source majors, findings, automatic scope and mandatory manual gates. Consumers must reject an unknown schema version rather than silently interpreting it as v1.

Use an explicit target only when intentionally evaluating another release in the installed CLI’s major train:

cargo rullst upgrade --to 12.0.0-rc.2 --dry-run

The same-major restriction prevents a v12 rules engine from pretending it understands an eventual v13 migration.

3. Apply the transaction

After resolving or accepting every finding:

cargo rullst upgrade

Before the first write, the CLI snapshots Cargo workspace manifests, the root lockfile and Rust sources under:

target/rullst-upgrades/<UTC-run-id>/
├── files/
├── index.tsv
├── report.md
└── report.json

It then edits the TOML while preserving comments and relative order, runs compiler-provided fixes and executes the Cargo check gate. A failing gate restores the controlled files automatically and returns a non-zero status.

To deliberately keep a partial result for diagnosis:

cargo rullst upgrade --keep-on-failure

To restore a persisted snapshot after that mode or after an interrupted run:

cargo rullst upgrade \
  --restore target/rullst-upgrades/<UTC-run-id>

Restore accepts only a path-validated snapshot inside the current project’s target/rullst-upgrades directory. cargo clean deletes target, so retain a normal version-control commit or copy a needed diagnostic report before cleaning.

4. Finish a v5 to v12 migration

The v5 README used attribute-style routing and a server builder with no router or port. The scanner reports these markers instead of applying a global text replacement. Replace the old shape:

#[routes]
fn home() -> Response {
    // ...
}

Server::new()
    .route("/", get(home))
    .run()
    .await;

with an explicit v12 router and typed error propagation:

use rullst::{Server, response::Html, routes};

async fn home() -> Html<&'static str> {
    Html("Hello from v12")
}

#[rullst::runtime::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = routes![get("/" => home)];
    Server::new(app).run(3000).await?;
    Ok(())
}

Then follow the complete v5 → v12 guide, including feature selection, disposable database migration/rollback, explicit Nexus and Studio boundaries, provider validation and authorization negatives.

5. Run the application-owned gates

At minimum:

cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
cargo build --release

Also test the actual production feature set, restore/migrate/rollback against a production-shaped database copy, cross-user and cross-tenant denials, proxy/TLS identity, CSRF/CORS, Nexus/Studio exposure and every configured live provider.

Future upgrades such as v12 to v13

The engine and migration knowledge are intentionally separated. Each new major must ship a new CLI from that major, extend the versioned rule catalog, document the supported source baselines, and add process-level fixtures for dry-run, machine-readable output, successful application and rollback. A v13 CLI can therefore reuse the transaction while owning v13-specific rules; a v12 CLI is not allowed to guess them.

Is this unique?

No. Assisted upgrades are an established framework practice: Rails documents interactive bin/rails app:update, Angular provides ng update, and Dart offers preview/apply analysis fixes through dart fix. Microsoft’s .NET Upgrade Assistant also analyzed and changed projects, although Microsoft now marks it deprecated in favor of its modernization tooling.

Rullst’s useful distinction is the bounded composition: Cargo-workspace-aware TOML edits, a version-selected framework rule catalog, human and JSON plans, controlled snapshots, default rollback, explicit recovery and Cargo gates in one CLI flow. This is a testable design choice, not evidence that Rullst is the first or universally the best updater.

Official references:

Polyglot Persistence

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

Rullst v12 keeps explicit relational and specialized persistence contracts. SQLx Active Record supports SQLite, PostgreSQL, MySQL, and MariaDB. The bounded blank/API profile can instead use Turso/libSQL as its typed primary ORM. MongoDB is for documents, DuckDB is for embedded OLAP/analytics, SurrealDB exposes documents plus bounded read-only graph queries, Qdrant exposes dense vector search, and Redis exposes selected native structures. No optional adapter silently changes the application’s primary database.

Choose the smallest feature

Using the umbrella crate:

[dependencies]
rullst = { version = "12.0.0-rc.1", features = ["orm-mongodb"] }

Available umbrella features are orm-turso, orm-mongodb, orm-duckdb, orm-surrealdb, orm-qdrant, orm-redis, and the convenience orm-polyglot. Direct rullst-orm users select turso, mongodb, duckdb, surrealdb, qdrant, redis, or polyglot.

Features are additive. Prefer one adapter unless the application genuinely uses several, and inspect the final dependency graph with:

cargo tree -e features

The project wizard first asks for a primary backend. SQLite, PostgreSQL, MySQL, and MariaDB use SQLx; Turso is available as a typed primary for the blank/API starter. The wizard then offers explicit additive capability adapters. For a hybrid SQLx application, use one or more explicit flags:

cargo rullst new edge_app --default --database mariadb --turso --mongodb \
  --qdrant --skip-initial-migration

MariaDB shares the SQLx MySQL protocol implementation but has its own live container contract. --turso is additive; --database turso is the explicit primary selection and currently rejects SQLx-specific non-blank blueprints.

Turso / libSQL as the primary ORM

cargo rullst new edge_app --default --api --database turso
cd edge_app
cargo rullst make:model Event --migration
cargo rullst db:migrate

The generated manifest enables orm-turso without a direct SQLx dependency, and the environment contains TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and TURSO_OFFLINE_PATH rather than a fictitious DATABASE_URL. Models use the same public derive with an explicit backend:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, rullst_orm::Orm)]
#[orm(table = "events", backend = "turso")]
struct Event {
    id: i64,
    label: String,
    active: bool,
}
}

Initialize once before using the generated inherent methods:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, rullst_orm::Orm)]
#[orm(table = "events", backend = "turso")]
struct Event {
    id: i64,
    label: String,
    active: bool,
}
async fn use_turso_model() -> Result<(), Box<dyn std::error::Error>> {
rullst_orm::polyglot::TursoOrm::init_from_env().await?;

let mut event = Event {
    id: 0,
    label: "started".into(),
    active: true,
};
event.save().await?;
let current = Event::find(event.id).await?;
let _ = current;
Ok(())
}
}

The typed contract includes CRUD, equality filters, ordering, bounded pagination/counts, app-assigned or generated primary keys, checksummed migrations, status, and rollback. Generated make:model and make:migration commands retain the Turso backend. It does not yet provide SQLx ORM relations, hooks, automatic timestamps, seed generation, schema auto-diff, or transparent embedded-replica synchronization. Those limits are why only the blank/API starter is currently advertised as Turso-primary. The derive rejects unsupported SQLx-specific model behaviors at compile time instead of silently ignoring their attributes.

Turso / libSQL explicit edge SQL

#![allow(unused)]
fn main() {
use rullst_orm::polyglot::{
    TursoConfig, TursoMigration, TursoQueryLimit, TursoStatement, TursoStore,
    TursoValue,
};

async fn use_edge_sql() -> Result<(), Box<dyn std::error::Error>> {
let config = TursoConfig::new(
    std::env::var("TURSO_DATABASE_URL").unwrap_or_default(),
    std::env::var("TURSO_AUTH_TOKEN").unwrap_or_default(),
);
let edge = TursoStore::connect(config).await?;
let create = TursoStatement::new(
    "CREATE TABLE events (id INTEGER PRIMARY KEY, label TEXT NOT NULL)",
    vec![],
)?;
edge.migrate(vec![TursoMigration::new("m20260829_events", vec![create])?])
    .await?;

edge.execute(TursoStatement::new(
    "INSERT INTO events VALUES (?1, ?2)",
    vec![TursoValue::Integer(1), TursoValue::Text("started".into())],
)?).await?;
let rows = edge.query(
    TursoStatement::new(
        "SELECT id, label FROM events WHERE id = ?1",
        vec![TursoValue::Integer(1)],
    )?,
    TursoQueryLimit::new(100)?,
).await?;
let _ = rows;
Ok(())
}
}

The live path speaks the official Hrana HTTP v3 protocol directly through the workspace’s Rustls-backed HTTP client; it does not embed the native SQLite engine or the legacy remote SDK dependency chain. URLs must use libsql:// or HTTPS; cleartext HTTP requires an explicit loopback-development opt-in. Requests reject redirects and have a 30-second deadline. Tokens and endpoint details are redacted from Debug; remote responses are capped at 16 MiB, result materialization at 1–10,000 rows, each statement at 1,024 positional parameters/8 MiB of parameter data, and each transaction at 1,024 statements/16 MiB of raw SQL plus parameters. SQL result cells can still be large, so applications must also constrain selected columns and data at the schema/query boundary.

An empty or mock_* endpoint selects a single-connection SQLite in-memory fallback. It executes real SQL and migrations deterministically but does not simulate remote replication, latency, failover, or Turso Cloud. The live CI contract runs the same API against the official sqld container and proves that a failed multi-statement batch rolls back its earlier writes.

The portable document contract

MongoDB, SurrealDB, and the deterministic offline store implement the same bounded document operations:

#![allow(unused)]
fn main() {
use rullst_orm::polyglot::{
    CollectionName, DocumentId, DocumentPage, DocumentRepository,
};

fn bounded_document_inputs() -> Result<(), Box<dyn std::error::Error>> {
let collection = CollectionName::new("audit_events")?;
let id = DocumentId::new("event-2026-0001")?;
let page = DocumentPage::new(0, 50)?;
let _ = (collection, id, page);
Ok(())
}
}

Collection names use a portable ASCII identifier grammar, IDs are bounded to letters, digits, _ and -, and every list requires a 1–500 row page. Models must serialize as objects and must not own the driver ID field (_id for MongoDB, id for SurrealDB); the portable DocumentId owns that concern.

Encrypted export and crash-resumable restore

MongoDB, SurrealDB and the deterministic store also implement DocumentInventory<T>, which retains each validated identifier. That enables one deliberately conservative recovery path without pretending the engines share transactions:

#![allow(unused)]
fn main() {
use rullst_orm::polyglot::{
    CollectionName, DocumentRecoveryBinding, DocumentRecoveryKey,
    DocumentRecoveryPolicy, export_document_snapshot,
    restore_document_snapshot,
};

async fn recovery<S, D, Event>(source: &S, destination: &D) -> Result<(), Box<dyn std::error::Error>>
where
    S: rullst_orm::polyglot::DocumentInventory<Event>,
    D: rullst_orm::polyglot::DocumentInventory<Event>,
    Event: serde::Serialize + serde::de::DeserializeOwned + Send + Sync,
{
let collection = CollectionName::new("audit_events")?;
let binding = DocumentRecoveryBinding::try_new(
    "my_application.production",
    collection,
)?;
let key = DocumentRecoveryKey::try_new(
    "recovery-2026-09",
    [42_u8; 32], // load 32 random bytes from a secret manager
)?;
let policy = DocumentRecoveryPolicy::try_new(100, 10_000, 16 * 1024 * 1024)?;

// Persist this opaque envelope in application-owned durable storage.
let snapshot = export_document_snapshot(source, &binding, &key, policy).await?;
let report = restore_document_snapshot(
    destination,
    &snapshot,
    &binding,
    &key,
    policy,
).await?;
assert_eq!(report.verified(), report.inserted() + report.replayed());
Ok(())
}
}

The key uses AES-256-GCM and a fresh nonce; its authenticated data binds the rotation ID, trusted application namespace and exact collection. Key material passes through a zeroizing temporary and key/snapshot Debug output is redacted. The policy permits pages of 1–500, 1–100,000 documents and a 1 KiB–64 MiB plaintext ceiling. Models in this portability path must be JSON objects without either id or _id.

Export scans twice and refuses to seal unequal observations. This detects ordinary concurrent changes, but it is not a database snapshot isolation primitive: pause writers or use a source-side transaction/export facility. Restore accepts only an empty destination or a matching subset left by an earlier attempt. It inserts without replacement, rejects different or extra rows before mutation and verifies an exact final inventory. A failed attempt can retain earlier successful inserts, so retry the same authenticated snapshot. Keep unrelated destination writers paused until verification. The destination database, namespace and collection/table must already be provisioned where the engine requires them; schema or permission failures stay visible and are never reclassified as an empty destination.

The live release matrix exercises MongoDB → SurrealDB → MongoDB. It proves the bounded adapter contract on that runner, not backup retention, key custody, replication, point-in-time recovery, topology failover or vendor operations.

MongoDB

#![allow(unused)]
fn main() {
use rullst_orm::polyglot::{
    CollectionName, DocumentId, DocumentRepository, MongoDbStore,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct AuditEvent {
    action: String,
}

async fn store_audit_event() -> Result<(), Box<dyn std::error::Error>> {
let store = MongoDbStore::<AuditEvent>::connect_or_mock(
    std::env::var("MONGODB_URL").unwrap_or_default(),
    "my_application",
).await?;

store.create(
    &CollectionName::new("audit_events")?,
    &DocumentId::new("event-1")?,
    &AuditEvent { action: "login".into() },
).await?;
Ok(())
}
}

An empty URL or one beginning with mock_/mock:// selects an in-process, deterministic BTreeMap store. A non-mock URL uses the official MongoDB Rust driver. The release contract includes an optional Testcontainers CRUD suite; availability, backups, indexes, topology and authorization remain deployment responsibilities.

SurrealDB documents and graph reads

#![allow(unused)]
fn main() {
use rullst_orm::polyglot::{
    GraphQuery, GraphRepository, SurrealAuth, SurrealConfig, SurrealDbStore,
};

async fn read_graph() -> Result<(), Box<dyn std::error::Error>> {
let config = SurrealConfig::new(
    std::env::var("SURREALDB_URL").unwrap_or_default(),
    "main",
    "application",
    SurrealAuth::bearer(
        std::env::var("SURREALDB_TOKEN").unwrap_or_default(),
    ),
);
let store = SurrealDbStore::<serde_json::Value>::connect_or_mock(config)?;
let query = GraphQuery::read_only(
    "MATCH (person:person)-[knows:knows]->(friend:person) RETURN friend",
    100,
)?;
let rows = store.query_graph(&query).await?;
let _ = rows;
Ok(())
}
}

The adapter uses SurrealDB’s documented HTTP /key, /sql, and /gql protocol instead of embedding its BSL-licensed SDK. It disables redirects, requires HTTPS outside loopback unless cleartext is explicitly enabled, redacts authentication in Debug, streams through a configurable 1 KiB–8 MiB memory ceiling, and sends namespace/database headers on every scoped request.

Graph queries must start with MATCH; semicolons, caller-provided LIMIT, and the INSERT, SET, REMOVE, and DELETE tokens are rejected. The adapter then appends its own 1–1,000 row limit. This is a conservative read boundary, not arbitrary SurrealQL execution or a graph mutation API. The /gql endpoint requires SurrealDB 3.2 or newer and explicit experimental enablement on the 3.2 release line; the live contract pins 3.2.4 and enables that capability.

DuckDB analytics

#![allow(unused)]
fn main() {
use rullst_orm::polyglot::{
    AnalyticsRepository, AnalyticsValue, DuckDbStore, QueryLimit,
};

async fn analyze_events() -> Result<(), Box<dyn std::error::Error>> {
let analytics = DuckDbStore::in_memory().await?;
analytics.execute(
    "CREATE TABLE events (sequence BIGINT, label VARCHAR)",
    vec![],
).await?;
analytics.execute(
    "INSERT INTO events VALUES (?, ?)",
    vec![
        AnalyticsValue::Signed(1),
        AnalyticsValue::Text("started".into()),
    ],
).await?;
let rows = analytics.query(
    "SELECT sequence, label FROM events WHERE sequence >= ?",
    vec![AnalyticsValue::Signed(1)],
    QueryLimit::new(500)?,
).await?;
let _ = rows;
Ok(())
}
}

DuckDB is bundled for a predictable optional build. Its connection is guarded and every native operation runs through Tokio’s blocking worker pool. Dynamic values are prepared parameters; SQL text remains trusted application structure. Results must declare a 1–10,000 row materialization limit. Scalar, decimal, temporal, interval, geometry and binary values are preserved; unsupported complex result types fail with a typed error instead of being guessed.

Specialized Qdrant and Redis stores

Qdrant has a separate dense-vector contract rather than a document or Active Record facade. See RAG Systems & Vector Search for its bounded collection, upsert, delete and cosine-query API. The project wizard and deterministic CLI expose it as the additive --qdrant choice.

Redis native structures are also explicit and namespaced:

#![allow(unused)]
fn main() {
use rullst_orm::{
    RedisDataConfig, RedisDataKey, RedisDataStore, RedisField, RedisMember,
    RedisScanLimit, RedisStructuresRepository, RedisValue,
};

async fn redis_example() -> Result<(), Box<dyn std::error::Error>> {
let config = RedisDataConfig::new(
    std::env::var("REDIS_URL").unwrap_or_default(),
    "my-application",
    std::env::var("REDIS_USERNAME").unwrap_or_default(),
    std::env::var("REDIS_PASSWORD").unwrap_or_default(),
);
let data = RedisDataStore::connect_or_mock(config).await?;
let account = RedisDataKey::new("account:42")?;
data.hash_set(
    &account,
    &RedisField::new("display_name")?,
    &RedisValue::new("Ada")?,
).await?;
data.set_add(&account, &RedisMember::new("reader")?).await?;
let roles = data
    .set_scan(&account, RedisScanLimit::new(100)?)
    .await?;
let _ = roles;
Ok(())
}
}

The adapter also supplies atomic signed hash increments, exact membership, Sorted Set add/top ranking and exact per-structure deletion. Empty or mock_* endpoint/ACL credentials choose the deterministic fallback. Live remote Redis requires rediss://; intentionally unauthenticated development uses the loopback-only constructor. Hash values are capped at 1 MiB, members at 4 KiB, and scans/ranges at 1–1,000 accepted rows. Redis may still allocate a protocol value before client-side validation, so untrusted writers require server ACLs, quotas and isolation. Lists, Streams, cluster/failover, eviction and durable Pub/Sub are not part of this datastore contract.

What this feature does not promise

  • no cross-backend transaction, replication, migration, or consistency layer;
  • no claim that document databases implement SQL joins or Active Record;
  • no automatic choice of a datastore based on a model;
  • no managed backups, production credentials, provider homologation, indexes, cluster failover, or performance guarantee;
  • no universal “support for every database”.

Run the application’s exact feature set in addition to the full workspace gates. Remote production readiness still requires a disposable live environment, negative authorization tests, backup/restore rehearsal, and operational review.

38. Transactional Outbox & Durable Effects

Use a transactional outbox when a committed database change must eventually produce an external effect even if the application process stops between the two operations. Typical examples are a webhook, an e-mail request, a search projection or a message published to another service.

after_commit is the simpler choice for a best-effort, process-local effect. Outbox is the durable choice. It stores an event in the same relational transaction as the domain mutation, then lets an independent worker claim and deliver it later.

1. Register the schema as a migration

Applications using the built-in SQLx migration runner can register the versioned migration supplied by the ORM:

#![allow(unused)]
fn main() {
use rullst_orm::OutboxMigration;
use rullst_orm::schema::migration::Migration;

fn migrations() -> Vec<Box<dyn Migration>> {
    vec![Box::new(OutboxMigration)]
}
}

Outbox::install() applies the same idempotent DDL directly and is useful in tests or explicit local setup. It never runs automatically. A production team should review and track the migration through the same deployment process as its domain schema; do not create tables opportunistically while serving a request.

The built-in contract supports the SQLx relational backends: SQLite, PostgreSQL, MySQL and MariaDB. It does not span MongoDB, DuckDB, Turso or SurrealDB transactions.

2. Commit domain state and event together

Choose a stable stream boundary and a deterministic event key. A stream can represent an application or tenant, but it is only a namespace: the framework does not infer authorization from it.

#![allow(unused)]
fn main() {
use rullst_orm::{Error, Orm, Outbox};
use serde_json::json;

async fn create_invoice() -> Result<(), Error> {
Orm::transaction(|_| Box::pin(async move {
    let insert = sqlx::query(
        "INSERT INTO invoices (id, status) VALUES (?, ?)"
    )
    .bind(123_i64)
    .bind("issued");
    rullst_orm::execute_query!(insert, execute, pool)?;

    Outbox::enqueue(
        "tenant-42",
        "invoice:123:issued:v1",
        "invoice.issued",
        &json!({"invoice_id": 123}),
    ).await?;

    Ok::<(), Error>(())
})).await?;
Ok(())
}
}

If the transaction rolls back, neither row survives. Calling enqueue outside Orm::transaction fails instead of silently opening an unrelated transaction. When the application already owns a raw SQLx transaction, use enqueue_with_tx(&mut transaction, ...).

(stream, event_key) is unique. Repeating the same kind and serialized JSON payload returns the existing event ID with inserted == false. Reusing that key for different content is an error; it does not overwrite the original event.

3. Claim, deliver and acknowledge

Each worker supplies a stable identifier, lease duration and maximum number of claims:

#![allow(unused)]
fn main() {
use rullst_orm::{Error, Outbox};

async fn deliver() -> Result<(), Error> {
if let Some(event) = Outbox::claim_next(
    "tenant-42",
    "webhook-worker-1",
    30, // lease seconds
    8,  // maximum claims
).await? {
    let payload = event.payload()?;

    // Perform the external effect. Pass event.event_key to a provider that
    // supports idempotency, or deduplicate it in the receiving service.
    let delivered = payload.get("invoice_id").is_some();

    if delivered {
        let owned = Outbox::acknowledge(event.id, event.claim_key).await?;
        if !owned {
            // The lease expired or another worker reclaimed the event.
            // Do not assume ownership or mutate the newer claim.
        }
    } else {
        Outbox::fail(
            event.id,
            event.claim_key,
            "provider temporarily unavailable",
            8,
            15,
        ).await?;
    }
}
Ok(())
}
}

An ACK or failure transition succeeds only for the exact, unexpired random claim token. A failed delivery becomes pending after the bounded delay, or dead_letter when its attempt limit is reached. If a worker dies during its final claim, the next claim sweep moves that expired event to dead-letter instead of retrying forever.

4. Understand the guarantee

The delivery guarantee is at least once:

  1. the worker claims an event;
  2. the external provider accepts the effect;
  3. the process stops before the database ACK;
  4. the lease expires and the event is delivered again.

No local database design can atomically commit an arbitrary remote HTTP side effect. Make the consumer idempotent using the stable stream/event key. Do not use a random key on every retry.

Other explicit limits:

  • key fields are 1–128 characters from a bounded ASCII grammar;
  • JSON payloads are at most one MiB;
  • leases are 1–3,600 seconds and attempts are 1–100;
  • ordering is not guaranteed across retries or concurrent workers;
  • generated observers are not automatically converted into outbox events;
  • cleanup, retention, tenant authorization and the worker supervision loop belong to the application;
  • monitor retry and dead-letter state through your normal database operations.

This separation keeps model saves predictable while giving applications a durable primitive where the event schema and operational policy are explicit.

5. Relay into rullst-messaging

With the umbrella messaging-orm-outbox feature, OrmOutboxRelay<B> maps one exact outbox stream to one topic on any static MessageBroker. It validates the claimed JSON, uses event_key as the broker idempotency key, publishes and then acknowledges the exact ORM claim. Its executable crash-window test stops after the first publish, reclaims the expired event and observes an exact broker replay with only one retained message.

That bridge improves composition; it does not change the guarantee. ORM commit, broker publish and ORM ACK are not one distributed atomic transaction. Operate the claim loop, retry/dead-letter policy, cleanup, authorization and final consumer deduplication explicitly. See Bounded Brokered Messaging for the relay code.

39. Scout Search Providers

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

Scout connects a #[orm(searchable)] SQLx model to one search backend. The model database remains authoritative; the search index is a projection updated only after a successful managed commit.

Enable the provider adapters

With the umbrella crate:

[dependencies]
rullst = { version = "12.0.0-rc.1", features = ["orm-scout"] }

Or enable scout-http directly on rullst-orm. The feature supplies bounded HTTP adapters for Meilisearch, Elasticsearch and Algolia. The in-memory MockSearchEngine is available without the HTTP feature.

Configure one engine

Configuration is process-wide and fail-closed if code tries to replace an already installed engine:

#![allow(unused)]
fn main() {
use rullst_orm::{MeilisearchEngine, set_search_engine};

fn configure() -> Result<(), rullst_orm::Error> {
let engine = MeilisearchEngine::new(
    std::env::var("MEILI_URL").unwrap_or_default(),
    std::env::var("MEILI_API_KEY").unwrap_or_default(),
)?;
set_search_engine(engine)?;
Ok(())
}
}

Empty or mock_* credentials deliberately select the deterministic offline store. That makes local/test behavior explicit and prevents an accidental live call with missing secrets. A keyless Meilisearch or Elasticsearch development server must be selected with MeilisearchEngine::local(...) or ElasticsearchEngine::local(...); those constructors accept only loopback HTTP. Normal custom endpoints require HTTPS, contain no URL credentials/path, and disable redirects.

Algolia normally derives its official API origin from the application ID:

#![allow(unused)]
fn main() {
use rullst_orm::AlgoliaEngine;

fn engine() -> Result<AlgoliaEngine, rullst_orm::Error> {
AlgoliaEngine::new("APPLICATION_ID", "ADMIN_API_KEY")
}
}

AlgoliaEngine::with_endpoint exists for an explicitly configured compatible proxy and applies the same HTTPS/loopback URL policy. Never expose an indexing or admin key to browser code.

Mark and query the model

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};

#[derive(Clone, Debug, FromRow, Orm)]
#[orm(table = "articles", searchable)]
struct Article {
    id: i32,
    title: String,
    body: String,
}

async fn find() -> Result<Vec<Article>, rullst_orm::Error> {
let results = Article::search("transactional outbox").await.get().await?;
Ok(results)
}
}

Generated save/update/delete operations project only after the relational commit. Rollback produces no search write. Provider or search errors remain typed errors; they are not silently converted into an empty result.

The shared adapter boundary enforces:

  • lowercase index names of at most 128 bytes;
  • positive i32 document IDs;
  • object-shaped JSON documents no larger than one MiB;
  • queries no larger than 1,024 bytes and without control characters;
  • at most 1,000 parsed hits and a four-MiB response body;
  • five-second connect and twenty-second request deadlines;
  • disabled redirects and no secret-bearing error bodies.

Meilisearch and Algolia asynchronous indexing tasks are awaited with a bounded poll loop. Elasticsearch uses refresh=wait_for for the adapter operations.

Choose the durability level

The generated Scout hook is process-local after commit. A provider outage is reported as PostCommit: the model row is already durable and must not be blindly inserted again. This path is suitable when the application can rebuild the index or explicitly retry.

If every projection must survive a process crash, write a versioned search event through Outbox::enqueue in the domain transaction and run the selected engine from an idempotent outbox worker. Rullst does not invent a stable event key or serialize every model hook automatically. See Transactional Outbox & Durable Effects.

Evidence boundaries

The repository runs a real update/search/delete lifecycle against a digest-pinned Meilisearch container. Elasticsearch and Algolia run deterministic offline tests plus local HTTP protocol fixtures that verify paths, headers, payloads, response bounds and ID parsing. Those fixtures do not prove a hosted Algolia account, every Elasticsearch version, cluster failover, ranking quality or production capacity.

Preparing a National NFS-e 1.01 homologation candidate

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

This guide exercises the part of the Brazilian National NFS-e pipeline that can be proved safely without sending a fiscal document. It builds a bounded DPS, checks checksum-pinned government schema sources, signs the document with an application-supplied A1 PKCS#12 certificate, and validates the signed result again.

It does not authorize a note. Rullst keeps homologation and production transmission disabled until the remaining protocol and external-evidence gates listed below are complete.

Enable the isolated dependency boundary before following the signing steps:

[dependencies]
rullst-capital = { version = "12.0.0-rc.1", features = ["nfse"] }

Umbrella applications can select rullst = { version = "12.0.0-rc.1", features = ["capital-nfse"] }. The feature adds local schema/signature/codec dependencies; it does not enable a live network path.

1. Know the pinned contracts

This source revision recognizes only these immutable artifact profiles:

EnvironmentOfficial archiveArchive SHA-256
ProductionNFSe-ESQUEMAS_XSD-v1.01-20260209.zipe7935cbd9470527c6cc32984c1b2263e614183bf0139ce2733eaaed2de9a8072
Restricted productionNFSe-ESQUEMAS_XSD-PRODREST-v1.01-20260727.zip6c7e0510d3ecff4454f291f4e10b742d27a4818f23aab181494f96d0ea79f3dc

Download production artifacts from the official current technical documentation page and restricted artifacts from the official restricted-production documentation page. Do not copy an archive from an unofficial mirror.

Verify the archive before extraction:

sha256sum NFSe-ESQUEMAS_XSD-v1.01-20260209.zip

NfseDpsSchemaValidator also verifies every expected XSD file. It rejects an unknown profile, a missing/modified file, a file larger than 256 KiB, traversal, and any import outside its closed in-memory catalogue. The XML instance cannot make it download a schema or follow a filesystem hint.

There is one intentionally visible compatibility rule. The pinned production simple-types file contains the DPS-series pattern ^0{0,4}\d{1,5}$, authored with .NET anchors even though ^ and $ are literals in XSD regex grammar. Only after the file hash matches, Rullst removes those two anchors in memory. The rewrite is tied to that exact file hash and exact one occurrence; any upstream change fails closed. Restricted production currently needs no rewrite.

2. Exercise the unsigned builder and official XSD

Point the example at the directory that directly contains DPS_v1.01.xsd:

RULLST_NFSE_XSD_DIR=/path/to/extracted/Schemas/1.01 \
  cargo run -p rullst-capital --example nfse_v101_preview

The example uses NfseDpsV101, not the legacy floating-point preview. Its bounded subset is an ordinary domestic service with:

  • integer BRL cents and ISS basis points;
  • explicit taxation and retention enums;
  • checked CPF/CNPJ digits, IBGE codes, DPS ID, series, service code, text, and XML size;
  • no automatic guess about municipal parameters or tax treatment.

Passing XSD validation proves document structure and scalar constraints only. SEFIN business rules still depend on the emitter, municipality, contributor registration, Simples Nacional state, service classification, and current official parameters.

3. Load and sign with an A1 certificate

Never commit a .pfx/.p12 file or its passphrase. Load them from the deployment secret boundary and keep the passphrase out of logs:

#![allow(unused)]
fn main() {
use rullst_capital::fiscal::{
    FiscalCertificate, NFSE_RESTRICTED_V1_01_20260727,
    NfseDpsSchemaValidator, sign_dps_xml,
};

fn prepare_signed_candidate(
    unsigned_xml: &str,
    schema_directory: &std::path::Path,
    pkcs12_path: &std::path::Path,
    passphrase: String,
) -> Result<String, Box<dyn std::error::Error>> {
    let pkcs12 = std::fs::read(pkcs12_path)?;
    let certificate = FiscalCertificate::from_bytes(&pkcs12, passphrase)?;
    let validator = NfseDpsSchemaValidator::from_pinned_directory(
        schema_directory,
        &NFSE_RESTRICTED_V1_01_20260727,
    )?;

    validator.validate(unsigned_xml)?;
    let signed_xml = sign_dps_xml(unsigned_xml, &certificate)?;
    validator.validate(&signed_xml)?;
    Ok(signed_xml)
}
}

sign_dps_xml accepts exactly one unsigned DPS 1.01 envelope with a unique 45-character infDPS/@Id. It extracts the matching key/certificate chain from PKCS#12, requires an RSA PKCS#8 key, emits an enveloped RSA-SHA256 signature using SHA-256 and inclusive C14N 1.0, and refuses to return partially signed XML. The signing path verifies the result against its embedded certificate before returning it; separate tests also verify the generated XMLDSig and validate a signed builder fixture with the pinned official schema.

Rullst redacts the certificate container in Debug and zeroizes certificate bytes, passphrases, decoded base64, and derived PEM buffers it owns. The application still owns secret-file permissions, secret-manager integration, rotation, process/core-dump policy, and access auditing.

4. Run the opt-in official-artifact regression

The repository does not redistribute mutable government packages. After downloading and verifying the pinned production archive, run the ignored test:

RULLST_NFSE_XSD_DIR=/path/to/extracted/Schemas/1.01 \
  cargo test -p rullst-capital \
  fiscal::signer::tests::signed_builder_output_matches_the_official_xsd_when_supplied \
  -- --ignored

The test generates an ephemeral RSA key and certificate at runtime. It is a schema/cryptographic interoperability fixture, not an ICP-Brasil certificate or an official homologation result.

The corresponding restricted-production package has a separate immutable manifest and regression:

RULLST_NFSE_RESTRICTED_XSD_DIR=/path/to/extracted/restricted/schemas \
  cargo test -p rullst-capital \
  fiscal::signer::tests::signed_builder_output_matches_the_official_restricted_xsd_when_supplied \
  -- --ignored

5. Exercise the offline protocol boundary

After producing the signed DPS, build the exact request body without sending it:

#![allow(unused)]
fn main() {
use rullst_capital::fiscal::NfseIssueRequest;

fn prepare(signed_dps: &str) -> Result<Vec<u8>, rullst_capital::fiscal::FiscalError> {
let request = NfseIssueRequest::try_from_signed_dps(signed_dps)?;
let body = request.to_json()?;
Ok(body)
}
}

Construction verifies that the document has one unique official DPS ID, one direct signed infDPS/tpAmb, one direct XMLDSig reference to that ID and a cryptographically valid embedded signature. GZip output fixes its timestamp to zero, so the JSON is deterministic for a given signed document. The environment passed to parse_response must equal that signed tpAmb.

For retained protocol fixtures, call request.parse_response(status, environment, body). HTTP 201 is represented only as NfseIssueResponse::Authorized; HTTP 400, 403 and 500 are represented as Rejected. The parser rejects unknown fields, wrong environments or DPS IDs, invalid access keys, malformed JSON/Base64/GZip/XML, unsigned or tampered NFS-e XML and decompressed material above four MiB. Embedded-signature verification proves document integrity against its declared certificate, not ICP-Brasil trust or emitter ownership.

6. Journal a caller-owned command before transport

The nfse feature provides bounded local evidence for a future reviewed transport workflow. Load exactly 32 random bytes from a secret manager; never derive this key directly from a password or commit it to the repository.

#![allow(unused)]
fn main() {
use rullst_capital::fiscal::{
    FiscalCommandJournal, FiscalJournalDisposition, FiscalJournalKey,
    NfseEnvironment, NfseIssueRequest, NfseIssueResponse,
};
use std::path::Path;

fn stage(
    path: &Path,
    key_bytes: &[u8],
    request: &NfseIssueRequest,
    parsed_response: Option<&NfseIssueResponse>,
) -> Result<(), Box<dyn std::error::Error>> {
let key = FiscalJournalKey::try_new("fiscal-2026-01", key_bytes)?;
let journal = FiscalCommandJournal::try_open(path, key)?;

// Use an opaque, stable, non-PII ID from the application's authoritative outbox.
let prepared = journal.prepare(
    "nfse-command:01J8YQ2V5M",
    NfseEnvironment::Homologation,
    request,
)?;
if prepared.disposition() == FiscalJournalDisposition::Recorded {
    // A future reviewed transport may run only after this synchronized record.
}

if let Some(response) = parsed_response {
    journal.record_response("nfse-command:01J8YQ2V5M", request, response)?;
}

for unresolved in journal.pending()? {
    // Locate the real request in the application's protected outbox, compare
    // request_digest(), and reconcile with SEFIN before deciding whether to retry.
    let _command_id = unresolved.command_id();
}

// Persist this separately if valid-prefix truncation must be detected on restart.
let _checkpoint = journal.checkpoint()?;
Ok(())
}
}

The HMAC-chained file accepts one active writer and at most 4,096 events/16 MiB. It stores no DPS/NFS-e XML, access key, processing message, response body, or certificate material. An exact command/request/result replay does not append; conflicts, corrupted/reordered frames, wrong keys, symlinks, external growth, quota exhaustion, and uncertain synchronization fail closed. pending() is a recovery index, not the protected request store itself. The host owns key rotation, directory permissions, exclusive-writer enforcement, the real request/outbox, independent checkpoint retention, backup/retention, retry policy, and authority reconciliation. The journal does not perform network I/O or establish exactly-once behavior across systems.

7. Do not enable live transmission yet

The official endpoints are immutable in NfseEnvironment, and the live path can already build an HTTPS-only rustls mTLS client with redirects disabled and bounded connect/request timeouts. It deliberately performs no request.

The exact request envelope and bounded response/rejection codec are now local, fixture-testable prerequisites. Before Rullst can enable restricted-production transmission, the exact source revision must still have:

  1. retained fixtures from the current official restricted-production contract, including every supported success/rejection shape;
  2. certificate validity, key-usage, emitter CPF/CNPJ, and ICP-Brasil chain policy reviewed against the current national rules;
  3. deployment of the local journal with protected authoritative request/outbox storage, exclusive-writer/key/checkpoint operations, retry policy, and explicit authority reconciliation;
  4. positive and negative tests in the restricted environment using an authorized real A1 certificate and valid contributor/municipality data;
  5. independent fiscal/security review and retained evidence tied to an immutable commit;
  6. successful official homologation before production can be considered.

Until those gates pass, NfseEnvironment::Homologation and NfseEnvironment::Production return FiscalError::Unsupported, while NfseEnvironment::Mock remains unmistakably MOCK_NOT_AUTHORIZED.

41. Tenant-Bound RAG in One Typed Operation

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

Rullst’s bounded RAG pipeline turns one authenticated question into a guarded embedding, authorized retrieval request, budgeted context, grounded model call, source metadata, and a terminal audit event.

It deliberately does not hide the datastore or invent authorization. The host application still decides who may ask, which records that identity may read, how documents are ingested and deleted, and which embedding model and vector dimensions define an index.

Add the feature

[dependencies]
rullst = { version = "12.0.0-rc.1", default-features = false, features = ["ai"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Run the complete offline contract

An empty or mock_* OpenAI key selects Rullst’s deterministic offline provider. Its fixture embeddings have 16 dimensions, so the local index below uses the same exact dimension.

use rullst::ai::rag::{
    InMemoryRagAuditTrail, InMemoryRagRetriever, RagDocument, RagPipeline,
};
use rullst::ai::{providers::openai::OpenAiProvider, AiClient};
use rullst::security::TenantContext;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build this only from verified authentication and membership claims.
    let tenant = TenantContext::try_new("tenant:acme")?;
    let client = AiClient::new(OpenAiProvider::new("mock_rag"));
    let retriever = InMemoryRagRetriever::try_new(1_000, 16)?;

    let content = "Rullst upgrades start with cargo rullst upgrade --dry-run.";
    let vector = client.embed(content).await?;
    let document = RagDocument::try_new(
        &tenant,
        "upgrade-guide",
        content,
        0.0, // The retriever replaces this with cosine similarity.
    )?;
    retriever.upsert(&tenant, document, vector)?;

    let audit = Arc::new(InMemoryRagAuditTrail::new(1_000)?);
    let pipeline = RagPipeline::new(client, retriever, Arc::clone(&audit));
    let response = pipeline
        .answer(&tenant, "How do I preview a framework upgrade?")
        .await?;

    println!("{}", response.answer());
    for source in response.sources() {
        println!(
            "source={} score={} chars={} truncated={}",
            source.document_id(),
            source.score(),
            source.included_chars(),
            source.truncated()
        );
    }

    let events = audit.entries()?;
    println!("recorded {} terminal RAG event(s)", events.len());
    Ok(())
}

The offline provider is a deterministic test fixture, not evidence of live model quality. In a real application, select a live provider explicitly and evaluate its configured model against a versioned domain corpus.

What the pipeline enforces

For each call, RagPipeline:

  1. validates the bounded question and runs guarded embedding;
  2. passes the trusted TenantContext, query vector, and bounded limit to the application retriever;
  3. rejects over-returned or differently tagged documents;
  4. runs prompt-injection checks and outbound PII masking on every passage;
  5. truncates by per-document and total Unicode-scalar budgets;
  6. refuses to generate when no safe context remains;
  7. returns only source identifiers, scores, included character counts, and truncation state alongside the answer;
  8. records exactly one terminal, secret-minimized audit event, or fails the operation if the required audit sink is unavailable.

Tune budgets with RagConfig::try_new(max_documents, max_document_chars, max_context_chars). The constructor rejects zero or unbounded values; RagPipeline::with_config accepts only an already validated configuration.

Use an authoritative production retriever

InMemoryRagRetriever is bounded and tenant-partitioned, but it is process local, nondurable, and uses an exact linear cosine scan. It is appropriate for tests, development, and small ephemeral datasets.

Production applications implement the public async RagRetriever trait over their chosen store. A PostgreSQL implementation should put the tenant predicate inside the same parameterized pgvector query that performs similarity ordering. A Qdrant implementation should apply the deployment’s reviewed tenant filter or separate-collection policy. It then creates each RagDocument from the same trusted context passed to retrieve.

The essential shape is:

async fn retrieve(
    &self,
    tenant: &TenantContext,
    query_embedding: &[f32],
    limit: usize,
) -> Result<Vec<RagDocument>, RagRetrievalError> {
    let rows = self.store
        .similar_chunks_for_tenant(&tenant.tenant_id, query_embedding, limit)
        .await
        .map_err(redacted_retrieval_error)?;

    rows.into_iter()
        .map(|row| RagDocument::try_new(tenant, row.id, row.content, row.score))
        .collect()
}

The pipeline verifies the returned tenant tag as defense in depth. That check cannot detect a datastore adapter that incorrectly read another tenant’s row and then falsely relabeled it. Authoritative tenant and ownership filtering must therefore happen in the datastore query itself.

Supply durable, minimized audit evidence

InMemoryRagAuditTrail is useful for local assertions. A single-process service can use the bounded built-in local file:

#![allow(unused)]
fn main() {
use rullst::ai::rag::DurableRagAuditTrail;
use std::sync::Arc;

let audit = Arc::new(DurableRagAuditTrail::try_open(
    "storage/audit/rag.log",
)?);
Ok::<(), Box<dyn std::error::Error>>(())
}

The trail synchronously appends a distinct versioned stream, validates every event/frame on restart and fails closed on corruption, unsafe file types, external length changes and quota exhaustion. The default ceilings are 16 MiB and 4,096 events; try_open_with_max_bytes may select a smaller byte quota. Its SHA-256 frame digest detects corruption but does not authenticate the writer. The host owns trusted directory permissions, exclusive writer access, rotation, retention, backup and export. Multi-instance deployments should implement RagAuditSink over an append-only, access-controlled destination.

The built-in event contains the tenant ID, a SHA-256 digest of the original question, document counts, included context characters, and the terminal outcome. It omits raw questions, passages, vectors, model answers, and provider error bodies. A digest is not encryption and low-entropy questions may be guessable, so do not expose the audit stream as public data.

Security and quality boundaries

  • Prompt-injection filtering is heuristic defense in depth, not proof that a passage or answer is safe.
  • Source metadata proves which identifiers entered the prompt; it does not prove that the model cited them faithfully or that their contents were true.
  • Apply normal output encoding, domain validation, and tool authorization after generation. Never treat model text as an authorized command.
  • Keep ingestion, updates, deletions, retention, embedding-model migrations, ANN tuning, backups, and disaster recovery explicit.
  • Test cross-tenant denial, empty retrieval, hostile passages, provider failure, datastore failure, audit failure, and context truncation in the host application.

For the lower-level vector-store contracts, see RAG Systems & Vector Search. For the framework’s broader claims and limits, see the capability ledger.

42. Server-Bound OAuth/OIDC Sessions

Rullst Connect can manage the browser-to-provider callback challenge for an Axum application. The bounded flow generates state and PKCE for OAuth 2.0, adds nonce for OpenID Connect, stores the private values in tower-sessions, and consumes them before validating the callback.

This removes security-sensitive plumbing from ordinary handlers. It does not configure the application’s session store, cookie, TLS, account-linking, or authorization policy.

Enable the session feature

For the unreleased workspace:

[dependencies]
rullst-connect = { path = "../Rullst/rullst-connect", features = ["axum-session"] }
tower-sessions = "0.15"

Published applications should replace the path with one immutable compatible version. Add a SessionManagerLayer to the Axum router. MemoryStore is useful for local examples and tests, but it is process-local and is not a production durability or horizontal-scaling strategy.

use tower_sessions::{cookie::SameSite, MemoryStore, SessionManagerLayer};

let sessions = SessionManagerLayer::new(MemoryStore::default())
    .with_http_only(true)
    .with_same_site(SameSite::Lax)
    .with_secure(true);

let app = app.layer(sessions);

SameSite::Lax permits the ordinary top-level OAuth callback while reducing cross-site cookie exposure. Production still requires HTTPS, a durable shared store where multiple instances are used, bounded store retention, protected keys and an explicit reverse-proxy policy.

Start OAuth 2.0 with state and PKCE

Use this path for providers such as GitHub where the application is using an OAuth authorization-code flow without an ID token:

#![allow(unused)]
fn main() {
use axum::response::Redirect;
use rullst_connect::prelude::*;
use tower_sessions::Session;

async fn start_github(
    session: Session,
    github: &GithubProvider,
) -> Result<Redirect, ConnectError> {
    let authorization = begin_oauth_session(&session, github).await?;
    Ok(Redirect::temporary(authorization.url()))
}
}

The returned URL contains the random state and the SHA-256 PKCE challenge. The 64-character verifier is serialized only in the server-side session record. OAuthAuthorization deliberately redacts its URL from Debug output.

Start OpenID Connect with nonce

Use the OIDC variant for Google, Apple, or a discovered custom OIDC provider:

let authorization = begin_oidc_session(&session, &oidc_provider).await?;
Ok(Redirect::temporary(authorization.url()))

This stores another random value and sends it as nonce. The provider adapter receives that same expected nonce later and validates it against the signed ID token in the adapters whose documented contract includes ID-token validation.

Consume the callback

Mount AuthSession directly as an Axum extractor. Extraction parses the real query, removes and immediately saves the stored challenge, rejects expiry or a constant-time state mismatch, and makes a later sequential replay fail:

#![allow(unused)]
fn main() {
use rullst_connect::prelude::*;
use tower_sessions::Session;

async fn github_callback(
    session: Session,
    callback: AuthSession,
    github: &GithubProvider,
) -> Result<UniversalProfile, ConnectError> {
    let user = github.get_user(callback.exchange_params()?).await?;

    // Rotate the browser session before establishing authenticated identity.
    session
        .cycle_id()
        .await
        .map_err(|error| ConnectError::Session(error.to_string()))?;
    session
        .insert("authenticated_user_id", &user.id)
        .await
        .map_err(|error| ConnectError::Session(error.to_string()))?;

    Ok(user.universal_profile())
}
}

Do not serialize ConnectUser as a credential store. Its public serialization already omits provider tokens, while UniversalProfile is the narrower credential-free identity projection. If an application needs provider refresh tokens, place them in a dedicated encrypted store with explicit rotation and revocation policy.

For a provider that returned both a refresh token and expires_in, construct a bounded process-local coordinator at the trusted time the token response was received:

#![allow(unused)]
fn main() {
use rullst_connect::{AutoRefreshingSession, ConnectError, ConnectUser};
use rullst_connect::prelude::ExposeSecret as _;

async fn call_authorized_endpoint(_: &str) -> Result<(), ConnectError> {
    Ok(())
}

async fn provider_request(
    github: &rullst_connect::providers::GithubProvider,
    user: &ConnectUser,
    token_received_at: u64,
) -> Result<(), ConnectError> {
    let tokens = AutoRefreshingSession::from_user_at(
        github,
        user,
        token_received_at,
    )?;
    let lease = tokens.access_token().await?;
    call_authorized_endpoint(lease.access_token().expose_secret()).await?;
    Ok(())
}
}

AutoRefreshingSession<P> checks a bounded early-expiration window and holds one async process-local refresh gate, so provider refresh calls cannot overlap and waiters reuse the first valid result. It keeps the old refresh credential if the provider does not rotate, adopts a valid rotation, requires the same provider user ID and changes state only after full validation.

Persist refresh state without storing plaintext tokens

Rullst supplies a storage-neutral authenticated envelope. The application still chooses its database/file/secret manager, but the stored token record need not invent its own cryptographic format:

#![allow(unused)]
fn main() {
use rullst_connect::{
    AutoRefreshingSession, EncryptedTokenSnapshot, Provider,
    TokenSnapshotBinding, TokenSnapshotError, TokenSnapshotKey,
};

async fn seal_current_state<P: Provider + ?Sized>(
    session: &AutoRefreshingSession<'_, P>,
    key_bytes: [u8; 32],
    local_account_id: &str,
) -> Result<EncryptedTokenSnapshot, TokenSnapshotError> {
    let state = session.state_snapshot().await;
    let binding = TokenSnapshotBinding::try_new("github", local_account_id)?;
    let key = TokenSnapshotKey::try_new("oauth-primary-2026", key_bytes)?;
    EncryptedTokenSnapshot::seal(&state, &key, &binding)
}
}

Write only EncryptedTokenSnapshot::as_str() to durable storage. On restart:

  1. parse the stored string with EncryptedTokenSnapshot::try_from_envelope;
  2. read its non-secret key_id() and select that 32-byte key in a secret manager;
  3. rebuild the same binding from trusted provider and local-account state;
  4. call open, then pass the restored state to AutoRefreshingSession::new.

The AES-256-GCM authentication tag covers the envelope version, key ID, provider and local account, so a copied record fails for another owner. The payload is bounded and revalidated after decryption. Debug output is redacted, and the envelope itself does not implement Display or Serde. The application must still commit each new generation transactionally, rotate/retain keys, authorize the account and revoke local state. Multi-process deployments also need a distributed compare-and-set/lease; retry/backoff, reauthentication and replay of the original API request are deliberately not inferred.

Lifecycle and failure semantics

The managed contract is intentionally small:

  • a challenge expires ten minutes after it is created;
  • there is one active challenge per browser session;
  • starting a second flow replaces the first, so the older browser tab fails;
  • authorization URLs must use HTTPS or exact loopback HTTP, contain no URL credentials/fragment, and preserve exactly one generated state and S256 PKCE tuple without a preconfigured nonce;
  • the challenge is removed and saved before state, nonce or PKCE-dependent exchange;
  • missing, mismatched, expired and later sequential callbacks fail closed;
  • provider error text is bounded before it becomes a typed error;
  • callback codes, state, nonce, verifier and authorization URLs are redacted from the managed types’ Debug output.

One active challenge makes replay and lifecycle behavior unambiguous, but it is not the best UX for applications that intentionally support concurrent login tabs. Such an application should build a bounded transaction store keyed by an opaque flow identifier and retain the same expiry, atomic consume, constant-time comparison and redaction properties.

The generic tower-sessions store interface does not expose a distributed compare-and-delete. Two requests that already loaded the same record can still race even though each removal is saved immediately. Provider authorization codes are themselves single-use, but account creation/linking and authenticated session establishment must still be idempotent. Deployments requiring a strict distributed callback claim should use an application-owned atomic challenge store.

What the application still must prove

Before release, test the exact deployed provider and browser path:

  1. The registered redirect URI exactly matches the application route and uses HTTPS outside an exact loopback development host.
  2. The session cookie remains Secure and HttpOnly, uses an intentional SameSite policy, and is rotated after successful authentication.
  3. Every application instance sees the same durable session store, or routing is deliberately constrained without pretending failover works.
  4. Issuer, audience, signature, expiry and nonce checks pass and fail against the provider’s real or restricted environment.
  5. Account creation/linking cannot attach an attacker-controlled provider identity to an existing local account.
  6. Denial, timeout, provider outage and abandoned-login recovery have bounded user-visible behavior without logging credentials.

The local Rullst regressions prove generation, round-trip, mismatch, missing state, expiry, replacement, replay, typed exchange parameters and redaction. They are not provider certification or deployment evidence.

Rullst Omni: Web-First, Platform-Enhanced Applications

Rullst Omni packages one canonical Rullst web application for desktop, Android and iOS with Tauri. It is deliberately a secure web-shell foundation, not a claim that a website automatically becomes a store-ready native product.

The architectural rule is simple:

  1. the Rullst server owns domain rules, identity, authorization, persistence, realtime policy and security;
  2. the web interface remains the universally reachable product;
  3. platform shells reproduce that interface and add only narrowly scoped native capabilities that have a real product need and platform tests.

This keeps web, desktop and mobile behavior aligned without treating an untrusted client as the authority.

Generate a desktop development shell

From a Rullst application root:

cargo rullst make:omni --platform desktop
cargo rullst omni desktop

The product name and version inherit [package].name and [package].version. Desktop development derives a com.example.<package> identifier when none is provided. That namespace is a visible placeholder, not a distributable product identity.

The default http://localhost:3000 profile starts the parent Rullst server, waits for it and owns only the child process it created. It refuses to attach when port 3000 was already occupied, stops if the child exits before readiness and fails after a bounded timeout. This prevents the shell from silently connecting to an unrelated local process.

For an externally operated HTTPS application, set its public web URL:

cargo rullst make:omni \
  --platform desktop \
  --backend-url https://app.example.com \
  --identifier com.exampleowner.myapp \
  --product-name "My App" \
  --app-version 1.2.3

Use a reverse-DNS namespace that you or your organization actually control; the value above is illustrative.

Generate Android or iOS

Mobile requires both a reachable backend and an application-owned identifier:

cargo rullst make:omni \
  --platform android \
  --backend-url https://app.example.com \
  --identifier com.acme.myapp

cargo rullst make:omni \
  --platform ios \
  --backend-url https://app.example.com \
  --identifier com.acme.myapp

Android emulator development may use http://10.0.2.2:3000. Distributable applications should use HTTPS. Android requires the Android SDK/NDK and Java; iOS generation requires macOS and Xcode.

cargo rullst omni android
cargo rullst omni ios

The CLI initializes only platforms selected by the user. A requested toolchain or Tauri initialization failure fails the command instead of printing a false success.

Security model

The generated local bootstrap has an origin-specific CSP and no inline script. Remote content is not given a global Tauri object or privileged command capability. A Rust-side navigation policy admits only:

  • the packaged Tauri bootstrap origin; and
  • the exact scheme, host and effective port of --backend-url.

Paths and query strings on that same backend remain usable. A lookalike host, scheme downgrade, different port or third-party origin is rejected.

This secure default means cross-origin OAuth and ordinary external links do not yet work inside the webview. Do not weaken the allowlist to https: or expose a generic shell command. Add a reviewed system-browser opener plus an allowlisted, single-use deep-link callback when the application needs that flow.

All normal web protections still apply. The server must enforce sessions, CSRF, secure headers, ownership/RBAC, input validation and rate limits. A mobile package does not make server-side authorization optional.

Share one typed wire contract

rullst::client_contract is available to native server code and wasm32-unknown-unknown clients. Its rullst.client v1 envelope gives web and Omni code the same bounded JSON shape without inventing a second business API:

#![allow(unused)]
fn main() {
use rullst::client_contract::{
    ClientContractPolicy, ClientRequest, IdempotencyKey, RequestId,
    CURRENT_CLIENT_CONTRACT_VERSION,
};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct LessonAttempt {
    lesson_id: String,
    answer: String,
}

fn encode_attempt() -> Result<(), Box<dyn std::error::Error>> {
let request = ClientRequest::mutation(
    CURRENT_CLIENT_CONTRACT_VERSION,
    RequestId::new("req_01j8lesson")?,
    IdempotencyKey::new("attempt_01j8lesson")?,
    LessonAttempt {
        lesson_id: "lesson_1".into(),
        answer: "bonjour".into(),
    },
);
let encoded = ClientContractPolicy::default().encode_request(&request)?;
let _ = encoded;
Ok(())
}
}

The server decodes through ClientContractPolicy, derives the user and tenant from its authenticated session, requires the idempotency key before a mutation, and computes grading, points, streaks and server time itself. Do not put a role, user_id, trusted score or authorization decision in the client payload.

The generic codec has a configurable ceiling no larger than 2 MiB, rejects unknown outer fields and selects only a mutually supported positive version. Its key proves request shape, not exactly-once execution: the application still needs a durable unique key plus an atomic result/effect transaction. This contract is a prerequisite for future offline queues, not an offline queue.

Offline behavior

The packaged bootstrap can explain that the device is offline and retry before the first navigation. It still does not automatically cache application data or mount background synchronization.

The opt-in native offline-sync feature now supplies a bounded foundation: account-bound AES-256-GCM snapshots, FIFO idempotent mutations, authoritative server revisions/cursors, explicit conflicts, full resync, quotas, recovery and logical erasure. A bounded foreground coordinator can drive an application-owned authenticated transport with request budgets, timeout and cursor-stall protection. Follow the offline synchronization tutorial to use it without moving authority into the client.

Calling the generated shell itself “offline-first” remains inaccurate. A real application profile must still provide at least:

  • reviewed platform persistence and Keychain/Keystore integration;
  • concrete authenticated HTTP, retry/background scheduling and application conflict UX;
  • concrete migrations beyond the current versioned fail-closed schema;
  • complete deletion of snapshots, backups and platform keys;
  • browser, Android and iOS tests for airplane mode and reconnection.

Adding native capabilities safely

Push notifications, biometrics, deep links, camera/file access, haptics and OS secure storage can make Omni feel native. Add them as opt-in capabilities, one at a time:

  1. state the user-facing need and supported platforms;
  2. grant the narrowest Tauri/platform permission;
  3. keep device credentials in Keychain/Keystore-class storage, never in web local storage or generated source;
  4. authenticate every server operation independently of the client signal;
  5. add negative tests for denied/replayed/cross-account requests;
  6. test a real device before documenting the capability as supported.

Biometrics may unlock a local credential; it must not manufacture server authorization. Push payloads should be minimized and treated as untrusted input.

What CI proves

Rullst maintains path-aware generation/compile workflows for three evidence classes:

  • desktop crate checks on Linux, macOS and Windows;
  • an Android debug APK build;
  • an iOS simulator build on macOS.

A green run proves that a fresh generated shell compiled for that runner and commit. All three gates passed on 755fbd61933bed04369e0eb5de50b11275db5e3d. This does not prove physical-device behavior, accessibility, signing, privacy declarations, TestFlight/Play testing or store acceptance.

Before distribution

Review the generated omni-app/README.md, then complete application-owned work:

  • production identity, icons, versioning and metadata;
  • HTTPS endpoint, authentication and retention policy;
  • accessibility and poor/offline-network behavior;
  • platform privacy manifests and usage descriptions;
  • signing/provisioning and secret handling;
  • physical-device and beta-channel tests;
  • store policy, screenshots, disclosure and review.

Tauri supplies legitimate packages and installers; stores decide whether the finished product meets their technical, functionality, privacy and content requirements.

Bounded Offline Synchronization for Omni

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

Rullst’s opt-in offline-sync feature supplies the native state and encrypted snapshot boundary needed to build a resilient Omni client. It is not mounted automatically by make:omni: the generated shell remains a minimal web shell, and the application chooses which entities may exist offline.

The contract deliberately keeps the server authoritative. Local records are a cache; queued writes are proposals. Roles, tenant membership, scores, streaks, entitlements and trusted time must be recomputed or revalidated by the server.

Enable the native profile

[dependencies]
rullst = { version = "12.0.0-rc.1", features = ["offline-sync"] }

The encrypted snapshot codec is native-only. A browser offline implementation needs a separately reviewed IndexedDB/service-worker adapter and browser tests; the current feature does not silently substitute local storage.

Create an account-bound state

#![allow(unused)]
fn main() {
use rullst::offline_sync::{
    OfflineAccountId, OfflineSnapshotCipher, OfflineSyncPolicy, OfflineSyncState,
};

fn load_device_key() -> Result<[u8; 32], std::io::Error> {
    Err(std::io::Error::other("platform secure storage adapter omitted"))
}
fn create_state() -> Result<(), Box<dyn std::error::Error>> {
let account = OfflineAccountId::new("account_01j8student")?;
let policy = OfflineSyncPolicy::default();
let mut state = OfflineSyncState::new(account.clone());

// Load these 32 high-entropy bytes from Keychain/Keystore-class storage.
// Never embed a production key in source or store it next to the snapshot.
let device_key = load_device_key()?;
let cipher = OfflineSnapshotCipher::new("device-key-2026-01", device_key)?;
let _ = (cipher, policy, state);
Ok(())
}
}

The cipher uses randomized AES-256-GCM. Its authenticated data binds the envelope domain, key id and exact account id, so another account, key, modified nonce/tag or modified ciphertext fails closed. The owned key is redacted from Debug and zeroized on drop, but Rust cannot erase copies made beforehand.

Queue a lesson attempt

Use a unique event entity for an immutable attempt. Do not queue an authoritative score: the server grades the answer and returns its own revision and value.

#![allow(unused)]
fn main() {
use rullst::client_contract::IdempotencyKey;
use rullst::offline_sync::{
    OfflineAccountId, OfflineEntityKey, OfflineMutation, OfflineSyncPolicy,
    OfflineSyncState,
};
use serde_json::json;

fn queue_attempt() -> Result<(), Box<dyn std::error::Error>> {
let policy = OfflineSyncPolicy::default();
let account = OfflineAccountId::new("account_01j8student")?;
let mut state = OfflineSyncState::new(account);
let client_epoch_ms = 1_800_000_000_000;
let attempt = OfflineMutation::upsert(
    IdempotencyKey::new("attempt_01j8french7")?,
    OfflineEntityKey::new("lesson_attempts", "attempt_01j8french7")?,
    None,
    client_epoch_ms, // UX ordering only; never trusted by the server.
    json!({
        "lesson_id": "french-basics-7",
        "answer": "bonjour"
    }),
)?;
state.queue(policy, attempt)?;

let batch = state.push_batch(policy, 25)?;
let _ = batch;
Ok(())
}
}

Queue order is FIFO and replay keys are unique across pending and conflicted operations. Counts, payload bytes, snapshot bytes and push size all have configurable limits below hard framework ceilings.

Send SyncPushBatch inside the versioned rullst.client envelope. On the server, authenticate the session, derive account/tenant context, validate the entity and domain payload, and atomically persist both the replay key and its result/effect. A client key alone does not provide exactly-once behavior.

Apply authoritative results

The server returns a SyncPushResult whose outcomes are one of:

  • Applied: durable result plus the current server record or tombstone;
  • Conflict: current server record plus a stable bounded code;
  • Rejected: stable code and an explicit retry hint.

Applying a response is transactional within the state value. Unknown or duplicated replay keys, mismatched entities, regressing revisions, reused revisions with different data and regressing server time leave the original state unchanged.

A retryable rejection stays in the FIFO queue. A conflict or permanent rejection moves the proposal out of automatic replay. The application must either accept server state or retry the original proposal against the latest server revision with a new idempotency key. There is no automatic client-wins mode.

Coordinate a bounded foreground sync

Implement OfflineSyncTransport on an application adapter that already owns its authenticated session, TLS policy and endpoint. The trait uses static dispatch: it does not box a provider or place credentials in offline state. The account_id argument is only a local binding/routing hint; the server must derive the real account, tenant, ownership and authorization from the authenticated request.

Map the response’s versioned client-contract envelope into AuthoritativePush or AuthoritativePull, including server-authored time, then run one bounded foreground attempt:

The following integration fragment intentionally depends on the application-owned OfflineSyncTransport implementation described above:

use rullst::offline_sync::{
    OfflineSyncCoordinator, OfflineSyncRunPolicy,
};

let run_policy = OfflineSyncRunPolicy::new(
    25,     // mutations per push
    4,      // push requests this run
    20,     // pull pages this run
    15_000, // timeout for every transport request
)?;

let report = OfflineSyncCoordinator::synchronize(
    &authenticated_transport,
    &mut state,
    policy,
    run_policy,
).await?;

The coordinator pushes before pulling, stops retrying a batch that produced no local progress, bounds page/request counts, times out every transport future and rejects has_more when the opaque cursor does not advance. It does not invent retry delays or silently start an OS background task. Successfully accepted pages remain in state if a later request fails, so seal and atomically persist the state after both success and error paths. The server must persist replay decisions atomically so a request accepted before a client crash can safely return the same result later.

Pull, reconnect and full resync

Apply incremental SyncPullPage values in order. If a newer server revision touches an entity with a divergent local proposal, Rullst preserves that proposal as an explicit conflict before caching server state.

When the server sets requires_full_resync, apply_pull changes nothing and returns FullResyncRequired. Fetch a complete authorized snapshot, then call replace_server_snapshot. New local entities whose base is still absent stay pending; stale proposals become conflicts instead of disappearing.

recover_server_cache clears derived records, cursor and accepted server time while preserving pending work and conflicts. It is useful after an application decides that its server cache is unusable; it is not a substitute for detecting corrupt encrypted bytes, which already fail authentication or schema checks.

Persist and erase

The persistence call below is also an application integration fragment: cipher, policy, state, and account come from the preceding steps, while platform_store_atomically is the reviewed platform adapter Rullst deliberately does not supply:

let encrypted = cipher.seal(policy, &state)?;
platform_store_atomically(&encrypted)?;

let restored = cipher.open(policy, &account, &encrypted)?;

Write the encrypted bytes atomically inside the platform’s application data directory. Store the key separately in Keychain/Keystore-class storage and apply OS backup/privacy policy intentionally. Rullst currently supplies neither that platform adapter nor background scheduling.

For logout/account deletion, call state.erase(), delete every persisted snapshot (including backups and temporary files), and remove the associated secure-storage key. The method performs logical state erasure; it cannot erase prior clones, filesystem snapshots, cloud backups or server data.

What remains before calling an app offline-first

  • reviewed Keychain/Keystore and atomic file/SQLite adapters per platform;
  • schema migration implementations beyond the current fail-closed v1 marker;
  • a concrete authenticated HTTP adapter plus application retry/backoff, cancellation and OS background execution around the bounded coordinator;
  • browser storage support where the web product needs offline data;
  • airplane-mode, process-kill, quota, corrupt-state, account-switch and reconnection tests on physical Android/iOS devices;
  • application-specific conflict UX, retention and erasure verification.

The module closes the protocol/state/cryptographic foundation, not those platform and product obligations.

45. Accessible Academy Media

Rullst’s LMS blueprint generates a bounded lesson-presentation foundation for video and audio. It belongs to the web-first Academy slice: authorization and progress remain server-owned, while the browser receives accessible media markup and an escaped transcript.

Generate the complete Academy starter

cargo rullst new language-academy --default --blueprint lms \
  --skip-initial-migration
cd language-academy
cargo test --offline --all-targets

The complete starter includes the integrated curriculum, assessment, gamification, automation and notification journey. A smaller learning foundation is available with --lms-modules auth,learning.

Lesson media contract

Generated lessons store these fields:

FieldContract
media_kindClosed renderer values: video or audio.
media_urlHTTPS URL or absolute same-origin path; control characters and backslashes are rejected.
captions_urlRequired valid source for video; normally a same-origin .vtt path.
transcriptRequired and bounded; the HTML renderer escapes it.
language_tagRequired bounded ASCII language tag such as en or pt-BR.

For production, prefer application-owned same-origin or signed media. Put a caption file at static/media/lesson.pt-BR.vtt and store the public source as /static/media/lesson.pt-BR.vtt; Server mounts the local static/ directory at /static.

WEBVTT

00:00.000 --> 00:04.000
Bem-vindo à primeira atividade.

The blueprint intentionally does not copy a media binary. Add your reviewed audio/video asset or application-specific object-storage delivery, then use a same-origin path such as /static/media/lesson.webm. If you choose a remote host, add only that reviewed origin to the application’s media-src CSP; do not weaken the policy to arbitrary HTTPS.

What the generated player enforces

  • no autoplay;
  • native video/audio controls;
  • a caption track for every video;
  • an always-available transcript for video and audio;
  • visible keyboard focus and nonce-bound styles;
  • escaped title and transcript values;
  • fail-closed rendering for unknown kinds, insecure sources or invalid accessibility metadata.

The protected lesson controller still checks the authenticated learner’s school, enrollment, entitlement and release policy before rendering the player. Progress submissions use CSRF and idempotency data and remain authoritative in the database.

Evidence boundary

Repository tests materialize the generated SQLite project and exercise both successful renderers and negative source/metadata cases. They do not prove codec support, buffering behavior, screen-reader quality, subtitle accuracy, microphone or speech recognition, physical mobile devices, CDN delivery or app store behavior. Run browser accessibility tests with your real content and deployment before making those claims.

46. Server-Authoritative Learning Activities

Interactive learning clients must never decide their own points. The complete Academy starter generates a static-dispatch ActivityEvaluator boundary that turns an untrusted submission into a server-authored ActivityResult.

Evaluate a single-choice exercise

Load the correct option, maximum score and canonical SHA-256 digest from your trusted, versioned rules. Do not accept them from an HTTP form.

This block is compiled inside the generated Academy starter, whose application-local crate::services modules provide the two imported contracts:

use crate::services::activity_contract::{
    ActivityAttempt, ActivityKind, SingleChoiceEvaluator,
    SingleChoiceSubmission, ACTIVITY_SCHEMA_VERSION, evaluate_activity,
};
use crate::services::score_service::{ScoreReceipt, record_activity_result};
use rullst_security::UserContext;

async fn grade(
    context: &UserContext,
    selected_option_id: i32,
) -> Result<ScoreReceipt, Box<dyn std::error::Error>> {
    // These three values represent data loaded from trusted server state.
    let evaluator = SingleChoiceEvaluator::new(7, 100, "a".repeat(64))?;
    let attempt = ActivityAttempt {
        schema_version: ACTIVITY_SCHEMA_VERSION,
        attempt_key: "attempt-language-1".to_string(),
        activity_id: 42,
        subject_user_id: 9,
        kind: ActivityKind::Exercise,
        ruleset_version: "portuguese-a1-v3".to_string(),
        started_at_epoch_seconds: 1_800_000_000,
        state_json: r#"{"prompt_version":3}"#.to_string(),
    };
    let validated = evaluate_activity(
        context,
        attempt,
        &SingleChoiceSubmission { selected_option_id },
        1_800_000_030,
        &evaluator,
    )?;
    Ok(record_activity_result(context, validated).await?)
}

The submission contains an option ID but no score, maximum or answer key. The contract rejects cross-owner access, activity-kind mismatch, invalid identity, non-object or oversized state, reversed time, out-of-range results and non-canonical evidence digests.

Persist one authoritative transaction

In the complete Academy starter, pass the opaque ValidatedActivityResult directly to record_activity_result. Callers cannot construct that wrapper or replace its points. The bridge loads the persisted activity and rechecks its course, kind, maximum, ruleset, season, evidence digest and exact evaluator configuration before one transaction:

  1. appends a deduplicated ScoreEvent v2;
  2. updates the authoritative leaderboard projection; and
  3. appends the strict score_recorded v2 outbox event before commit.

The configuration is checked again under the transaction’s policy lock. A concurrent answer-policy edit therefore cannot commit a result graded against stale rules.

The complete starter also persists the bounded attempt/result in that transaction. Identical retries are no-ops; changing the selected option under the same attempt key is a conflict. The database scopes that client key by learner and activity, while the score-event key is derived server-side, so keys chosen by different learners cannot reserve one another’s attempts.

Submit through the authenticated route

The generated owner-only route is POST /activities/{id}/attempts. After your normal authenticated session and CSRF ceremony, its JSON body contains only:

{
  "attempt_key": "attempt-language-1",
  "selected_option_id": 7
}

Do not add learner ID, ruleset, answer key, points, maximum, evidence or client time to this payload. The route and persisted activity supply them. The stored bounded state_json is hidden in Nexus but is still application data: include activity_attempts in retention, export and erasure policy where applicable.

For a bounded pair-matching activity, use POST /activities/{id}/attempts/matching:

{
  "attempt_key": "attempt-match-1",
  "pairs": [
    { "left_id": 1, "right_id": 11 },
    { "left_id": 2, "right_id": 12 }
  ]
}

The persisted policy owns the complete left/right ID sets and correct mapping. The request must be a complete permutation of two to eight pairs; unknown or duplicate IDs fail closed. Input order does not affect replay identity, partial credit uses integer server scoring, and no answer text crosses this endpoint.

For typed recall, configure a closed accepted_answers array and submit to POST /activities/{id}/attempts/typed:

{
  "attempt_key": "attempt-recall-1",
  "answer": "  Ownership  "
}

The built-in evaluator caps UTF-8 input at 512 bytes, rejects control characters and trims it. When case_sensitive is false it applies Unicode lowercase before exact comparison. It does not perform NFC/NFKC normalization, accent folding, stemming or fuzzy matching; add a reviewed domain evaluator when your pedagogy requires those semantics. The durable submission key is SHA-256 over the exact policy binding plus normalized input, so the raw answer is absent from activity_attempts. A digest is not encryption and may remain personal data; retain/erase it under the same lifecycle as the attempt.

The full Academy quiz service follows the same score-event invariants but is still a separate evaluator. Do not claim generic quiz or game integration until those paths are unified and their materialized tests pass.

Implement another ActivityEvaluator when spelling needs language-specific normalization or when a listening/game exercise needs different trusted rules. Keep the concrete evaluator type visible; do not use a runtime registry merely to hide domain differences.

47. Durable Spaced-Review Queue

The complete LMS blueprint can turn newly applied authoritative activity scores into a durable due-review queue. This is useful for vocabulary, facts, concept recall and other practice that benefits from repeated exposure.

Generate the complete Academy starter

cargo rullst new language-academy --default --blueprint lms \
  --skip-initial-migration
cd language-academy
cargo test --offline --all-targets

Review scheduling belongs to the complete starter because it composes activity evaluation, score persistence, school/course authorization and enrollment. The smaller detached LMS profiles do not include this vertical.

Enable a review policy

Each reviewable activity has one activity_review_policies row. Use the generated Nexus model or application-owned parameterized administration code to configure these fields:

FieldAccepted contract
algorithm_versionExactly rullst-box-v1.
passing_ratio_milli500–1000; 800 means 80%.
first_interval_secondsOne hour through 31 days.
lapse_interval_seconds60 seconds through the first interval.
maximum_interval_secondsFirst interval through five years.
enabledExactly 1 to schedule; 0 disables scheduling.

Malformed policy or existing state fails the score transaction closed. A different algorithm version also requires an explicit application migration; it is never silently reinterpreted.

Submit an authoritative exercise

Use one of the authenticated activity routes described in the previous tutorial:

  • POST /activities/{id}/attempts for single choice;
  • POST /activities/{id}/attempts/matching for bounded matching;
  • POST /activities/{id}/attempts/typed for typed recall.

The route obtains identity, answer policy, points and time from server state. When the score is new, the same database transaction updates activity_review_states before it commits the score and outbox event. An exact retry is a no-op and cannot make the interval grow twice.

rullst-box-v1 applies a deliberately small inspectable transition:

  • a passing result increments repetitions and schedules at least the configured first interval;
  • a perfect result also raises the bounded ease value;
  • later passes multiply the prior interval by that ease, capped by the policy;
  • a lapse resets repetitions, increments lapses, lowers bounded ease and uses the configured lapse interval.

Read the learner’s due queue

After the normal authenticated session, request:

GET /reviews/due?limit=20

The handler derives the learner and current time from server extensions; there is no learner ID in the query. It accepts a limit from 1 through 50, checks the active school membership and returns only activities whose course scope and active enrollment still authorize the learner. Results are ordered by due time and then activity ID.

[
  {
    "activity_id": 42,
    "course_id": 3,
    "title": "Recall: ownership",
    "due_at_epoch": 1800086400,
    "repetitions": 1,
    "lapses": 0
  }
]

Render this response in the web application as the source of truth. An Omni shell can present the same web-first route, but offline proposals must still be reconciled with the server before the schedule is considered authoritative.

Product and evidence boundary

The state includes learner/activity history and must participate in retention, export and erasure policy. The generated foundation does not prove that its intervals improve learning and does not implement FSRS, SM-2, speech recognition, language-specific answer normalization, AI personalization, streaks or a polished review UI. Tune policy only with reviewed product rules and measured outcomes; use an explicit version plus migration for any new algorithm.

Repository evidence covers deterministic pass/lapse transitions and a materialized SQLite journey across single-choice, matching and typed recall, including exact replay and cross-user denial. PostgreSQL/MySQL contention, real-user efficacy, browser UX and physical Omni devices remain separate gates.

48. Signed Local OIDC Testing

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

Rullst Connect includes an explicitly mounted local identity-provider fixture so an application can exercise a cryptographically verified OIDC flow without a third-party account. Unlike an in-process provider stub, this path traverses HTTP discovery, authorization, token exchange, JWKS retrieval and ID-token verification through the ordinary OidcProvider implementation.

Enable and bind the fixture

Enable the Axum feature in development:

[dev-dependencies]
rullst-connect = { version = "12.0.0-rc.1", features = ["axum"] }

Mount the router only on an exact loopback listener:

#![allow(unused)]
fn main() {
use rullst_connect::mock_idp::{MockIdpConfig, mock_router_with_config};

async fn run() -> Result<(), Box<dyn std::error::Error>> {
let issuer = "http://127.0.0.1:8080";
let callback = "http://127.0.0.1:3000/auth/callback";
let config = MockIdpConfig::try_new(
    issuer,
    "academy-local-client",
    "academy-local-secret",
    callback,
)?;
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
axum::serve(listener, mock_router_with_config(config)).await?;
Ok(())
}
}

MockIdpConfig rejects non-HTTP or non-loopback issuers and callbacks, issuer paths, empty/oversized identifiers and control characters. The application must still bind the actual listener to loopback; possession of a router does not control how the host serves it.

Configure the ordinary OIDC client

Use non-placeholder local credentials so OidcProvider traverses the HTTP fixture instead of selecting the separate network-free credential fallback:

#![allow(unused)]
fn main() {
use rullst_connect::providers::OidcProvider;

async fn provider() -> Result<OidcProvider, rullst_connect::ConnectError> {
OidcProvider::discover(
    "http://127.0.0.1:8080",
    "academy-local-client",
    "academy-local-secret",
    "http://127.0.0.1:3000/auth/callback",
).await
}
}

Compose this provider with the server-bound session ceremony from Tutorial 42. That ceremony generates and stores state, PKCE verifier and OIDC nonce; the fixture binds the challenge and nonce to its one-shot authorization grant. The callback then supplies the consumed values to OidcProvider::get_user.

What the test proves

The checked-in loopback regression proves that the current client can:

  • validate discovery metadata on the exact issuer origin;
  • preserve an exact registered client and callback;
  • exchange one expiring authorization code only once;
  • reject a missing, malformed or mismatched S256 PKCE verifier;
  • bind the requested nonce into a signed ID token;
  • select the Ed25519 public key by kid from JWKS;
  • validate EdDSA signature, issuer, audience, expiry and nonce; and
  • accept only an issued, unexpired bearer token at userinfo.

Authorization grants and access-token digests are process-local and capped at 64 records. A restart erases them. This is intentional test behavior, not a durable identity service.

Non-production boundary

The signing seed, client credentials and identity are deterministic public fixtures. The router has no interactive login, consent, refresh-token, device, federation, account-management, key-rotation or administrative lifecycle. It has not passed an OIDC conformance suite. Never expose it publicly, reuse its key or credentials, or use a successful fixture test as evidence that a live provider/deployment is correctly configured.

49. Bounded Brokered Messaging

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

This tutorial starts with rullst-messaging’s deterministic in-memory broker and then switches the same trait contract to the durable local SQLite adapter. Remote broker protocols remain separate adapters.

1. Enable the umbrella feature

[dependencies]
rullst = { version = "12.0.0-rc.1", features = ["messaging"] }

Use messaging-sqlite instead of messaging for durable local state. A direct dependency uses rullst-messaging = { version = "12.0.0-rc.1", features = ["sqlite"] }.

2. Create a bounded broker

#![allow(unused)]
fn main() {
use rullst::messaging::{BrokerConfig, InMemoryBroker};

fn create_broker() -> Result<(), rullst::messaging::MessagingError> {
let config = BrokerConfig::try_new("billing")?
    .with_limits(
        10_000,        // retained messages
        128,           // consumer-group subscriptions
        5,             // delivery attempts
        1024 * 1024,   // payload bytes
    )?;
let broker = InMemoryBroker::new(config);
let _ = broker;
Ok(())
}
}

The hard ceilings prevent an accidental configuration from turning the local broker into unbounded memory state. Capacity exhaustion fails closed until terminal messages are explicitly purged.

Persist the same contract locally

#![allow(unused)]
fn main() {
use rullst::messaging::{BrokerConfig, SqliteBroker};

async fn open() -> Result<(), rullst::messaging::MessagingError> {
let config = BrokerConfig::try_new("billing")?
    .with_limits(10_000, 128, 5, 1024 * 1024)?;
let broker = SqliteBroker::connect("sqlite://storage/messages.sqlite", config).await?;
let _ = broker;
Ok(())
}
}

All mutations use serialized SQLite write transactions. A committed publish or ACK survives restart, while an uncommitted operation rolls back. In-flight leases also survive; after expiry the next broker operation requeues or dead-letters them. Multiple processes may share one file and namespace, but reopening it with different limits is rejected. This is local durability, not network replication, automatic failover or exactly-once side effects.

Protect header values and payloads

SqliteBroker::connect deliberately retains the compatible plaintext profile. For a new namespace/database, load a 32-byte high-entropy key from a secret manager and select the encrypted profile explicitly:

#![allow(unused)]
fn main() {
use rullst::messaging::{
    BrokerConfig, MessagingKeyring, MessagingStorageKey, SqliteBroker,
};

async fn open_encrypted(
    secret_manager_key: [u8; 32],
) -> Result<(), rullst::messaging::MessagingError> {
let primary = MessagingStorageKey::try_new("messages-2026-09", secret_manager_key)?;
let keyring = MessagingKeyring::new(primary);
let broker = SqliteBroker::connect_encrypted(
    "sqlite://storage/messages.sqlite",
    BrokerConfig::try_new("billing")?,
    keyring,
).await?;
let _ = broker;
Ok(())
}
}

To rotate, put the new primary first and append the old decryption key:

#![allow(unused)]
fn main() {
use rullst::messaging::{MessagingKeyring, MessagingStorageKey};

fn rotate(new_key: [u8; 32], old_key: [u8; 32])
    -> Result<MessagingKeyring, rullst::messaging::MessagingError> {
let keys = MessagingKeyring::new(MessagingStorageKey::try_new(
    "messages-2026-10",
    new_key,
)?)
.with_decryption_key(MessagingStorageKey::try_new(
    "messages-2026-09",
    old_key,
)?)?;
Ok(keys)
}
}

New publications use the primary. Old records are not silently rewritten and their keys remain mandatory until those messages become terminal and are purged. Plaintext/encrypted profiles cannot be mixed or changed in place; move through a new namespace/database with an application-reviewed republish procedure. Passwords are not AES keys, and source literals are unsuitable for production key custody.

The encrypted profile protects header values and payloads. Topic, event and content type, message/key IDs, timestamps, idempotency key, fingerprint and delivery state remain visible metadata. Protect the database file and backups, control rollback, rehearse key recovery and authorize topic access separately.

3. Register a consumer group and publish

#![allow(unused)]
fn main() {
use rullst::messaging::{
    MessageBroker, PublishRequest, StartPosition, SubscriptionRequest, TraceContext,
};

async fn example(
    broker: &rullst::messaging::InMemoryBroker,
) -> Result<(), rullst::messaging::MessagingError> {
broker
    .subscribe(SubscriptionRequest::try_new(
        "invoices",
        "receipt-mailers",
        StartPosition::Earliest,
    )?)
    .await?;

let trace = TraceContext::try_with_state(
    "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
    "vendor=value",
)?;
let request = PublishRequest::try_new(
    "invoices",
    "invoice.paid",
    "invoice/2026-0042/paid/v1",
    br#"{"invoice_id":"2026-0042"}"#.to_vec(),
)?
.with_content_type("application/json")?
.with_trace_context(&trace)?;

let first = broker.publish(request.clone()).await?;
let replay = broker.publish(request).await?;
assert_eq!(first.id(), replay.id());
assert!(replay.is_duplicate());
Ok(())
}
}

Idempotency is scoped to the topic. The same topic/key with different content returns MessagingError::IdempotencyConflict; it never silently overwrites the original publication.

Only validated W3C version-00 traceparent and a conservative tracestate subset are copied. baggage is deliberately excluded because arbitrary application metadata may contain credentials or personal data. Sampling, export, retention and tenant-aware correlation remain host responsibilities.

4. Receive, acknowledge, retry, or dead-letter

#![allow(unused)]
fn main() {
use rullst::messaging::{
    FailureCode, MessageBroker, ReceiveRequest, RetryDisposition,
};
use std::time::Duration;

async fn consume(
    broker: &rullst::messaging::InMemoryBroker,
) -> Result<(), rullst::messaging::MessagingError> {
let request = ReceiveRequest::try_new(
    "invoices",
    "receipt-mailers",
    "worker-1",
    10,
    Duration::from_secs(30),
)?;

for delivery in broker.receive(request).await? {
    let effect_succeeded = true;
    if effect_succeeded {
        broker.ack(delivery.ack_token()).await?;
    } else {
        let disposition = broker
            .retry(
                delivery.ack_token(),
                Duration::from_secs(15),
                FailureCode::try_new("mail.transient")?,
            )
            .await?;
        if disposition == RetryDisposition::DeadLettered {
            // Alert through an application-owned operational channel.
        }
    }
}
Ok(())
}
}

If an adapter needs a stable broker-neutral representation, encode the received envelope explicitly:

#![allow(unused)]
fn main() {
use rullst::messaging::{BrokerConfig, MessageEnvelope, WireEnvelopeCodec};

fn frame(
    envelope: &MessageEnvelope,
) -> Result<(), rullst::messaging::MessagingError> {
let config = BrokerConfig::try_new("billing")?;
let bytes = WireEnvelopeCodec::encode(envelope, &config)?;
let decoded = WireEnvelopeCodec::decode(&bytes, &config)?;
assert_eq!(&decoded, envelope);
Ok(())
}
}

The v1 frame is canonical, bounded by BrokerConfig, namespace-bound and rejects unknown versions, truncation, trailing bytes and non-canonical fields. It carries an accepted envelope only. It does not retain the caller’s publish idempotency key, connect to a broker, or map remote ACK/retention semantics, so it cannot be presented as a Kafka/NATS/RabbitMQ adapter by itself.

An ACK token is an opaque, single-use capability bound to one group and one lease. Once it expires, the message is requeued or dead-lettered at the attempt ceiling. Do not serialize tokens into logs or application records.

5. Design for at-least-once delivery

The consumer may complete the mail/payment/webhook effect and stop before the ACK reaches the broker. Therefore:

  1. use delivery.envelope().id() as a stable deduplication key;
  2. claim that key transactionally in the side-effect system where possible;
  3. ACK only after the effect is durably accepted;
  4. treat retries and dead letters as normal operational states;
  5. authorize topics and tenant scope in the host application.

The in-memory broker loses state on process exit. The SQLite adapter retains state; its default profile is plaintext, while the explicit encrypted profile protects header values and payloads but not routing/idempotency metadata. The host owns key custody, file permissions, protected backup/restore, rollback detection, retention, disk monitoring and topic/tenant authorization. A future remote adapter is supported only after it passes the shared contract plus its own protocol, restart, and fault matrix; an adapter name alone is not durability evidence.

6. Relay a relational outbox after commit

Enable messaging-orm-outbox on the umbrella crate. Commit the domain mutation and rullst_orm::Outbox::enqueue together as described in the transactional outbox tutorial. Then bind one exact outbox stream to one broker topic:

#![allow(unused)]
fn main() {
use rullst::messaging::{
    InMemoryBroker, MessagingError, OrmOutboxRelay,
};

fn configure_relay(
    broker: InMemoryBroker,
) -> Result<OrmOutboxRelay<InMemoryBroker>, MessagingError> {
    OrmOutboxRelay::try_new("tenant-a", "domain-events", broker)
}
}

A supervised worker claims and relays one event:

#![allow(unused)]
fn main() {
use rullst::messaging::{InMemoryBroker, OrmOutboxRelay};
use rullst_orm::Outbox;

async fn relay_one(relay: &OrmOutboxRelay<InMemoryBroker>) {
    let claim = match Outbox::claim_next("tenant-a", "worker-a", 30, 8).await {
        Ok(Some(claim)) => claim,
        Ok(None) | Err(_) => return,
    };
    match relay.relay_and_ack(claim).await {
        Ok(receipt) if !receipt.outbox_acknowledged() => {
            // The lease expired; a later claim will replay the same event key.
        }
        Ok(_) => {}
        Err(error) => {
            // Record a secret-free operational failure and let the lease expire.
            let _accepted_before_ack_failure = error.accepted_publication();
        }
    }
}
}

The ordering is intentionally publish then ACK. A stop in between causes a new ORM claim to publish the same event key and content; the broker returns its original ID as a duplicate. This is at-least-once relay with bounded idempotency, not an atomic transaction across two systems. Keep the worker supervised and make the final consumer idempotent too.

Auditable Revisions

Rullst SQLx models can bind each instance mutation to a validated principal and restore eligible update revisions as a new compensating mutation. The audit row and model write share a transaction; an audit failure rejects the write.

This contract is opt-in and does not infer identity from request data. Install the principal and tenant contexts only after your authentication and authorization boundary has validated them.

1. Mark the model and install the table

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};

#[derive(Debug, Clone, FromRow, rullst_orm::Orm)]
#[orm(table = "projects", auditable, tenant_column = "account_id")]
struct Project {
    id: i32,
    account_id: String,
    name: String,
    #[orm(masked)]
    api_token: String,
}

async fn install() -> Result<(), rullst_orm::Error> {
rullst_orm::audit::create_audit_table().await?;
Ok(())
}
}

create_audit_table also adds the v2 columns to the legacy audit table. Legacy records retain version 1 and cannot be restored.

Fields whose names contain password, token, secret, API key, credential, cookie or similar markers must use #[orm(masked)] on auditable models. Their values are never retained in audit payloads or reverse patches.

2. Bind a mutation to trusted context

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};
#[derive(Debug, Clone, FromRow, rullst_orm::Orm)]
#[orm(table = "projects", auditable, tenant_column = "account_id")]
struct Project { id: i32, account_id: String, name: String, #[orm(masked)] api_token: String }
use rullst_orm::audit::{AuditContext, with_audit_context};
use rullst_orm::with_tenant;

async fn update(mut project: Project) -> Result<(), rullst_orm::Error> {
let audit = AuditContext::user("user-42")?
    .with_correlation_id("request-01J...")?;

with_tenant("account-a", with_audit_context(audit, async move {
    project.name = "New name".to_string();
    project.save().await
}))
.await?;
Ok(())
}
}

Background work can use AuditContext::service("billing-worker"); reviewed maintenance can use AuditContext::system("migration-2026-08"). Empty, padded, control-character, and oversized identifiers fail validation. An auditable mutation without an active context also fails closed.

3. Restore an eligible update

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};
#[derive(Debug, Clone, FromRow, rullst_orm::Orm)]
#[orm(table = "projects", auditable, tenant_column = "account_id")]
struct Project { id: i32, account_id: String, name: String, #[orm(masked)] api_token: String }
use rullst_orm::audit::{AuditContext, with_audit_context};
use rullst_orm::with_tenant;
async fn restore(project: Project, audit_id: i32) -> Result<Project, rullst_orm::Error> {
let actor = AuditContext::user("admin-7")?
    .with_correlation_id("support-case-1042")?;

let restored = with_tenant("account-a", with_audit_context(actor, async move {
    project
        .restore_revision(audit_id, "approved support rollback")
        .await
}))
.await?;
Ok(restored)
}
}

The returned model contains the restored state. Rullst verifies the exact model, ID, active tenant, patch version and current post-state before writing. PostgreSQL and MySQL/MariaDB lock the row during this check. Success creates a new updated audit entry with reverted_audit_id and the bounded reason; audit history is not rewritten or deleted.

Use restore_revision_with_tx when a caller-owned SQLx transaction must include other relational writes. Prefer Orm::transaction when generated post-commit observers must wait for the final commit decision.

Deliberate refusal cases

Restoration fails for:

  • legacy v1, create, or delete entries;
  • a revision from another model, record, or tenant;
  • a row changed again after the selected revision;
  • a patch containing a redacted/sensitive change;
  • malformed, empty, too deep, too large, or excessively wide patches.

Those refusals prevent audit history from becoming an unsafe generic backup mechanism. Use reviewed database backups and restore drills for disaster recovery. Bulk update/delete builders do not invent per-row audit history, and cross-process export or notification should use the transactional outbox.

Supervised Development Auto-Reload

Release-audit follow-up: the implementation and final workspace validation are still in progress. See the audit evidence; this tutorial is not a claim that the current branch is already release-ready.

Rullst v12’s development command rebuilds and restarts a directly linked application. This keeps Tokio, ORM pools, sessions and other process globals in one runtime. It replaces the advertised DLL-swap profile after Windows/LMS testing exposed an uninitialized ORM in the loaded library.

Run the development loop

Generate a normal application and use either development command:

cargo rullst new learning-app --default --blueprint blank \
  --database sqlite --skip-initial-migration
cd learning-app
cargo rullst dev
# Or, in an interactive terminal:
cargo rullst dash

There is no hot-reload question in the v12 wizard. Both commands supervise reloads automatically; use cargo run for ordinary execution.

What a save does

flowchart LR
    A[Save source or assets] --> B[Coalesce changes]
    B --> C[Compile executable]
    C -->|Failure| D[Keep current application and show diagnostic]
    C -->|Success| E[Snapshot new executable]
    E -->|Failure| D
    E -->|Success| F[Stop owned child and start replacement]
    F --> G[Verify process generation over HTTP]
    G --> H[Browser refresh]

The watcher observes source, static assets, templates and manifest/configuration files, including atomic editor renames. It excludes build outputs, Git data and runtime database/log files. Events while a build is running remain pending for a later rebuild. Saving CSS also triggers this conservative rebuild/restart path; no compilation-free claim is made.

Initial migrations run before the first start when the generated migration directory exists. Later migrations are explicit: use the dashboard migration action or cargo rullst db:migrate. The dashboard queues migrations through the supervisor, using its current executable snapshot with bounded output and cancellation cleanup; the action is serialized with rebuilds. It does not compile an unsaved or unbuilt migration into that snapshot. Restarting a process does not apply arbitrary database-schema changes safely.

Failure and state boundaries

EventBehavior
Compiler errorKeep the current process running; retain bounded diagnostics.
Snapshot copy/create errorKeep the current process running and report the failure before stopping anything.
Successful buildSnapshot the executable, stop the owned child, start the replacement.
Replacement cannot spawnAttempt to restart the prior executable snapshot and report the error.
Replacement exits during startupReport the exit; save a correction to rebuild and retry.
Readiness cannot be verified in 15 secondsReport the limitation; do not claim successful readiness.
Dashboard exit / Ctrl+CCancel owned build/migration work and terminate/reap the application, subject to the Windows descendant limitation below.
Configured port changesRestart the CLI so its dashboard/readiness target follows the new port.

The old binary is copied before execution so Windows does not lock Cargo’s output during the next build. Cleanup targets only the snapshot created by this supervisor. On Unix the child receives a two-second shutdown interval, followed by termination of its owned process group if needed; group cleanup precedes reaping the leader. Windows uses best-effort tree termination: without Job Objects it cannot guarantee cleanup of orphan descendants after their parent has exited, and it does not promise graceful request drain.

All process-local state resets, including in-memory sessions/queues/caches. Persist important state explicitly. In-flight requests may be interrupted. This is a local development loop, not zero-downtime production deployment.

Browser and security boundary

The debug/development server serves a local reload script and an opaque generation marker under /_rullst/dev-*. The script polls the same origin and refreshes only after a different valid generation responds. Readiness checks match the child generation rather than trusting any service on the port. The marker is not a secret or an authentication credential. These endpoints cannot instruct the server to compile, launch a process, or swap a library.

Eligible full-document, known-size, uncompressed HTML up to 10 MiB receives the script and a nonce seeded before inner header middleware. HTMX partial requests, streaming/larger/compressed responses pass through without injection. A browser singleton prevents duplicate pollers, and changed HTML is marked no-store. Polling has a request timeout and bounded retry delay. Browser refresh loses unsaved DOM-only state. Custom routers that do not use the Rullst Server development composition may need manual browser refresh.

Production/release builds do not mount this browser surface. Keep development servers on loopback and do not override their host for an untrusted network. The current supervisor probes 127.0.0.1 and the configured port; custom HOST or RULLST_HOST bindings, including IPv6-only loopback, are not equivalent verified-readiness configurations. Keep the default host and restart the CLI after changing its port configuration.

Existing DLL scaffolds and the v13 decision

The v12 CLI rejects the old --hot-reload generation flag and removes HOT_RELOAD when it launches a child, so old generated projects use their directly linked router unless their application reloads that variable itself. Remove legacy HOT_RELOAD entries from .env as well: application-owned dotenv loading can otherwise restore them. Regenerate fresh projects for release acceptance; already edited application code may need application-specific migration.

The existing library loader is retained only as a legacy experimental boundary. Passing Rust, Axum, Tokio or SQLx objects across a library ABI does not provide a stable interoperability contract. Do not reintroduce cross-library ORM pools.

For v13, compare supervised restart with any proposed replacement using real blueprints and databases on Windows, Linux and macOS. Measure cold/warm reload time, failed-build recovery, cancellation, memory growth, process cleanup and state ownership. Restore DLL swapping only if its safety requirements can be established and it offers a measured practical benefit; keeping supervised restart remains a valid v13 outcome.

Terminal accessibility

Use RULLST_REDUCED_MOTION=1 cargo rullst dash for static rendering with colors, or NO_COLOR=1 cargo rullst dash for static color-free output. Use cargo rullst dev with redirected/non-interactive output.

Tutorial 52: Typed Server Functions

#[server_function] lets one concrete async Rust signature describe both the native server implementation and its Wasm caller. The transport is explicit: the macro also creates a <function>_rpc_router() that you mount in the server. It does not discover routes through runtime reflection.

use rullst::{Router, server_function};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
pub struct SumResponse {
    pub value: u32,
}

#[server_function(path = "/api/rpc/math/add")]
pub async fn add(left: u32, right: u32) -> rullst::rpc::RpcResult<SumResponse> {
    Ok(SumResponse {
        value: left.saturating_add(right),
    })
}

pub fn rpc_routes() -> Router {
    add_rpc_router()
}

fn main() {
    let _router = rpc_routes();
}

When this function is compiled for the native server, its written body runs. When compiled for wasm32, calling add(20, 22).await serializes (20, 22) and returns the decoded RpcResult<SumResponse>. Transport failures are machine-readable RpcFailure values; they never become a fabricated default application value.

Mount the generated route inside server policy

Merge the generated router before applying the standard security baseline and your domain layers:

use rullst::{Router, server_function};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct SumResponse { pub value: u32 }
#[server_function(path = "/api/rpc/math/add")]
pub async fn add(left: u32, right: u32) -> rullst::rpc::RpcResult<SumResponse> {
    Ok(SumResponse { value: left.saturating_add(right) })
}
fn secured_transport() -> Result<axum::Router, rullst::SecurityBaselineError> {
    let app = Router::new()
        .merge_axum(add_rpc_router().into_axum())
        .into_axum();

    rullst::apply_security_baseline(
        app,
        rullst::SecurityConfig::default(),
        rullst::config::Environment::Production,
    )
}

fn main() -> Result<(), rullst::SecurityBaselineError> {
    let _app = secured_transport()?;
    Ok(())
}

The production baseline verifies the double-submit CSRF cookie/header pair. The Wasm caller reads the bounded rullst_csrf cookie and forwards it as X-CSRF-Token. The application must still add session/authentication, trusted tenant resolution, object/role authorization and rate limiting in the order documented by ProductionPreset. Never accept role, owner or tenant authority from a function argument.

Failure codes

Application failures use the same lowercase dotted-code grammar as the shared client contract:

#![allow(unused)]
fn main() {
fn capacity_failure() -> Result<rullst::rpc::RpcFailure, rullst::client_contract::ClientContractError> {
    rullst::rpc::RpcFailure::application("course.capacity_reached", false)
}

let failure = capacity_failure()?;
assert_eq!(failure.code(), "course.capacity_reached");
Ok::<(), rullst::client_contract::ClientContractError>(())
}

Do not place provider bodies, database errors, PII, tokens or debug text in a failure code. Log sensitive diagnostics only through an approved server-side telemetry policy.

Exact v12 limits

  • zero to 16 simple identifier parameters;
  • owned parameter types and one owned output type implementing the needed Serde traits;
  • a concrete async free function with no generics, receiver, unsafe, extern ABI or variadic arguments;
  • rullst::rpc::RpcResult<T> as the return type;
  • an optional same-origin path below /api/rpc/, using at most 128 ASCII bytes;
  • 256 KiB encoded request and response policy;
  • JSON POST transport with a versioned envelope and request-ID correlation.

These bounds do not make a mutation exactly once. Put a stable idempotency key in the domain payload and enforce it transactionally on the server when replay would be harmful. Actual browser-engine compatibility, network availability and deployed identity policy must be tested by the application.

Rullst Specification 📄

“The Single Source of Truth (SST) for Framework Architecture & Conventions”

This document is the Single Source of Truth (SST) for the Rullst Framework. It specifies the exact conventions, API structures, naming rules, directory standards, and subsystem maturity lifecycles across all monorepo crates.

Important

AI & Human Alignment Directive: Whenever updating, refactoring, or generating code/documentation for Rullst, always refer to this specification as the baseline. Every capability in the framework is strictly tagged with its implementation lifecycle status:

  • 🟢 [Implemented / Bounded]: A defined implementation exists with automated tests for the stated scope. This is not a deployment, provider-homologation, or certification claim.
  • 🟠 [Partial]: Useful foundations exist, but a named interoperability, architecture, or conformance boundary is still incomplete.
  • 🟡 [Offline Test Mock / Simulador Dev]: Deterministic offline sandbox fixtures for local development and offline CI without external API dependencies.
  • 🔵 [Roadmap / Em Construção]: Architectural design, public traits, and domain models specified in full, with production drivers in active engineering.

📂 1. Directory Structure Conventions

A standard Rullst application scaffold strictly adheres to this folder hierarchy:

my-app/
├── src/
│   ├── controllers/      # Route controllers (async request handlers)
│   │   └── mod.rs
│   ├── models/           # Active Record & Repository Models (rullst-orm entities)
│   │   └── mod.rs
│   ├── pages/            # Shared HTML views, templates, and layouts
│   │   └── mod.rs
│   ├── middlewares/      # Custom application middleware layers
│   │   └── mod.rs
│   └── main.rs           # Application entrypoint, server bootstrap & central routing
├── Cargo.toml            # Project cargo dependencies
└── Rullst.toml           # Framework configuration (database, environment, secrets)

🛠️ 2. Naming Conventions

To guarantee consistency, both humans and AI coders must adhere to the following naming normalization rules:

  • File Names: Standard Rust snake_case (e.g. users_controller.rs, post_model.rs, billing_service.rs).
  • Struct / Model / Trait Names: Standard PascalCase (e.g. UsersController, PostModel, PaymentProvider).
  • URL Paths: Lowercase kebab-case (e.g. /users, /user-profiles, /billing/webhooks).
  • Database Identifiers: Snake case (e.g. user_id, created_at, billing_accounts).

⚡ 3. Framework Crates & Capability Matrix

CrateResponsibilitiesStatus & Capabilities
rullst-coreKernel HTTP runtime, routes!, Server bootstrap, HTML engine, async task queues, WebSockets, circular telemetry buffers, storage facade, and the default baseline CSRF/WAF/header/PII stack.🟢 [Implemented / Bounded]: Routing, server lifecycle, html! engine, graceful shutdown, backpressure guard, queues, and local storage with path-traversal protection. ApplicationLifecycle adds an opt-in process-local monotonic startup/ready/draining/stopped state, at most 32 immutable component readiness bits, secret-minimized /ready, fail-closed admission and a bounded drain wait. Server marks ready after binding, begins drain before Axum’s graceful wait, and accepts an explicit supervisor shutdown future; deterministic tests cover startup failure, in-flight completion, rejection after drain and lock poisoning. It does not run dependency checks, coordinate replicas/load balancers or authorize domain requests. SQLite and Redis persist dispatch_at timestamps for at most 366 days and never claim them early; Redis promotion uses server time and a digest-pinned live CI/release contract. Execution starts on the first later worker poll and is at-least-once. Custom drivers fail closed for future scheduling until implemented. TenantStorage, TenantCache, TenantRealtime and TenantPresence bind those facades to a validated TenantContext, apply immutable tenant namespaces and prove same-name local non-interference; the realtime wrappers also bound channel/event/identity names and payload size. Memory and Redis caches expose an opt-in, at-most-200-entry metadata snapshot containing logical key, UTF-8 value length and remaining TTL but never the value; custom drivers fail explicitly unless they implement that method. Remote bucket policy, distributed transport/liveness, cache operator authorization and application room authorization remain deployment/application work.
🟢 [Implemented / Bounded]: The in-memory upload admission contract enforces a hard size/allowlist boundary, canonical tenant/name, recognized signature versus MIME/extension, active-text denial, randomized tenant quarantine keys, SHA-256 binding and fail-closed scanner release. It is not multipart streaming, a deep parser, remote persistence or a production malware engine.
🟢 [Implemented / Bounded]: Validated environment precedence is RULLST_ENV, legacy APP_ENV, then [app].env; invalid values fail instead of silently enabling development.
🟢 [Implemented / Bounded]: apply_security_baseline and Server compose configured CSP nonce headers, exact-origin CORS with explicit credential opt-in, bounded WAF, double-submit CSRF and optional PII masking in one tested order, with the per-application config installed outside every middleware. Browser/proxy/TLS deployment evidence and application-owned session/auth/tenant/authorization remain separate. A fail-closed typed Academy boundary-assessment contract records those application observations without certifying them, and the extended rullst-security stack is still composed explicitly.
🟢 [Implemented / Bounded]: client_contract exposes the portable rullst.client v1 typed JSON envelope, positive version negotiation, bounded correlation/idempotency/failure tokens, server-authored time and a fail-closed 2 MiB codec on native and Wasm. It deliberately contains no role, tenant or authorization assertion; durable replay and domain policy remain server/application work.
🟢 [Implemented / Feature-gated Foundation]: native offline-sync adds bounded account state, FIFO idempotent proposals, server revisions/cursors, explicit conflicts/full resync/recovery/logical erasure, account-bound AES-256-GCM snapshots and a static-dispatch foreground coordinator with request budgets, timeout and cursor-stall checks. Platform persistence/secure-key adapters, browser offline storage, concrete authenticated HTTP/retry/background orchestration, future-schema migrations and device evidence remain application/platform work.
🔵 [Roadmap]: Native S3/R2 direct cloud drivers.
rullst-ormActive Record & Repository patterns, parameterized SQLx connection pool (PostgreSQL, MySQL/MariaDB, SQLite), typed Turso/libSQL primary profile, schema migrations, AES-256-GCM privacy, Scout search, typed pgvector/Qdrant queries, Redis native structures, and optional capability-oriented persistence adapters.🟢 [Implemented / Bounded]: Relational CRUD, eager loading, type-safe queries, migration runner, versioned field encryption, and connection-pool resilience for supported SQLx drivers/features. PostgreSQL, MySQL, MariaDB and SQLite have distinct executable matrix contracts, while MariaDB intentionally shares SQLx’s MySQL protocol/backend.
🟢 [Implemented / Bounded]: #[derive(Orm)] #[orm(backend = "turso")] supplies typed CRUD, equality filters, ordering, pagination/counts and generated/app-assigned keys through a process-wide TursoOrm. Its migrations are ordered, checksummed, drift-detecting and reversible. The blank/API CLI profile generates, compiles, migrates, reports status and rolls back locally, while the same typed contract passes against the official remote libSQL server. Unsupported SQLx-specific model behaviors fail during macro expansion rather than being ignored. Other SQLx-specific blueprints, ORM relations/hooks, schema auto-diff, seed generation and transparent embedded-replica synchronization are not part of this bounded Turso profile.
🟢 [Implemented / Bounded]: The optional persistence boundary supplies portable document CRUD for MongoDB and SurrealDB, parameterized OLAP queries through in-process DuckDB, explicit parameterized Turso/libSQL SQL/transactions, and bounded read-only ISO GQL through SurrealDB. These capability APIs do not claim shared semantics or cross-store transactions. External adapters select deterministic offline behavior for empty or mock_* credentials where documented; SurrealDB uses its HTTP protocol rather than embedding the BSL-licensed SDK.
🟢 [Implemented / Feature-gated]: scout-http provides bounded Meilisearch, Elasticsearch and Algolia indexing/search adapters plus deterministic mocks. Meilisearch has a digest-pinned live lifecycle; Elasticsearch/Algolia have protocol fixtures, not hosted-provider certification. Generated projections are process-local post-commit effects unless the application explicitly composes the transactional outbox.
🟢 [Implemented / Feature-gated]: pgvector with strict-postgres supplies typed SQL vector helpers. qdrant supplies a separate bounded dense-vector collection/upsert/delete/cosine-query contract, while redis supplies namespaced Hash, Set and Sorted Set operations. All three have digest-pinned live lifecycles; RAG orchestration, authorization, production ANN tuning and Redis cluster/failover remain application/deployment boundaries.
🟢 [Implemented / Benchmark Evidence]: A lockfile-pinned Criterion target compares five equivalent typed-SQLite shapes through one Rullst, Diesel and SeaORM connection under the same schema, seed and SQLite policy. It is per-run evidence, not a superiority, negligible-overhead, networked-database or full-application claim.
rullst-authArgon2id password hashing, encrypted cookie sessions (AES-256-GCM), opt-in application JWTs, Passkey ceremony foundations, RBAC context guards.🟢 [Implemented / Bounded]: Non-blocking spawn_blocking Argon2id hashing, versioned expiring AES-256-GCM sessions, fail-closed RequireRoleLayer, compile-validated #[rullst::require_role], named Policy<User, Resource> decisions, and a feature-gated application JWT policy with required versioned claims, bounded TTL/scopes, strong HS256 keys, kid rotation and revocation contracts that reject process-local state in production mode.
🟢 [Implemented / Feature-gated]: sqlite supplies bounded shared local auth state. SqliteJwtRevocationStore persists JTI expiry and monotonic subject session versions through serialized transactions, stored quota/configuration and async verification. SqlitePasskeyStore persists validated public credentials, bounded device inventory/rename/revocation and optimistic signature-counter CAS; executable restart, replay, quota, corruption/configuration and two-instance contention evidence covers both stores. Authentication, role persistence, resource/tenant/device ownership, trusted file permissions/encryption, backup and multi-host replication remain application/deployment boundaries.
🟠 [Partial]: Passkey registration/assertion validates the documented ES256/none-attestation scope, but challenge state remains process-local. Sticky ceremony routing or an application shared challenge layer is required across instances. Normative WebAuthn conformance or adoption of an audited full server library, refresh tokens and complete recovery/session UX remain required before a general stable claim.
rullst-securityExplicit extended defense-in-depth layers: bounded RASP, authenticated Vault, Login Jail, Secure Headers, rate limiting, DLP and security telemetry.🟢 [Implemented / Bounded]: AES-256-GCM envelopes with rotation/AAD, bounded URI/header/body RASP heuristics, local abuse controls, CSWSH origin guard, OS-random TOTP with SVG enrollment QR, strict JSON transport inspection plus an explicitly mounted reusable JSON Schema 2020-12/OpenAPI 3.1-component policy, explicit log redaction, file-backed SRI hashes, and a versioned/bounded LiveSecurityEvent v1 dashboard envelope. DurableSiemSpool preserves the compatible unsigned local format, while AuthenticatedSiemSpool offers an explicit HMAC-SHA256-chained format with named active/historical keys, zeroized key material, sequence/predecessor validation and byte/record quotas. Restart, forgery, wrong/missing keys, reordering, interior deletion, quota, symlink and external-length-change paths fail closed. Whole valid-tail rollback requires a separately trusted checkpoint, and the caller owns directory/key trust, permissions, retention and exclusive-writer operation. Schema construction caps bytes/nodes/depth, accepts only local references, disables network/filesystem resolution and uses linear-time regexes; auth/ownership/domain rules and query/header/form validation remain application contracts. A deterministic Sentinel classifies three caller-supplied aggregate patterns and can issue HMAC-authenticated, subject-bound, expiring, one-shot process-local proof-of-work challenges; it is not AI attribution, automatic blocking or distributed replay protection. The CLI emits bounded fail-closed evidence and a CycloneDX 1.5 Cargo SBOM; it does not certify the application.
🟢 [Implemented / Feature-gated]: redis-rate-limit provides namespaced atomic Redis fixed-window counters, hashes client keys and exposes an explicit process-local offline mode that production can reject with require_distributed().
🟠 [Partial]: Recovery-code consumption must be persisted transactionally by the application. Real Redis cross-instance/eviction/failover evidence is still required. CSP nonce composition is shared, but Core and Security are not yet one canonical Server stack; WebSocket CSRF tickets/frame crypto, trusted rollback checkpoints, spool compaction/remote acknowledgement and external SIEM delivery are not implemented.
rullst-aiMulti-provider LLM client (Gemini, OpenAI, Claude, DeepSeek, Ollama, explicit OpenAI-compatible endpoints), prompt injection defenses, PII masking, bounded tenant-aware RAG, guarded local tools, and conversational memory.🟢 [Implemented / Bounded]: Guarded AiClient, heuristic prompt filter, PII masking, machine-readable provider capabilities, configurable bounded live-request deadlines, a versioned deterministic injection/jailbreak/PII regression corpus, and a capability-declared OpenAI-compatible adapter. The adapter separates literal-loopback-IP HTTP(S) from HTTPS cloud configuration, supports optional Bearer authentication, disables ambient proxies/redirects, and bounds image/response bodies; unrelated protocols use AiProvider. A separate static-dispatch StreamingAiClient<P> enforces chunk/output ceilings and explicit cancellation; exact OpenAI-compatible configurations may declare strict incremental SSE with a required terminal marker and cancellation raced against request/body reads. AdaptiveAiEvaluator<P> runs caller-defined multi-turn strategies with independent turn/prompt/response/deadline limits, cancellation, typed pass/fail/inconclusive decisions and a versioned report that retains no raw prompt, response or provider error. Repository fixtures prove orchestration, not live-model behavior.
🟢 [Implemented / Bounded]: Strict URL/resolved-IP/redirect/resource policy plus an opt-in deny-by-default HTTPS fetcher with exact-host allowlist, DNS pinning, proxy bypass, peer verification and streaming limits. Explicit vision helpers accept application-admitted bytes, canonical exact-root local files or URLs only through that fetcher; capability and prompt checks precede I/O, input is capped at 10 MiB, supported image signatures are sniffed and a supplied remote media type must match. Local tool dispatch separately requires allowlist, principal authorization, closed bounded JSON, call budget, audit sink, and payload-bound approval for destructive/financial calls. RagPipeline::answer composes guarded embedding, a static-dispatch application retriever, Unicode-safe context budgets, guarded generation, source metadata, and required secret-minimized terminal audit under a trusted TenantContext; a bounded tenant-partitioned process-local cosine retriever supplies the offline contract. DurableRagAuditTrail and DurableToolAuditTrail synchronously append minimized events to distinct versioned local files under byte/record quotas and fail closed on restart corruption, symlink targets, competing-writer growth or durability uncertainty. Their SHA-256 frames detect corruption but do not authenticate events. The separate opt-in AuditDeliveryClient exports a caller-minimized, at-most-16-KiB JSON envelope with an exact HMAC-SHA256 signature, key/timestamp metadata, stable event identity across bounded transient retries, explicit cancellation and a closed event-bound acknowledgement. Cloud delivery requires HTTPS and literal-loopback HTTP(S) is development-only; the receiver still owns freshness verification, deduplication, authorization, persistence, retention and key operations.
🟢 [Implemented / Feature-gated]: StatefulChat<M> loads bounded tenant/conversation history, performs guarded generation and atomically appends one user/assistant exchange through a static ChatMemory. The bounded in-memory store is always available; sql-memory supplies fixed-schema SQLite/PostgreSQL/MySQL/MariaDB storage with an even monotonic revision and transactional compare-and-swap, so stale cross-process writers fail instead of silently reordering. Raw message encryption/retention, authenticated ownership within a tenant, provider audit, backups and conflict UX remain application contracts; the CLI scaffold remains the Turso/custom-model path.
🟠 [Partial]: The egress fetcher is not automatically mounted around provider transports, RAG or arbitrary application clients; hosted-provider SSE conformance, non-compatible streaming protocols, image decoder safety, host path trust/authorization, provider-native tool calling, cancellation for ordinary non-streaming calls, automatic provider retries, durable audit outbox/receiver operations, approver authentication, first-party external vector-store retrievers, authoritative datastore/domain authorization, ingestion/deletion, maintained application-specific eval corpora and output policy remain application or roadmap work. Exact live-model evaluation execution/results and provider behavior remain external evidence.
🟡 [Offline Mock]: Deterministic offline chat/vision/embedding fallbacks.
rullst-capitalMulti-gateway billing, SaaS MRR/ARR metrics, constant-time webhook signatures, contractor payouts, and a bounded National NFS-e preparation pipeline.🟢 [Implemented / Bounded]: Provider-specific payment/payout adapters, pooled HTTP clients, explicit mock credentials, and signature/freshness/replay foundations for the methods documented by each adapter.
🟢 [Implemented / Feature-gated]: webhook-sql persists bounded provider-scoped payload digests or caller-supplied stable event identifiers across processes on SQLite, PostgreSQL, MySQL, and MariaDB. Immutable capacity/TTL, serialized claims, expiry, restart, contention, configuration drift, and fail-closed full/storage states have executable evidence. A caller-owned relational transaction can bind one semantic event claim to its domain mutation. Middleware admission and cross-system exactly-once are not implied; external effects still require an outbox, idempotent consumers, and reconciliation.
🟢 [Implemented / Bounded]: Static-dispatch metered billing uses the current Stripe Meter Events and Lemon Squeezy Usage Records request shapes, binds accepted responses to the original event, caps response bodies and exposes deterministic non-live mocks. Stripe forwards a bounded provider identifier; Lemon Squeezy explicitly requires application-outbox deduplication.
🟠 [Partial]: Uniform live method coverage, provider-account interoperability, cross-system exactly-once, and reconciliation are incomplete; Alipay RSA2 fails closed.
🟢 [Implemented / Feature-gated]: nfse pins the current official 1.01 production/restricted artifact profiles by SHA-256, builds a strict ordinary-service DPS subset without floating-point money, and validates extracted official XSD sources from a closed in-memory catalogue. After hash verification, the production profile receives exactly one declared compatibility normalization: .NET-style ^...$ anchors are removed from the known DPS-series pattern so the XSD-regex engine applies the authority’s apparent intent instead of treating the anchors as literals. The same feature signs infDPS/@Id with PKCS#12 RSA-SHA256/inclusive-C14N 1.0, verifies its local XMLDSig test fixture, and constructs a bounded rustls mTLS identity/client. Its offline protocol codec requires the signed tpAmb, emits the exact dpsXmlGZipB64 JSON object deterministically and parses bounded synchronous 201 authorization or 400/403/500 rejection responses, binding environment, submitted DPS, access key and a cryptographically valid embedded NFS-e XMLDSig. A single-active-writer HMAC-chained local command journal records idempotent prepared/terminal digests, recovers unresolved descriptors after restart and supports independently retained exact-tip checkpoints without storing XML, access keys or response messages. Certificate secrets are redacted and zeroized where owned by Rullst.
🟡 [Offline Mock]: Deterministic NfseEnvironment::Mock fixture, unmistakably not a tax authorization.
🔵 [Roadmap / External Evidence]: Live transmission, full emitter-certificate/ICP-Brasil trust policy, authoritative request/outbox and multi-writer operations, restricted-environment certificate tests, independent review and SEFIN homologation. Homologation/production transmission remains fail-closed.
rullst-connectSocial login / OAuth2 / OIDC providers (Google, Apple, GitHub, Discord, Auth0, Cognito) with PKCE and rotating JWKS.🟢 [Implemented / Bounded]: OAuth2/OIDC clients with constant-time PKCE comparison, validated discovery, bounded JWKS refresh/cache policy, deterministic mock credentials and a credential-free UniversalProfile projection. ConnectUser serialization omits access/refresh tokens. Category-aware remote revocation rejects malformed or oversized tokens before transport: Google, Discord and Apple accept the documented access/refresh categories, GitHub accepts access tokens, and Auth0/Cognito accept refresh tokens; protocol fixtures bind method, endpoint, client authentication and form/JSON shape, while request/response Debug omits credentials, bodies and URL query data. Other providers fail explicitly as unsupported, and remote success does not clear application sessions or persistence. AutoRefreshingSession<P> validates and user-binds token generations, detects expiry with a bounded early-refresh window, serializes provider refresh through static dispatch, retains/rotates refresh credentials and swaps state only after a complete valid response; callers waiting behind a successful refresh reuse that state. Its state/leases redact secrets. EncryptedTokenSnapshot supplies a bounded, versioned AES-256-GCM envelope that authenticates key ID, provider and trusted local-account binding, preserves the validated generation and rejects copied-owner/tampered records. The optional sqlite store persists only a pseudonymous binding digest, generation/key metadata and that ciphertext under an immutable row ceiling; BEGIN IMMEDIATE, exact-successor compare-and-swap and conditional deletion reject stale shared-local writers, with restart, contention, quota, configuration, corruption, key and symlink evidence. The application still owns secret-manager key custody/rotation, account authorization, a lease around the remote provider call, losing-call reconciliation, retry/backoff, trusted directory/backup, reauthentication and multi-host replication. The optional Axum/tower-sessions lifecycle generates a ten-minute state + PKCE challenge, adds nonce for OIDC, keeps verifier/nonce server-side, removes and immediately saves the sole active challenge before validation and rejects sequential replay/expiry/mismatch. The host still owns durable session storage and cookie/TLS/account policy; the generic session-store API is not distributed compare-and-delete, so simultaneous already-loaded callbacks require idempotent effects or a stronger application store. ReqwestClient also exposes explicit HTTP(S) corporate-proxy constructors: endpoint shape is bounded, URL credentials are rejected, authenticated remote proxies require HTTPS, system-proxy lookup is disabled and a local protocol fixture proves routing/auth headers.
🟢 [Implemented / Local Test Fixture]: The explicitly mounted Axum Mock IdP accepts only configured HTTP-loopback issuer/callback origins, binds one exact client, bounds process-local grants/tokens, consumes expiring authorization codes once, verifies S256 PKCE, signs nonce-bound EdDSA ID tokens and publishes discovery/JWKS. The deterministic key and credentials are public test fixtures; interactive login/consent, refresh/device/federation flows, durability, rotation, public exposure and OIDC conformance are not claimed.
🔵 [Roadmap]: PAC/WPAD, SOCKS, proxy mTLS identity and enterprise deployment certification are not implied. Message brokers live in rullst-messaging, not this OAuth-focused crate.
rullst-messagingBroker-neutral event envelopes, idempotent publication, consumer groups, acknowledgement leases, retry, dead letters, durable local SQLite state, and future remote adapters.🟢 [Implemented / Bounded Foundation]: rullst.messaging.v1 envelopes, bounded identifiers/headers/payloads/batches/retention, topic-scoped exact-replay idempotency, fan-out between groups, competing consumers, expiring single-use ACK leases, bounded retry/attempt ceilings, dead-letter views, explicit terminal purge, injectable time and a reusable static-dispatch contract suite. Debug output redacts keys/tokens/header values/payloads. A canonical bounded v1 envelope codec rejects unknown versions, non-canonical/truncated/oversized frames and namespace mismatch; a deterministic digest fixture freezes its bytes. Validated W3C version-00 traceparent and a conservative tracestate subset propagate through only those two allowlisted headers; arbitrary baggage, sampling and export remain host work. InMemoryBroker remains deterministic/process-local. The opt-in SqliteBroker uses a fixed schema and serialized BEGIN IMMEDIATE mutations for publications, subscriptions, claims, ACK/retry/DLQ and purge; exact limits are persisted per namespace. Its explicit AES-256-GCM profile encrypts header values plus payload with randomized nonces and AAD binding to immutable row metadata. A bounded primary/prior-key ring rejects missing keys until old records are purged, and plaintext/encrypted profiles cannot mix. The opt-in static OrmOutboxRelay binds one relational outbox stream to one topic, validates claimed JSON, publishes the durable event key as broker idempotency and only then ACKs the exact ORM lease; a publish-before-ACK crash/reclaim test produces an exact replay and one broker message. Shared-contract, raw-storage/restart, wrong-key/tamper/row-swap, symlink, rotation, expired-lease, two-instance contention, configuration-drift and malformed-row repair regressions are executable.
🟠 [Partial]: Delivery is at least once. The default profile is plaintext. Even in the encrypted profile, topic/event/content metadata, IDs, timestamps, idempotency keys, fingerprints, rotation key IDs and delivery state remain visible; key custody, permissions, backup/rollback detection, retention, disk operations, topic/tenant authorization and destination-side idempotency belong to the host. Profile migration requires a new namespace/database and application-owned republishing. The outbox database and broker publication are not one atomic transaction; worker supervision, cleanup and destination idempotency remain application work. The local adapter does not provide replication or automatic failover. The envelope codec is not a remote transport and does not preserve caller publication keys or broker acknowledgements by itself. Kafka, RabbitMQ, Redis Streams, NATS/JetStream, SQS/SNS, Google Pub/Sub and Pulsar adapters plus their live restart/fault matrices remain roadmap work.
rullst-iotno_std sensor telemetry/protocol helpers and an Ed25519-signed firmware-manifest verification gate.🟢 [Implemented / Bounded]: Ed25519 manifest verification with target/hash/length/counter checks, an explicit durable monotonic-CAS store boundary, no_std telemetry/frame models, bounded MQTT 5 PUBLISH and RFC 7252 CoAP base-request encoders, a credential-free local HTML snapshot renderer, and a safe telemetry-module CLI scaffold. Protocol vectors and restart/retry/conflict tests prove these local contracts, not a broker, network or physical device.
🟠 [Partial]: GPIO state, I2C/Modbus frames, BLE GATT records, RSSI topology, power recommendation and Digital Twin JSON are data/helpers only, not hardware, network or realtime drivers.
🟡 [Simulador Dev]: Deterministic MQTT-value/HSM/PQC fixtures require feature = "experimental-simulators" and never represent broker or cryptographic capabilities.
🔵 [Roadmap]: Concrete hardware-backed counter/boot integration, firmware download/flashing, MQTT/CoAP transports and state machines, hardware drivers/HSM and audited ML-KEM.
rullst-mailTransactional email engine with Resend, SendGrid, Postmark, optional SMTP, optional native AWS SES v2, and offline fixtures.🟢 [Implemented / Bounded]: Mandatory pre-flight pipeline, anti-CRLF validation, bounded disposable-domain/security/DLP heuristics, provider-specific transports, seven safe scaffold variants (including provenance-aware fiscal receipts and explicit D+1/D+3/D+7 dunning), and expiring purpose-bound HMAC tracking tokens. TenantMailResolver selects an in-process driver directly from an explicit authenticated Core TenantContext; invalid IDs and unavailable registry state fail closed, and tests prove two contexts do not cross-deliver. MailError classifies permanent/transient/rate-limit outcomes; the in-process FailoverDriver sends another provider only transport/HTTP 5xx/429/transient-SMTP failures, captures bounded delta Retry-After, fails closed on circuit-state errors and emits structured tracing without provider response bodies. Mail::enqueue preserves tenant and bounded due-time metadata through SQLite/Redis without early claims; the worker consumes that timestamp only after it is due. Direct Resend/SendGrid retain provider-native scheduling, while real SMTP/Postmark/Log and SES paths reject future direct delivery; offline fixtures may retain it for assertions. The shared attachment contract accepts at most 32 items, 20 MiB each and 25 MiB raw aggregate; validates safe basenames, parameter-free MIME and unique HTML-referenced CIDs; redacts bytes from Debug; and feeds provider-native Resend, SendGrid, Postmark, native SES and nested SMTP MIME serialization. The opt-in static AttachmentInspectionGuard fails before transport on executable magic, spoofed known types, active PDF/SVG, recognized secrets and unsafe text links; external scanners can implement the same contract. The provider-neutral SuppressionGuard checks process-local or opt-in shared-local SQLite state before transport; verified event identities are replay-bound, suppression reasons escalate monotonically and immutable quotas are transactional. ObservedMailDriver emits only a bounded provider label, terminal outcome, latency, attachment count and scheduling/tenant booleans through a non-failing observer. With aws-ses, AwsSesDriver sends SES v2 Simple messages through the official AWS SDK and SigV4, including temporary credentials, caller-owned rotating providers/config, HTML/text, RFC 8058 headers and attachments/CID; it rejects provider field limits and an encoded estimate over 40 MiB before network, caps Retry-After, and a loopback contract asserts the signed regional ses/aws4_request request plus typed/redacted rejection. The legacy constructor remains only an offline-fixture or explicit trusted bearer-proxy boundary, never an unsigned AWS request. Fiscal mock responses remain visibly unauthorized; dunning does not infer billing state or scheduling.
🟠 [Partial]: Exact execution time, exactly-once delivery, live-account SES acceptance and inbox delivery are not implied. The local attachment inspector is not antivirus, sandboxing, recursive archive inspection or CDR; provider/account limits may be tighter. Provider webhook authentication/adapters, multi-host suppression replication, file encryption, distributed breaker/telemetry operations, durable encrypted tenant credentials, rotation and cross-process distribution remain application/deployment concerns; tracking payloads are authenticated but not confidential. SES identity/domain verification, sandbox exit, IAM least privilege, quotas, reputation and provider operations remain AWS/account/deployment work.
🟡 [Offline Mock]: Memory/Log plus empty or mock_* provider credentials.
rullst-studioLocal Developer Control Room (http://127.0.0.1:5555), clean route navigation, live system telemetry visualizers.🟢 [Implemented / Bounded]: Local control center, RadarSnapshot telemetry, database/migration surfaces when configured, and explicit Unavailable states for unconnected probes. The data browser reads/filters SQLx tables and, only after the verified debug-loopback/same-origin middleware installs an unforgeable request marker, can update primitive non-key values or delete exactly one complete-primary-key-selected row. Values are bound, request/schema/value cardinality is bounded, backend-specific types remain read-only and SQLite/PostgreSQL/MySQL/MariaDB have executable mutation contracts. This is not application tenant/RBAC, audit, rollback or shared-production administration. The supplied queue snapshot exposes only backend records; SQLite can explicitly retain 1–100,000 successful jobs with atomic pruning and purge while deleting them by default. Retained payload access/policy belongs to the host. An explicitly supplied memory/Redis Cache exposes at most 100 metadata rows in the UI; logical keys become process-bound HMAC tokens, values never leave the driver, and only individual local invalidation is available. A separately mounted push-only trace router accepts 1–128 attribute-free v1 spans under 128 KiB after HMAC-SHA256, source/ID/clock/nonce validation and atomic replay rejection; the bounded in-process viewer derives slow-query and repeated-label heuristics without SQL or bindings. It is not OTLP, durable trace storage, a key manager or remote Studio authentication. Successful feature-flag toggles invalidate all warm DbFeatureDriver caches in the same process through a constant-size epoch. Cross-process/direct-writer invalidation remains TTL-bound unless the application distributes the signal.
rullst-nexusAuto-generated Admin CMS (/nexus), dynamic model CRUD, AI Admin Assistant (/nexus/chat), SOC Threat Radar.🟢 [Implemented / Bounded]: #[derive(Nexus)] emits registered named-field metadata with inferred primitive or explicit semantic widgets; the panel provides parameterized CRUD/search/sort/pagination plus bounded selected-record delete/deactivate. Construction is fail-closed, requires an authentication policy and admin role layer, validates bounded unambiguous model/field/enum/relation metadata, enforces server-side field policy, caps form pairs and field bytes, rejects unknown/protected/duplicate or semantically invalid form values, minimizes database errors returned to clients, and escapes record/model metadata on audited paths. Boolean widgets are inferred; enum options and multiline intent are explicit because an unrelated Rust field type does not expose those semantics to the struct derive. Deactivation requires a writable Boolean is_active/active.
🟢 [Implemented / Opt-in Bounded]: a registered text tenant column scopes every built-in read, create, update, delete and batch operation to a trusted Core TenantContext; create injects the context value and missing context fails closed. with_required_audit transactionally couples successful mutations to a minimized fixed-schema row containing the built-in authenticated actor, optional tenant, table/action, optional known key, count, committed outcome, correlation ID, timestamp and format version; missing audit storage rolls back the mutation. The audit table is in the same relational database, mutable by its administrators, records no denied attempts, and may omit an automatically generated create key. Host identity/membership/domain policy, global-model and custom-route authorization, database privileges, schema/type compatibility, retention/backup/replication and immutable external audit delivery remain application/deployment contracts.
rullst-macrosProcedural macros (html!, rullst::model, rullst::runtime::main) and compatibility helpers.🟢 [Implemented / Bounded]: Compile-time html! escaping with explicit RawHtml, model/runtime macros, and trybuild diagnostics. A concrete async #[server_function] returning RpcResult<T> generates a matching explicit native router and Wasm caller over the bounded rullst.client v1 JSON envelope: owned Serde parameters/results, same-origin /api/rpc/... path, 256 KiB request/response policy, request correlation, media-type/version/schema checks, CSRF-cookie forwarding and message-free failure codes. The host must mount the route inside production security, authenticated identity, tenant, authorization and rate-limit layers; application idempotency and browser/network interoperability beyond CI are not inferred. #[island] hydration remains experimental.
cargo-rullstDeveloper CLI toolkit, scaffolding generators (make:*), project blueprints, AST IDOR static route scanner.🟢 [Implemented / Bounded]: Interactive wizard, generators, heuristic IDOR scanner, CycloneDX exporter, toolchain doctor and a fail-closed Academy evidence diagnostic that explicitly does not certify a deployment. Version 12 deterministic generation can explicitly select the blueprint, primary database or database-free blank profile, AI, Redis and additive persistence capabilities. It deliberately fixes generated database-backed application code to Active Record and full-stack rendering to server-side html! plus HTMX; Repository/Data Mapper and the LiveView, Wasm Island, Pico.css and Tera foundations remain application APIs rather than equivalent v12 generator profiles. The optional storage multi-select remains public and accepts zero or more Turso/libSQL, MongoDB, DuckDB, SurrealDB and Qdrant add-ons with their distinct capability boundaries. SQLx manifests disable umbrella defaults and select one strict primary backend. A structural gate retains 18 internal layouts: nine directly linked public shapes and nine legacy DLL regression shapes. A minimal eight-case matrix still checks legacy templates and release boundaries without advertising DLL runtime support. Seven additional public-CLI profiles exercise all six blueprints plus distinct database/AI/Redis/polyglot axes; the CLI-level polyglot case compiles while dedicated ORM matrices own adapter runtime evidence. dash uses bounded logs/input, probes application and Studio availability, observes its child process, reports configured rather than presumed-connected persistence, runs migrations asynchronously and restores terminal/process state on exit; neon motion is optional and has reduced-motion/color-free modes. The public development commands now use supervised process restart: coalesced source/asset/configuration changes trigger a real build, compile failures retain the current application, and successful candidates run from owned executable snapshots. A debug/development-only same-origin generation probe drives browser refresh and verifies startup identity; process state resets and shutdown is bounded. The CLI rejects legacy DLL profile generation after the Windows LMS/ORM state-split finding. The retained experimental loader is not a public v12 workflow or stable Rust ABI; see the release audit and supervised-reload tutorial. make:chat-session emits registered SQLx or Turso-primary models, reversible migrations and application-owned bounded chat memory; materialized contracts run persistent mock conversations on both backends and prove collision refusal. make:billing --model likewise emits SQLx/Turso-primary persistence plus Stripe/LemonSqueezy pricing, authenticated checkout/portal and mandatory signed-webhook code; its materialized contract compiles, migrates, persists, denies cross-owner subscription mutation before customer binding and refuses existing outputs on both backends.
🟢 [Implemented / Bounded]: The LMS starter supplies bounded curriculum, school-scoped learning/assessment/publication/progress/completion, roles, leaderboard, automation/outbox/workers, localized in-app notifications and a minimized privacy-request foundation. Its SSR catalog performs limited, ORM-parameterized title/category filtering; generated auth/catalog/course/player shells consume the Core CSP nonce without remote page dependencies or inline style attributes and include keyboard landmarks, visible focus and reduced-motion handling. Lesson presentation distinguishes video/audio, rejects non-HTTPS non-local sources, requires a WebVTT track for video and a bounded transcript/language for both; materialized tests cover escaping and fail-closed negatives, not real-browser playback or caption quality. Privacy claims use exact leases, retry/dead-letter with a hard ten-attempt ceiling, actor/digest-bound completion and a supervised static-dispatch executor with an explicit protocol-only mock; the product must still supply the adapter that performs application-specific export/deletion/anonymization. Materialized SQLite exercises catalog/player escaping/nonce, privacy hard limits and the documented vertical/cross-school boundaries. Detached --lms-modules auth, auth,learning and auth,learning,assessment profiles remain small compiling foundations; the assessment profile grades versioned quizzes authoritatively without pulling score/leaderboard/outbox verticals. The complete starter is the default.
🟠 [Partial]: Other detached combinations, profile hot reload, complete generated frontend alternatives, full Turso-primary parity beyond Blank/API, media upload/hosting/transcoding, advanced/localized search, caption/transcript quality and localization, WCAG/browser evidence, distributed failover, PostgreSQL/MySQL isolation, visual authoring, exported telemetry and the separately operated Academy remain roadmap or release-engineering work.

v12 audit correction invariants

The current release audit reopens earlier readiness claims. A historical score, checked roadmap item or green mainline run is not evidence that the current revision satisfies these contracts.

  • Generated safe ORM projections validate column identifiers. Empty membership predicates match nothing. Mandatory tenant, global and soft-delete scopes remain grouped outside application OR expressions; nested queries preserve validation errors. Bulk mutation must not bypass model authorization.
  • Queries inside a managed transaction use its executor. Callbacks that already borrow a mutation executor must fail explicitly on unsupported reentrant ORM access rather than deadlock or silently use an unrelated connection.
  • Local durable stores use SQLx-owned transactions so dropping a cancelled operation schedules rollback. A cancellation racing an already dispatched commit can still have an uncertain outcome and requires reconciliation.
  • Authentication validates expiry independently of clock-skew allowances; provider-specific identity claims, nonce and refresh semantics cannot be replaced by successful offline fixtures.
  • Payment adapters must not fabricate live checkout prices, portal URLs or mutation success. Unsupported provider operations fail explicitly. Signed webhook bytes still require provider-specific schema and lifecycle validation.
  • Development restart owns builds, application snapshots and migrations. It is not a stable Rust DLL ABI or production rolling-deployment mechanism. Windows descendant cleanup remains best effort without a Job Object implementation.

These describe required behavior, not a declaration that every final release gate has passed. The audit records the current evidence and remaining work.

Pre-release scaffold source invariant

An unpublished pre-release cargo-rullst may reuse only local framework crates whose package names and versions exactly match that CLI. The invocation directory is preferred; otherwise the exact still-present checkout from which the CLI was compiled is used. Stable or version-mismatched packages fall back to crates.io. This permits evaluation outside the repository without silently mixing release trains, but generated absolute path dependencies remain non-portable until the matching immutable release is published.

Shared-local facade composition invariant

The umbrella features auth-sqlite, capital-quota-sql, oauth-sqlite, mail-sqlite, messaging-sqlite, and queue-sqlite may deliberately share one file-backed SQLite database for a bounded single-host deployment. Each subsystem owns a fixed, distinct table namespace. The host prepares and checks every store sequentially and keeps ApplicationLifecycle unready until all required components have succeeded. Restart must reuse the same validated database URL, quotas, namespaces, and encryption keys; all handles must close before a host copies, restores, or replaces the file.

The executable facade contract proves persistence and idempotent replay across restart, encrypted token/message plaintext absence from the database/WAL/SHM, queue recovery, aggregate readiness, and fail-closed token corruption without breaking an unrelated mail read. It does not create a cross-subsystem transaction, consistent online backup, key manager, distributed database, or multi-host coordination. Whole-file snapshot consistency, filesystem trust, permissions, key/backup operations, contention policy, and recovery drills are host responsibilities. Separate databases remain preferable where failure isolation or write throughput matters.

Native relational enum invariant

rullst-orm has one bounded native-enum contract. #[derive(Enum)] generates a closed label set shared by Display, parsing, Serde, RullstValue and SQLx codecs. A database enum has 1–64 unique labels; its type identifier and labels use the documented bounded ASCII allowlists. Blueprint::native_enum emits:

  • a named PostgreSQL enum with exact existing-label drift detection only under strict-postgres;
  • an inline ENUM for MySQL/MariaDB; and
  • a TEXT CHECK constraint for SQLite.

PostgreSQL through SQLx Any must fail before DDL because that driver cannot decode custom PostgreSQL types. Adding, removing or reordering variants, deployment order, dependent-object removal and rollback remain explicit, reviewed migration work. The schema helper does not auto-migrate an existing type or infer application compatibility.

The Capital row also includes one implemented, feature-gated quota boundary: BillingSubject binds a shared team/workspace counter to trusted tenant state, Billable::quota_request derives the limit from the subscription owner, and QuotaStore atomically reserves idempotent units before resource creation. The deterministic local store is always available; quota-sql uses fixed-schema SQLx transactions on SQLite/PostgreSQL/MySQL/MariaDB, with exact replay/release and a caller-owned transaction path for atomic domain writes. Live container contracts exercise all four protocols. Membership establishment, plan state, migrations, reconciliation and non-relational adapters remain application boundaries.

3.1. Generated Academy activity boundary

The generated ActivityEvaluator boundary uses static dispatch and accepts an untrusted submission, never client-supplied points. It validates authenticated ownership, activity/ruleset identity, bounded object-shaped state, server-time ordering and a canonical evidence digest, then constructs ActivityResult from the evaluator’s outcome. Built-in bounded evaluators cover single-choice, a complete permutation of at most eight matching pairs and typed recall with a closed answer set, 512-byte/control-character boundary, trim and optional Unicode lowercase comparison. Typed replay persists a policy-bound SHA-256 digest rather than raw input; it does not perform Unicode normalization, accent/fuzzy matching or make the digest non-personal data. The complete Academy starter’s record_activity_result rechecks the authenticated actor, loads course/kind/maximum/ruleset/season and the canonical evidence digest and exact evaluator configuration from persisted activity state, rejects any divergence, then atomically appends an exact-replay activity-attempt record, ScoreEvent v2, the leaderboard update and score_recorded. The generated owner-only POST /activities/{id}/attempts accepts only an idempotency key and selected option. POST /activities/{id}/attempts/matching accepts only an idempotency key and bounded pair IDs, while POST /activities/{id}/attempts/typed accepts the key and bounded learner text. All derive learner/activity identity, policy, answers, points, evidence and time from authenticated/server state. Durable attempt identity is scoped by learner and activity, and the event idempotency key is derived by the server rather than trusted as a global client namespace. The application must keep evaluator answer rules in trusted state and include retained attempt state in its privacy lifecycle. Listening/game evaluators and unification with the separately persisted quiz evaluator remain roadmap work.

Activities may opt into the exact rullst-box-v1 review policy. For a newly applied score, the score transaction locks and validates that versioned policy, loads the learner/activity review state, applies a deterministic bounded pass/lapse transition and upserts the next due time before commit. An exact activity replay exits before this transition and therefore cannot advance the schedule. The owner-only GET /reviews/due derives the learner and current time from server state and returns at most 50 due activities after rechecking active school membership, course scope and enrollment. Invalid policy/state or a changed algorithm version fails the score transaction closed. This is a simple inspectable scheduling foundation, not FSRS/SM-2 compatibility, efficacy proof, AI personalization, generated pedagogy or a complete adaptive-learning system; PostgreSQL/MySQL contention evidence also remains open.


⚡ 4. Core API Specifications (rullst-core)

rullst-core provides the runtime kernel. Database and queue drivers are modular and feature-gated.

4.1. Server & Routing (rullst::routing)

  • Routing Macro: Central declarative routing declared via the routes! macro wrapping Axum routing handlers:
    #![allow(unused)]
    fn main() {
    use rullst::{response::Html, routes};
    
    async fn home() -> Html<&'static str> {
        Html("Home")
    }
    
    async fn posts_index() -> Html<&'static str> {
        Html("Posts")
    }
    
    let router = routes![
        get("/" => home),
        get("/posts" => posts_index),
    ];
    }
  • Server Lifecycle & Graceful Shutdown:
    #![allow(unused)]
    fn main() {
    use rullst::{Server, routes};
    
    async fn serve() -> Result<(), rullst::server::ServerError> {
        let router = routes![get("/" => || async { "OK" })];
        Server::new(router).run(3000).await
    }
    }
    ApplicationLifecycle is the opt-in orchestration contract behind a lifecycle-aware server. Its phase is monotonic, its immutable registry has at most 32 validated required-component labels, and request admission requires both Ready and every component bit. Exact GET/HEAD health probes bypass admission so /ready can return a bounded 503 during startup, dependency failure or drain while /health stays process-only. The JSON reports counts, not labels or dependency errors. Server::run_with_shutdown accepts a caller-owned future; when it resolves, the lifecycle changes to draining before Axum waits for accepted requests and then becomes stopped. Dependency checks/timeouts, component updates, replica consensus, load-balancer timing, authorization and the deployment termination deadline remain host contracts.
  • Default Dynamic Cache Boundary: headers_middleware supplies Cache-Control: no-store only when the handler has not already selected an explicit cache policy. Versioned public/static responses can therefore opt into reviewed caching without weakening the default for dynamic data.
  • Double-Submit Form Contract: csrf_middleware installs the exact request-scoped CsrfToken used by the CSRF cookie on eligible safe requests and preserves it after a valid state-changing request. Server-rendered forms must echo that value in _token; HTMX/JavaScript may instead send it through X-CSRF-Token. The cookie intentionally remains script-readable and must not be confused with an authentication or session cookie.

4.2. Server-Side Rendering (rullst::macros)

  • Macro: html! expands supported HTML trees into ordinary Rust String construction at compile time.
  • XSS Protection: Dynamic display values in the supported {expr} syntax are HTML-escaped by the generated code.
  • Raw Unescaped HTML: Explicitly bypassed using the wrapper rullst::html::RawHtml(String).
  • Example:
    #![allow(unused)]
    fn main() {
    use rullst::html;
    
    let username = "<script>alert('xss')</script>";
    let rendered = html! {
        <div class="user-badge">
            <span>"User: "{username}</span>
        </div>
    };
    // Automatically escapes to: &lt;script&gt;alert('xss')&lt;/script&gt;
    }

4.3. Durable Queue Timing and Completion History

  • SQLite and Redis persist dispatch_at for at most 366 days and never claim a job before its stored millisecond due time. Execution remains poll-dependent and at-least-once.
  • Successful SQLite jobs are deleted by default. The explicit Queue::sqlite_with_completed_history constructor validates a 1–100,000 row limit, changes a processing row to completed, and prunes excess history in the same transaction.
  • Queue::purge_completed_history removes those opt-in retained successes. Rows contain the original payload, so Studio access, data minimization and retention policy remain host responsibilities. Redis/custom drivers expose inspection or history only when their capability implements it.

🗄️ 5. Active Record ORM & Schema Engine (rullst-orm)

5.1. Model Definition & CRUD

#![allow(unused)]
fn main() {
use rullst_orm::{FromRow, Orm};

#[derive(Debug, Clone, FromRow, Orm)]
#[orm(table = "users")]
pub struct User {
    pub id: i32,
    pub name: String,
    pub email: String,
    #[orm(encrypted)]
    pub secret_token: Option<String>,
}

async fn use_users() -> Result<(), rullst_orm::Error> {
// Queries (after `Orm::init(...)` and schema migration at startup).
let all_users: Vec<User> = User::all().await?;
let user: Option<User> = User::find(1).await?;

// Mutations
let mut new_user = User { id: 0, name: "Alice".into(), email: "alice@example.com".into(), secret_token: None };
new_user.save().await?; // Auto-executes parameterized INSERT or UPDATE
new_user.delete().await?;
let _ = (all_users, user);
Ok(())
}
}

The Orm derive grammar is fail-closed. Model and field attributes are parsed as structured nested metadata; unknown or duplicate options are compile errors. Every SQLx model requires a persisted named id field. Explicit table/column/relation identifiers use the 1–64 byte portable ASCII identifier grammar, and declared hook, scope, policy, relation-model, tenant, soft-delete, and embedding references are validated before code generation. A relation field accepts exactly one relation declaration; options that do not apply to that relation fail compilation. belongs_to_many requires pivot_table and defaults omitted owner/related pivot keys from the two model names.

Only skip, default, json, and json(nullable) from SQLx field metadata are compatible with generated ORM persistence in v12. rename, try_from, flatten, and unknown SQLx options fail compilation instead of letting the decoded shape drift from generated SQL. Soft-delete sentinel expressions are bounded compile-time SQL fragments, not parameterized runtime values: they are capped at 128 bytes and reject statement separators, NUL, and SQL comments, while portability and semantic review remain the model author’s responsibility.

5.2. Parameterized Queries & Privacy

  • Values accepted by non-raw query APIs use SQLx parameterization. Structural identifiers use the bounded ASCII grammar. pgvector helpers bind canonical vector/distance strings after rejecting empty/non-finite vectors and invalid distances; they do not interpolate those runtime values. Methods explicitly suffixed/named raw remain caller-owned escape hatches rather than an injection-safety claim.
  • Generated builders assemble bindings by emitted clause position (CTE, JOIN, WHERE/HAVING, ORDER BY), not by the order in which fluent methods were called. Nested typed subqueries export that ordered binding sequence.
  • Generated magic filters bind supported primitive fields to their Rust type at compile time (String, i32, f64, and bool), and generated column enums make unknown columns unrepresentable on typed paths. String-column builders, custom RullstValue conversions and raw SQL are explicit runtime-checked or caller-owned alternatives, not compile-time schema verification.
  • String and Option<String> fields annotated with #[orm(encrypted)] are encrypted before generated ORM writes and decrypted after generated model reads using AES-256-GCM. Randomized ciphertext cannot be filtered, ordered, grouped, or explicitly selected by generated query-builder methods; use a separately reviewed blind index when equality lookup is required. Raw SQL remains an explicit, non-transparent escape hatch.

5.3. Generated Relationship Contract

  • SQLx models may declare morph_many, morph_one, and one or more explicit typed morph_to targets. A polymorphic relation requires morph_name = "..." (name remains a legacy alias).
  • morph_to fails macro expansion unless the source has a persisted bindable <morph_name>_id field and a persisted String discriminator named <morph_name>_type. foreign_key may override the ID field and related_key may override the target key.
  • The discriminator stores the Rust target model name. Lazy loading returns None for a different target; eager loading batches each declared target and never guesses an undeclared runtime type. Target models used in eager inverse loading must implement Clone.

5.4. Tenant Scope Contract

  • A SQLx model declaring #[orm(tenant_column = "tenant_id")] must have a persisted String, i32, f64, or bool tenant field. The derive rejects a missing or unsupported field type.
  • Generated queries fail closed when called outside with_tenant(...) and bind the active tenant inside the scope. Generated full/partial updates and instance delete/restore paths reject a model from another tenant.
  • Model::unscoped() is the explicit global escape hatch. Deciding who may use it, deriving tenant identity from authenticated state, and database-level RLS remain host responsibilities.
  • SQLx builders keep offset-based chunk(...) for compatibility and expose fallible chunk_by_id(...)/chunk_by_id_with_tx(...) for stable ascending keyset traversal over the generated i32 primary key. This prevents deletes of processed rows from shifting later rows behind an offset; it is not a database-server cursor or a universal cross-shard snapshot.
  • A model delete with marked cascade_soft_delete has-one/has-many relations runs parent and direct-child mutations in one transaction. An existing explicit or task-scoped transaction is reused; otherwise delete() opens, commits, or rolls back its own transaction. Recursive descendant/cycle traversal remains a separate contract.
  • Generated #[orm(auditable)] instance save()/delete() operations write their bounded audit entry through the same explicit, implicit, or task-scoped transaction as the model mutation. Audit write errors fail the mutation and roll its savepoint back; direct log_audit calls also honor a task-scoped transaction. Every recorded mutation requires an AuditContext naming a validated user, service, or system principal; the record also carries the optional correlation identifier and derives its typed tenant key from the active with_tenant(...) scope. The host remains responsible for deriving both contexts from authenticated authority rather than client assertions.
  • create_audit_table creates the v2 schema and adds its columns to a legacy table without presenting legacy rows as v2 evidence. JSON payloads are bounded and recursively mask sensitive names for create, update, and delete; audit/debug output does not expose principal, tenant, correlation, reason, or payload values.
  • An auditable model exposes restore_revision(audit_id, reason) and its caller-owned transaction variant. Only a bounded v2 update patch for the exact model, ID, and active tenant is eligible. The current row must still match the revision’s recorded post-state; PostgreSQL and MySQL/MariaDB also lock that row during restoration. A successful restore is a compensating audited update referencing the restored audit ID and reason. Legacy, create/delete, oversized, stale, malformed, cross-tenant, or redacted-field revisions fail closed. Bulk builders still do not synthesize per-row history, and durable external export remains an explicit outbox/application contract.

5.5. Process-Local Post-Commit Contract

  • Orm::transaction and direct generated model save()/delete() operations own a post-commit callback scope. after_commit callbacks registered within it run only after SQLx confirms commit and are discarded on rollback. When no managed transaction is active, after_commit executes immediately for an already committed/autocommit operation.
  • Generated observers retain synchronous lifecycle callbacks such as creating, created, and saved for mutation validation. The separate committed(ModelCommittedEvent) callback receives an owned, hidden-field- aware snapshot after the managed commit. Generated Redis invalidation/pub-sub and Scout projections use this same post-commit boundary.
  • Savepoint-scoped generated saves/deletes and revision restores collect their callbacks in a nested scope. The callbacks are promoted to the enclosing commit boundary only after that savepoint succeeds, so catching a failed auditable mutation cannot leak a later committed effect.
  • Every queued callback is attempted. A failure is returned as PostCommit, whose contract explicitly means the database mutation is already durable. Applications must not retry the database mutation blindly from this error.
  • A caller-owned raw SQLx transaction passed to save_with_tx or delete_with_tx does not expose its later commit/rollback decision to the ORM. Use Orm::transaction for the strict process-local boundary.
  • These callbacks do not survive process failure and provide no retry, idempotency or cross-node delivery. Use the explicit durable outbox below for an irreversible or externally delivered effect; it is not enabled automatically by a generated observer.

5.6. Durable Transactional Outbox Contract

  • Outbox::enqueue accepts only a currently managed Orm::transaction and writes rullst_outbox through that same transaction. A domain rollback also removes the event. enqueue_with_tx provides the equivalent explicit path for a caller-owned SQLx transaction. No implicit independent commit is permitted.
  • (stream, event_key) is the database uniqueness boundary. Replaying the same key and exact event kind/payload returns the existing i64 identifier; reusing the key with different content fails closed. stream, event key, event kind and worker identifiers use a bounded ASCII grammar, and serialized payloads are limited to one MiB.
  • PostgreSQL, MySQL/MariaDB and SQLite share the outbox state machine. A claim increments attempts and receives a random token plus a bounded lease. Only that token may acknowledge or fail the event; expiration permits another worker to reclaim it. Failure schedules a bounded retry or moves the event to dead_letter at the configured attempt limit, including a worker that dies while holding its final lease.
  • Delivery is at least once, not exactly once. A worker may perform its external effect and crash before acknowledgement, so consumers must use the stable stream/event key as their own idempotency key. Ordering across retries or concurrent workers is not guaranteed.
  • Outbox::install is an explicit setup/test convenience and never runs at startup. OutboxMigration puts the same schema under the built-in reviewed migration lifecycle. The ORM does not infer tenant authorization from stream, automatically serialize model observers, dispatch HTTP webhooks, purge delivered rows or promise cross-database transactions.

5.7. Generated Redis Query Cache Contract

  • The optional redis feature enables .remember(seconds) for generated SQLx reads. Orm::init_redis_with_namespace(url, application_namespace) is the recommended initializer when a Redis database is shared; the compatibility init_redis(url) initializer uses the literal namespace default.
  • Versioned SHA-256 cache keys bind the validated application namespace, an opaque digest of the active tenant scope when present, table, generated SQL, and typed bindings. Raw tenant identifiers are not emitted in keys.
  • Generated reads always bypass Redis inside explicit and task-scoped database transactions, so cached state cannot replace the transaction’s own view. remember(0) is invalid. Outside transactions, explicitly requesting cache without initializing Redis fails closed as a configuration error; transport failures and corrupt cached JSON fail open to the authoritative database.
  • Cache writes occur only after a successful database read and retain encrypted model fields as ciphertext. Generated model save()/delete() operations invalidate the active tenant/table’s versioned keys only after commit through a bounded Redis SCAN plus asynchronous UNLINK; rollback preserves existing entries. Raw SQL, bulk builders, caller-owned raw transactions and writes from other processes cannot be inferred. Callers must retain a defensive TTL and treat Redis cluster/failover and durable invalidation delivery as separate application contracts.

5.8. Polyglot Persistence Boundary

  • Optional persistence adapters are disabled by default and selected with mongodb, duckdb, turso, surrealdb, qdrant, or the polyglot convenience feature. The umbrella crate exposes matching orm-* features.
  • DocumentRepository<T> provides create, find, replace, delete, and deterministic bounded listing. Collection names, document IDs, offsets and limits are validated before reaching a driver.
  • DocumentInventory<T> is the separate identifier-preserving extension used by recovery tooling. Its stable ascending pages avoid breaking existing third-party DocumentRepository implementations.
  • export_document_snapshot performs two matching bounded observations before sealing a versioned payload with AES-256-GCM. The authentication data binds the key-rotation ID, trusted application namespace and exact collection; decoding is capped at 64 MiB and 100,000 documents. Restoration accepts only an empty destination or an exact matching subset, never replaces or deletes, tolerates only an exact raced duplicate and verifies the complete final inventory. Applications must quiesce source/destination writers and durably store, rotate and protect keys/snapshots. Required destination schema must be provisioned first; driver/schema errors never become an implicit empty collection. The API is crash-resumable, not a cross-store transaction or managed backup service.
  • MongoDbStore<T> uses the official MongoDB Rust driver and stores the portable DocumentId as _id; portable models must not define _id.
  • DuckDbStore serializes access to its native connection and delegates every database operation to spawn_blocking. Dynamic values use prepared parameters, and callers must supply a QueryLimit before rows are materialized. Application-provided SQL text remains a trusted structural input.
  • TursoStore speaks the official Hrana HTTP v3 protocol directly for remote edge SQL. It uses positional typed parameters, conditional atomic batches, a 30-second request deadline, no redirects, a 16-MiB response bound, bounded row materialization, and ordered checksummed migrations. This avoids an unnecessary embedded/native SDK dependency while retaining conformance against the official libSQL server. Empty or mock_* endpoints use a one-connection SQLite fallback that exercises real SQL without pretending to be a remote replica. HTTPS/libsql:// is required outside explicitly enabled loopback development.
  • SurrealDbStore<T> uses the documented /key, /sql, and /gql HTTP endpoints with namespace/database headers, no redirects, bounded streaming responses, HTTPS by default, and redacted authentication configuration. GraphQuery::read_only accepts one MATCH query, rejects mutation tokens and caller-supplied limits, then appends a bounded limit.
  • This boundary does not turn every backend into SQL Active Record, perform cross-database transactions, provide an online-consistent snapshot, synchronize records between engines, or prove a third-party deployment. See the Polyglot Persistence guide.

5.9. Scout Search Projection Contract

  • #[orm(searchable)] projects generated save/delete operations only after a managed relational commit. Search adapter failures remain visible; a failed query is not silently treated as an empty result, and PostCommit means a projection failed after the database mutation became durable.
  • MockSearchEngine is deterministic and always available. The optional scout-http feature adds Meilisearch, Elasticsearch and Algolia. Empty or mock_* credentials select the mock; keyless live constructors accept only loopback HTTP, while remote/custom origins require HTTPS without URL credentials, redirects, paths, queries or fragments.
  • Index names, positive IDs, object payloads, queries, response bytes and hit counts are bounded. Meilisearch/Algolia tasks use bounded polling; Elasticsearch requests use refresh=wait_for. Provider response bodies and credentials are not copied into transport errors.
  • The repository proves a real Meilisearch lifecycle in a digest-pinned container. Elasticsearch and Algolia protocol fixtures prove the documented HTTP shape and bounds, not hosted service operation, version-wide compatibility, ranking quality or cluster failover.
  • The generated hook remains process-local. Guaranteed crash recovery requires an application-versioned event in the transactional Outbox and an idempotent worker; the ORM cannot infer a safe event key or external retry policy from an arbitrary model save.

5.10. PostgreSQL pgvector Contract

  • The optional pgvector feature re-exports the SQLx-compatible Vector type. The supported execution profile combines it with strict-postgres; other SQLx backends do not pretend to implement PostgreSQL vector operators.
  • where_similar, L2, cosine and inner-product ordering validate column names, reject empty/non-finite vectors and invalid distances, and bind vector and distance values. ORDER BY bindings are assembled after WHERE bindings regardless of builder call order.
  • A digest-pinned PostgreSQL + pgvector container installs the extension, uses a typed vector model and proves L2 threshold/cosine ordering queries. The application still owns reviewed migrations, vector dimensions, embedding model compatibility, HNSW/IVFFlat index selection/tuning, tenant policy, context budgets, citations, ingestion/deletion and RAG evaluation.

5.11. Qdrant and Redis Specialized Store Contract

  • The optional qdrant feature exposes a separate VectorRepository rather than pretending Qdrant is SQL Active Record. Collection names, dimensions, vectors, cosine norm, point payloads, query limits and response bytes are bounded. The HTTP client rejects redirects and URL credentials, uses short connect/request deadlines, requires HTTPS outside loopback, redacts API keys, and never copies provider response bodies into errors.
  • QdrantConfig::new selects a deterministic in-process fallback for empty or mock_* endpoint/API-key values. unauthenticated_local is an explicit loopback-only path for self-hosted development. The supported live API is one unnamed dense cosine vector per numeric point with create, single-point upsert/delete and bounded nearest-neighbor query; named/sparse/multivectors, arbitrary filters, collection tuning and distributed topology are outside it.
  • The optional redis feature exposes RedisDataStore for explicitly namespaced Hash, Set and Sorted Set operations in addition to the generated query cache. Keys, fields, UTF-8 values/members, finite scores and scan/range materialization are bounded. Remote endpoints require rediss://; URL credentials are rejected, ACL credentials are redacted, operations have connection/response deadlines, and empty/mock_* credentials select a deterministic in-process fallback.
  • Digest-pinned Qdrant and Redis matrices prove their respective live lifecycles, including Redis namespace/structure separation. They do not prove hosted-provider availability, backups, cluster failover, tenant authorization, eviction policy, ANN quality, or cross-store transactions.

5.12. ORM Telemetry Contract

  • Generated model/query entrypoints, transaction-aware variants, raw ORM queries and generated streams emit rullst.orm.query spans with only a static model, validated table and bounded operation name. SQL text, bindings, model values, DSNs and error strings are not fields of these Rullst-owned spans. The explicit debug query logger remains a separate opt-in surface.
  • Managed transactions emit begin and lifecycle spans. Their final outcome is one of the bounded commit/rollback states; transaction errors are returned to the caller rather than copied into telemetry. Generated stream spans are entered only while the stream is polled, so a tracing guard is never held across suspension.
  • Every pool constructed through Orm::init* emits SQLx pool-acquire timing at info level and promotes acquisitions slower than 500 ms to warnings. Primary and replica pools share this configuration. Direct pools constructed by the application are outside the contract.
  • These standard tracing spans/events are exported when the host enables the umbrella telemetry feature and initializes Core’s OpenTelemetry subscriber. The host still owns OTLP endpoint security, filters, sampling, retention and collector availability. SQLx or application logging configured separately may have its own statement-data policy.

💳 6. Billing, Payments & Fiscal Engine (rullst-capital)

rullst-capital exposes bounded billing-provider and payout-provider adapters, plus a bounded Brazilian digital-invoicing preparation pipeline (NFS-e Nacional). Local cryptographic/schema validity is not tax authorization.

Every reviewed live adapter uses the same fail-closed outbound HTTP boundary: redirects and ambient proxy environment variables are disabled, connection and whole-request timeouts are finite, and a successful provider response is read only up to one MiB before JSON decoding. Failures expose a redacted typed ProviderFailure contract with permanent, transient, and rate-limited classes; only a bounded numeric Retry-After delta is retained. Raw response bodies, request URLs, credentials, and transport diagnostics are not included in the public error. Rullst deliberately does not retry billing mutations: callers may retry a transient or rate-limited result only when that exact operation has a persisted provider-forwarded idempotency key and a reconciliation policy. Returned checkout locations are accepted only as bounded, absolute, credential-free HTTPS URLs without fragments; provider/account sandbox acceptance remains external evidence.

6.1. Multi-Gateway Payment Architecture

Billing adapters implement BillingProvider; the Wise payout adapter implements the separate PayoutProvider contract. Individual billing operations may still return Unsupported when a provider adapter has no reviewed implementation:

#![allow(unused)]
fn main() {
use rullst_capital::providers::stripe::StripeProvider;
use rullst_capital::providers::BillingProvider;

async fn create_checkout() -> Result<(), rullst_capital::CapitalError> {
let provider = StripeProvider::new("mock_api_key", "mock_webhook_secret");
let session = provider
    .create_checkout_session(
        "customer@example.com",
        "price_monthly",
        "https://example.com/billing/complete",
    )
    .await?;
let _ = session;
Ok(())
}
}

#[derive(rullst::Billable)] is the umbrella convenience for named structs with an email: String field. It preserves generics; optional subscription_id: Option<String> and tier: Option<String> fields expose the corresponding helpers. An all-or-none grace_period_starts_at: Option<i64>/grace_period_ends_at: Option<i64> pair exposes a validated half-open window of at most 366 days. A provider-bound SubscriptionHandle<P> delegates cancellation and pausing; the explicit subscription_with path keeps static dispatch. These values do not infer or persist ownership, team membership, entitlement, currency, payment methods, usage or provider scheduling. Shared quota accounting is a separate explicit boundary described below.

The same derive inherits the bounded charge_with/charge helpers for an immediate off-session charge. A charge requires a positive integer amount in currency minor units (maximum eight digits), a three-letter currency, explicit provider customer and tokenized payment-method IDs, the model e-mail and an application-owned idempotency key of at most 255 bytes. BillingProvider::charge defaults to UnsupportedOperation; the reviewed live implementation is Stripe Payment Intents, which forwards the idempotency key, confirms off-session and fails closed on an amount/currency mismatch or a status other than succeeded or processing. Empty/mock_* Stripe credentials return a deterministic local receipt with the distinct non-success Mock status for exact retries. Rullst does not model raw payment credentials, prove that a stored method has a valid mandate, persist idempotency, grant an entitlement, reconcile webhooks or imply direct-charge parity across adapters.

Provider-Specific Metered Usage

MeteredBillingProvider deliberately uses an associated request type rather than pretending provider identities and retry semantics are interchangeable. StripeMeterEvent targets the current /v1/billing/meter_events API with the default stripe_customer_id and value payload mapping. It validates positive integer usage, the meter event name, customer, timestamp window and a forwarded identifier; the Stripe adapter sends that identifier as both event identity and HTTP idempotency key. LemonSqueezyUsageRecord targets /v1/usage-records, requires the numeric subscription-item relationship and an explicit increment or set action that must match the configured aggregation.

Both adapters limit response JSON to one MiB, bind identity/quantity/action fields before returning UsageStatus::Accepted, redact request/receipt identities from Debug, and return deterministic Mock receipts for empty or mock_* API keys. Stripe documents only a rolling provider deduplication window. Lemon Squeezy’s reviewed request has no equivalent event-key field, so its receipt reports ApplicationOutboxRequired; the application must claim event_key durably before submission. Provider-account acceptance, durable outbox storage, retries, reconciliation and entitlement/quota policy remain application/release evidence. The legacy uniform BillingProvider::report_usage is compatibility-only and fails closed for live Stripe/Lemon Squeezy rather than guessing the required provider-specific identity.

Coupons and Relative Trial Extensions

CouponCode accepts at most 256 ASCII identifier bytes and redacts its value from Debug. The Stripe adapter uses discounts[0][coupon], requests expanded discount evidence and accepts only a response bound to both the subscription and requested coupon. Lemon Squeezy documents discount codes for checkout, not post-checkout subscription mutation; it and every adapter without a reviewed live contract return UnsupportedOperation rather than a false success. Empty or mock_* credentials retain the deterministic offline no-op required for local applications and tests.

TrialExtension resolves 1 to 730 whole days against a trusted clock. Billable and SubscriptionHandle expose the historical ergonomic extend_trial(15) meaning plus extend_trial_days_at for a stable persisted command clock and set_trial_end for explicit reconciliation. Stripe sends trial_end; Lemon Squeezy sends trial_ends_at through its JSON:API PATCH. Both cap provider responses and bind the returned subscription and expiration. The host must authorize the subscription owner, persist a stable command time before retry, serialize conflicting changes, reconcile signed webhooks and evaluate provider-specific billing-cycle effects. Live-account acceptance is release evidence, not inferred from protocol fixtures.

Shared Team and Workspace Quotas

BillingSubject identifies one authoritative user, team, workspace or trusted tenant as the owner of both the subscription and its shared counters. Billable::quota_request derives the limit from that owner’s tier policy instead of accepting it from an HTTP payload. A QuotaStore then performs an atomic, idempotent reservation before the application creates the resource.

InMemoryQuotaStore is the deterministic offline/process-local contract. The opt-in quota-sql feature supplies SqlQuotaStore for SQLite, PostgreSQL, MySQL and MariaDB. Its conditional counter update and unique event claim prevent concurrent members from exceeding the same limit. Exact retries return a replay grant without consuming or executing again; a key reused with different units or limit fails closed. QuotaGate::execute blocks the callback before an over-limit creation and compensates an ordinary callback error.

The convenience gate cannot make two unrelated storage systems atomic. A relational application that needs exact quota/resource atomicity must open a transaction from SqlQuotaStore::pool, call reserve_with_transaction, perform the domain insert through that transaction and commit once. Trusted middleware must establish membership and active tenant before constructing the subject. Plan/webhook reconciliation, migrations and custom/non-relational stores remain explicit application work.

6.2. Invoice Rendering

Invoice::generate_html remains the source-compatible escaped HTML renderer. Trusted paths use validate/try_generate_html: the legacy public f64 model accepts only bounded finite positive values with at most two decimal places, converts them to integer minor units, and requires the exact item sum.

The opt-in invoice-pdf feature adds bounded paginated A4 rendering. Its embedded Helvetica subset supports WinAnsi text; other scripts require a caller-supplied TTF/OTF of at most eight MiB containing every used glyph. PDF output is capped at sixteen MiB. Invoice::bind_succeeded_charge creates an immutable PaidInvoice only when a final, non-mock receipt exactly matches the recipient, minor-unit amount and currency. It derives a stable non-secret key from the invoice and provider evidence for use by an application outbox.

The downstream rullst-mail/capital-invoice feature converts that value into a pipeline-validated HTML message with the PDF attached and exposes one-call facade, tenant-aware or static-driver delivery. It does not infer a webhook event, atomically claim the delivery key, guarantee provider acceptance or promise exactly-once delivery. Applications processing retries or multiple instances must persist/claim the key and reconcile payment state durably before sending.

6.3. Webhook Signature Verification

  • The Axum and opt-in Actix middleware adapters call one canonical bounded verifier before dispatch. Built-in provider adapters use provider-appropriate cryptographic verification; equality checks for derived signatures are constant-time where applicable.
  • Timestamped protocols enforce a bounded freshness window. The default replay store is bounded and process-local and fails closed instead of evicting an unexpired proof when full.
  • The opt-in webhook-sql store shares bounded payload-digest or semantic-event claims across processes on SQLite, PostgreSQL, MySQL, and MariaDB. Its schema profile is immutable, claims serialize through one configuration lock, expiry uses the database transaction clock, and storage/configuration/capacity failures reject the request.
  • SQL-backed middleware admission claims a payload before handler dispatch; it is replay protection, not an exactly-once delivery protocol. When billing correctness requires atomic domain mutation, the application must verify the provider payload, select the provider’s stable event ID, and call check_and_record_event_key_with_transaction through the same relational transaction as the mutation. Cross-system effects still require an outbox, idempotent consumers, and reconciliation.

6.4. NFS-e Nacional Specification (FiscalEngine)

  • 🟢 [Implemented / Bounded] DPS 1.01 Builder: NfseDpsV101 models an ordinary domestic-service subset, validates CPF/CNPJ/IBGE/identifier/text limits, keeps BRL values in integer cents and ISS rates in basis points, and emits an unsigned DPS in the official namespace. The legacy floating-point preview remains compatibility-only.
  • 🟢 [Implemented / Bounded] Pinned Schema Validation: Production profile v1.01-20260209 and restricted profile v1.01-20260727 carry immutable archive/file SHA-256 values. NfseDpsSchemaValidator reads only the expected bounded files and resolves imports from an in-memory catalogue; it never downloads schemas or follows instance hints.
  • 🟢 [Implemented / Bounded] Local XMLDSig and mTLS Preparation: sign_dps_xml parses a protected PKCS#12 A1 container, rejects malformed/duplicate/already-signed envelopes and emits an enveloped inclusive-C14N 1.0 RSA-SHA256 signature over the unique infDPS/@Id. The matching certificate chain is embedded and tested with independent local verification. The same container can construct a rustls mTLS identity/client with HTTPS-only, no redirects, and bounded timeouts.
  • 🟢 [Implemented / Bounded] Offline SEFIN Issuance Codec: NfseIssueRequest accepts only one structurally bound and cryptographically valid embedded DPS XMLDSig, emits deterministic GZip/Base64 inside the exact dpsXmlGZipB64 JSON object, and parses at most four MiB. HTTP 201 can become Authorized only when environment, submitted DPS ID, 50-digit access key, infNFSe/@Id and the embedded NFS-e XMLDSig agree; HTTP 400/403/500 become a separate bounded Rejected variant. Unknown fields, malformed JSON/XML/Base64/GZip, duplicate/confused IDs, invalid signatures and decompression amplification fail closed. Embedded-signature validity does not establish ICP-Brasil trust or emitter ownership.
  • 🟢 [Implemented / Bounded] Local Fiscal Command Journal: The nfse feature exposes a single-active-writer FiscalCommandJournal that accepts only a homologation/production command whose selected environment equals the signed infDPS/tpAmb. It synchronously records a prepared command before any caller-owned transport and then one bound authorized or rejected terminal result. Exact command/request/result replays do not append; key reuse with different material, invalid transitions, external file growth, quota exhaustion, wrong keys, symlinks, corruption and durability uncertainty fail closed. The append-only v1 file is bounded to 16 MiB and 4,096 events, uses a named 256-bit HMAC key and chains every frame to the prior tag. It stores the caller’s opaque command ID, request/result digests, environment, state and bounded times, never the DPS/NFS-e XML, access key, certificate, response body or processing messages. pending() recovers minimized unresolved descriptors after restart. A serializable exact-tip checkpoint can detect valid-prefix truncation only when retained independently. The host owns a non-PII command namespace, key custody/rotation, a trusted directory, one active writer, secure storage of the actual request, checkpoint persistence, backup/retention, authority reconciliation and retry policy; this journal does not transmit, retry, prove cross-system exactly-once or establish tax authorization.
  • 🟡 [Simulado] Offline Mock Environment: NfseEnvironment::Mock produces deterministic test fixtures for local sandboxing.
  • 🔵 [Roadmap / External Evidence] Official SEFIN Homologation & Production: Homologation and Production validate credentials and then return FiscalError::Unsupported without network I/O. Enabling transmission requires emitter-certificate/ICP-Brasil lifecycle checks, deployment of the local journal plus authoritative request/outbox and reconciliation storage, retained protocol fixtures, real restricted-environment tests with an authorized contributor and municipality, independent review, and successful official homologation.

🛡️ 7. Enterprise Security, RASP & Vault (rullst-security)

7.1. Rullst Vault (Authenticated Field Encryption)

  • Algorithm: AES-256-GCM with authenticated 96-bit random nonces and 128-bit authentication tags.
  • Envelope Format: RULLST:v2:<key_id>:<base64_nonce>:<base64_ciphertext_and_tag>.
  • Key Rotation: Built-in keyring support (decrypt_with_keyring) can read prior keys while new writes use the active key. Deployment coordination, re-encryption, key custody and retirement remain operator responsibilities.
  • ORM Configuration: RULLST_ENCRYPTION_KEY, RULLST_ENCRYPTION_KEY_ID, and RULLST_ENCRYPTION_KEYRING select the current and still-readable prior keys. Rullst does not provide key custody or automatic retirement.

7.2. Runtime Application Self-Protection (RASP)

  • Bounded Heuristic Inspector: ASCII case-insensitive signature matching covers selected SQL injection, traversal, SSRF, shell/JNDI patterns across URI, non-secret headers, and supported bounded textual/JSON bodies. Percent decoding and body/JSON inspection allocate; this control does not replace typed parsing, SQL binds, validation, authorization, or SSRF allowlists.
  • Login Guard Tarpit: record_login_failure returns progressive delay decisions and record_login_failure_and_wait applies them asynchronously; both share bounded, temporary in-memory jails keyed by a hashed identity.

7.3. MFA and Security Evidence Boundaries

  • TOTP enrollment: Secrets contain 160 bits derived from the OS RNG, verification accepts exactly six ASCII digits with constant-time comparison, and enrollment can emit an otpauth:// URI or bounded SVG QR. Secret custody, recovery workflow and durable rate limiting belong to the application.
  • Security CLI: CycloneDX generation, MSRV/tool diagnostics, unsafe/IDOR source heuristics, network observations and compliance evidence are bounded checks. They do not certify a deployment, prove absence of vulnerabilities or replace provider/CI evidence tied to an immutable SHA.

7.4. Bounded JSON Schema Enforcement

  • JsonSchemaPolicy::from_schema compiles an application-supplied JSON Schema 2020-12 document once; from_openapi_component selects one explicit components.schemas entry from OpenAPI 3.1. OpenAPI 3.0 is rejected because it is not the same schema dialect.
  • Construction caps serialized bytes, node count and depth, rejects non-local $ref/$dynamicRef, disables network/filesystem retrieval and selects the linear-time regex engine. The route-scoped Axum middleware first enforces the existing exact media-type, syntax, duplicate-key, payload-size and depth boundary, then returns 422 for schema mismatch without echoing values.
  • The policy validates JSON bodies only. Authentication, authorization, ownership, business invariants and query/header/form parameters remain separate application boundaries.

7.5. Deterministic Threat Sentinel and Proof of Work

  • ThreatClassifier assesses a bounded aggregate window supplied by the host against transparent thresholds for credential stuffing, API scraping and distributed automation. It does not collect traffic, infer identity, use a model or attribute a botnet.
  • ProofOfWorkGate issues OS-random, HMAC-authenticated challenges bound to one canonical application subject. Tokens have bounded difficulty, TTL and cardinality; successful verification atomically consumes local state so only one concurrent verifier succeeds in the process.
  • Classification is evidence, not authorization. The host chooses whether and where to challenge, provides an accessible fallback, rate-limits issuance and owns trusted proxy/device policy. Replay state is process-local; distributed enforcement, durable telemetry and cross-process one-shot consumption require an application adapter.

7.6. Authenticated Local SIEM Journal

  • AuthenticatedSiemSpool is the opt-in authenticated counterpart to the compatible unsigned DurableSiemSpool. It normalizes each local v1 event, writes it synchronously under 16 MiB/4,096-record ceilings and authenticates sequence, key identifier, predecessor tag, payload length and exact payload with a domain-separated HMAC-SHA256 chain.
  • SiemKeyRing accepts one active write key and at most seven historical verification keys. Key identifiers are bounded, key material is consumed into zeroizing storage, and secret-bearing Debug output is redacted. A rotation reopens the spool with the new active key plus every still-needed historical key; silently missing or wrong keys fail closed.
  • This journal detects forged records, interior deletion/reordering and external length changes. Removal of a complete valid tail is indistinguishable from an earlier valid file unless the operator retains a trusted external checkpoint. Path/key custody, permissions, single-writer enforcement, rotation retirement, compaction, retention, backup, transport, retry, acknowledgement and dead-letter handling remain operator/application work.

📡 8. IoT, Firmware Security & Protocol Frames (rullst-iot)

8.1. Ed25519 OTA Firmware Gate

  • Firmware Verification: Strict Ed25519 signature validation over a cryptographic manifest [target, version, rollback_counter, firmware_len, firmware_sha256].
  • Anti-Rollback Protection: Verification rejects any counter lower than or equal to the state loaded into the manager. The recommended RollbackCounterStore path additionally performs an exact compare-and-set and may report success only after a strictly increasing value is durably committed across reset. Atomicity, integrity, wear-leveling and power-loss behavior are obligations of the caller’s platform adapter and require hardware-specific evidence.
  • Commit Invariant: In-memory partition selection and store-backed counter commit are blocked until full cryptographic verification succeeds. verified_target_partition exposes the inactive bank for platform flash/read-back before commit. The compatibility commit_verified_update path is process-local and does not claim persistence, flash or bootloader control.

8.2. Embedded Sensor Frames (#![no_std])

  • rullst-iot core models compile under bare-metal #![no_std] targets (STM32, ESP32-C3, Cortex-M).
  • cargo rullst make:iot <DeviceName> generates and registers a local telemetry module, enables the umbrella iot feature and refuses unsafe names or collisions. It does not install firmware, a HAL, MQTT or CoAP.
  • IotDashboard renders an escaped HTML snapshot. It does not infer online state or provide a live device connection.
  • MqttPublish encodes one bounded MQTT 5 PUBLISH packet with validated topic, minimal Remaining Length, QoS/packet-identifier invariants and an empty property section. CoapRequest encodes bounded RFC 7252 base requests with a token, ordered URI-Path/Content-Format options and a non-empty payload marker. Both compile under no_std; neither opens a socket or owns protocol session state.
  • 🔵 [Roadmap] MQTT/CoAP Transport: Async connections, TLS/DTLS, broker negotiation, acknowledgements, retransmission/congestion control, block-wise transfer and interoperability matrices remain separate integration work.

🤖 9. AI Agent & LLM Orchestration (rullst-ai)

9.1. Guarded AI Client

  • Provider-agnostic interface for Google Gemini, OpenAI, Anthropic Claude, DeepSeek, Ollama, and explicit OpenAI-compatible endpoints. The compatible adapter is chat-only by default; applications declare optional request shapes for one exact model. Loopback may be unauthenticated, cloud requires HTTPS/Bearer, and unrelated protocols implement the public AiProvider boundary rather than passing through arbitrary HTTP.
  • Prompt Injection Firewall: Real-time token heuristics intercepting prompt exfiltration, instruction overrides (DAN mode), and delimiter injection attacks.
  • Automated PII Masking: Scrubs sensitive data (CPF/CNPJ, credit cards, emails) prior to outbound LLM dispatch.

9.2. Bounded Streaming and Cancellation

  • StreamingAiClient<P> preserves static dispatch, reapplies the mandatory input guardrails and enforces at most 4,096 non-empty chunks, 64 KiB per chunk and 2 MiB aggregate output independently of the provider.
  • An OpenAI-compatible configuration may explicitly declare SSE streaming. The transport requires text/event-stream, bounded raw bytes, supported chat deltas and [DONE]; malformed, truncated or oversized streams fail closed.
  • AiCancellation races the initial request and every streamed body read. It drops local transport work but does not prove upstream cancellation or stop provider billing. Non-compatible protocols and ordinary non-streaming calls retain deadline/drop semantics.

9.3. Bounded Tenant-Aware RAG

  • RagPipeline::answer requires a trusted Core TenantContext and composes guarded embedding, a static-dispatch application RagRetriever, bounded context selection, guarded generation, source metadata, and one required terminal RagAuditSink event.
  • Retrieved documents carry the trusted tenant tag. The pipeline rejects mismatches, over-return, injection heuristics, empty context, non-finite embeddings, and unavailable mandatory audit evidence rather than silently generating an ungrounded response.
  • Context limits count Unicode scalar values per document and in total. The audit event omits raw question, context, embeddings, provider bodies, and answer; its SHA-256 query digest is correlation metadata, not encryption.
  • InMemoryRagRetriever and InMemoryRagAuditTrail are bounded process-local development/test implementations. DurableRagAuditTrail and DurableToolAuditTrail add bounded, synchronously persisted, versioned local evidence with restart validation and fail-closed corruption/quota handling. They are single-process writers; their SHA-256 frames detect corruption but do not authenticate events. Production hosts own authoritative tenant and ownership predicates, durable/external vector adapters, ingestion/deletion, model/vector compatibility, output policy, directory permissions, rotation/retention/backup, external audit delivery, tuning, evaluation and recovery.

9.4. Bounded Conversational Memory

  • StatefulChat<M> uses static dispatch over ChatMemory, requires a trusted TenantContext and validated ConversationId, loads only the configured even number of recent messages, applies the guarded AiClient, and persists the user/assistant exchange only after generation succeeds.
  • InMemoryChatMemory is deterministic, tenant-partitioned, cardinality-bound, and intended for tests/local use. The opt-in sql-memory adapter supports SQLite, PostgreSQL, MySQL, and MariaDB through a dedicated SQLx Any pool.
  • The SQL adapter advances an even conversation revision and inserts both messages in the same transaction. A compare-and-swap predicate rejects stale cross-process writers; Rullst deliberately does not retry the provider call. History reads bind the tenant/conversation and never include rows newer than the revision observed by that read.
  • Message text is not encrypted by this adapter. Authenticated conversation ownership within a tenant, retention/erasure, provider audit, backups, migration governance, and user-facing conflict retry remain host policy. The generated Turso/custom-model scaffold is a separate application-owned path.

9.5. Authenticated Audit Export

  • AuditDeliveryClient exports an application-minimized serializable event in a versioned JSON envelope of at most 16 KiB. Cloud endpoints require HTTPS; local HTTP(S) requires a literal loopback IP. Redirects and ambient proxies are disabled.
  • HMAC-SHA256 covers a domain separator, key ID, Unix-millisecond timestamp and the exact body. A caller-supplied event ID remains unchanged across one to five attempts. Only transport/deadline errors, HTTP 429 and HTTP 5xx are retryable, and a closed acknowledgement of at most 8 KiB must bind that ID. AiCancellation races request, body and retry waits. Empty or mock_* keys select deterministic offline behavior.
  • A timeout may follow remote acceptance. The receiver therefore owns signature and freshness verification, event-ID deduplication, authorization, persistence, retention, key distribution/rotation and operational availability. The client does not minimize arbitrary event values or provide a durable outbox/SIEM service.

9.6. Bounded Adaptive Evaluation

  • AdaptiveAiEvaluator<P> keeps static provider dispatch and reapplies the mandatory prompt guardrail on every strategy-generated prompt. A scenario is capped at 32 turns, 16 KiB per prompt and 2 MiB per response, with an independent per-turn deadline and AiCancellation raced against each call.
  • AiEvaluationStrategy temporarily receives the bounded response or a low-cardinality guardrail/provider/deadline outcome and explicitly chooses pass, fail, inconclusive or a next prompt. The synchronous strategy is application code and must not persist/log model text without policy.
  • The version-1 JSON report records caller-supplied suite and exact model/configuration subject labels, provider name, terminal code and only per-turn byte counts/outcomes. It retains no prompt, response or provider error. Subject labels are assertions, not automatic model discovery.
  • Deterministic repository fixtures prove bounds, feedback, redaction, cancellation and status semantics. Operators still version domain corpora, run them against every exact live model/configuration and review results; a pass is not universal safety, groundedness or jailbreak-resistance evidence.

📊 10. Control Center & Admin Interfaces (rullst-studio & rullst-nexus)

10.1. Rullst Studio (http://127.0.0.1:5555)

  • Local-first developer dashboard with a server-rendered dark interface; browser assets and final page policy remain deployment concerns.
  • Process observations sourced from RadarSnapshot::collect() and explicitly supplied local collectors; unsupported values remain unavailable.
  • Generated applications start the standalone Studio only in debug builds and bind it to loopback. Its local capability verifies the direct loopback peer, accepts only a local Host authority, requires same-origin Origin on unsafe methods, and rejects missing origins on mutations. This is a local DNS-rebinding/CSRF boundary, not production authentication.
  • Queue, revenue, security and telemetry pages report only values supplied by their configured process-local source. Unsupported driver operations and disconnected integrations remain errors or Unavailable. The standalone migration surface provides CLI guidance and returns 501 from legacy mutation handlers because no migration/seeder registry is installed.
  • The database browser accepts a deliberately narrow ASCII SQL-identifier boundary. Reads are bounded; writes require the crate-private proof inserted by the verified local middleware, database-inspected table/column/complete-PK metadata, a 64 KiB request limit, primitive typed binds and exactly one affected row. Primary keys/backend-specific values are read-only, while delete requires DELETE <table>. SQLite, PostgreSQL, MySQL and MariaDB run separate mutation contracts. This is not application authorization, tenant scoping, audit, rollback or shared-production administration. The ER diagram inspects the same relational backends with bound lookup values and strict normalized Mermaid identifiers. Swagger requires an application-supplied OpenApi.
  • Request SSE records method, URI, status, and latency without bodies or headers. Environment values are redacted by default and the typed config projection never renders connection URLs, filesystem paths, cookies, tokens, or credentials. A successful Studio database-flag mutation invalidates warm DbFeatureDriver caches in the same process; direct writers and other processes remain subject to TTL unless the host distributes invalidation. SQLite removes completed jobs by default. An explicit 1–100,000-row history policy retains and atomically prunes real completion records for Studio, with a separate purge; retained payload access and lifecycle belong to the host. Redis/custom queue inspection remains capability-specific.
  • Studio::with_cache is an explicit metadata-only diagnostic capability. The memory and Redis cache drivers return at most 200 sorted entries containing logical key, UTF-8 value byte length and remaining TTL; custom drivers return InspectionUnsupported by default. Studio displays at most 100 keyed opaque identifiers and never cache values or exact logical keys. Individual invalidation requires its unforgeable verified-local marker and a fresh process-bound HMAC token. The page has no bulk flush operation.
  • Distributed trace producers use separately mounted, push-only routers; each endpoint binds one exact producer name to one key and multiple producers use separate endpoints/keys over the same shared store. A v1 batch is at most 128 KiB and 128 spans, carries W3C-compatible IDs, a bounded source/operation/kind/timing/status set, and accepts no attributes, SQL, bindings, headers, bodies or error strings. HMAC-SHA256 authenticates the exact body and source/timestamp/nonce headers; a 60-second window and bounded atomic nonce cache reject replay, while the shared in-process store is capacity-bound and idempotent by trace/span ID. The local viewer reports a fixed 100 ms slow-query signal and a three-equal-label possible-N+1 heuristic. This is not an OTLP collector, durable backend, secret manager, remote viewer/login, or proof that a repeated operation is defective. TLS, network policy, key distribution/rotation, clock synchronization, producer label redaction, retention and availability belong to the deployment.
  • Exposing Studio beyond the developer machine requires an explicit authenticated network boundary owned by the application; no environment variable silently converts the local server into a production admin surface.

10.2. Rullst Nexus (/nexus)

  • Auto-generated CMS with dynamic CRUD operations and AI Admin Assistant.
  • Security Default: Fail-closed by design; requires explicit authentication middleware and RBAC role validation (admin) on all mutating endpoints.
  • Generated applications may use NexusAuthPolicy::local_development_or_basic_from_env(): debug builds accept only a peer address verified as loopback through ConnectInfo, while release builds require validated Basic Auth credentials from the environment. Missing peer metadata is denied, and an environment mode flag cannot enable unauthenticated release access.
  • A model may explicitly declare one text tenant column. Nexus then obtains the scope only from a trusted Core TenantContext, injects it on create, includes it in every built-in read/mutation/batch predicate, and denies a missing context. Models without that metadata remain global administrator models. Authentication and tenant-membership resolution remain host contracts.
  • with_required_audit requires the fixed rullst_nexus_audits schema and appends one minimized committed-mutation row in the same transaction. Audit unavailability rolls the data change back. This is not append-only, tamper-evident, denied-attempt, retention, backup, replication or external-SIEM evidence; those properties remain host responsibilities.

🛡️ 11. Architectural Guidelines for Backward Compatibility

  1. #[non_exhaustive] on Public Structs: All configuration structs and enums must use #[non_exhaustive] to ensure minor versions can add fields without breaking downstream code.
  2. Deprecation Policy (#[deprecated]): Public APIs will never be removed without at least one minor release cycle marked with #[deprecated].
  3. Ergonomic String Constructors: Public constructors accept impl Into<String> to support both &str literals and owned String parameters without boilerplate.
  4. Zero-Panic Invariant: Production paths must never call panic!(), unwrap(), or expect(); domain errors must return typed Result<T, AppError>.

🔄 12. Assisted Framework Upgrade Contract

cargo rullst upgrade is the canonical application-upgrade boundary. It is an assistant, not a claim that compilation proves production compatibility.

  • 🟢 [Implemented / Bounded] Planning: --dry-run enumerates only Cargo workspace manifests, preserves TOML comments/order, understands normal, inline-table, workspace, target-specific and renamed Rullst dependencies, and reports path/git dependencies that have no version instead of guessing. --dry-run --json emits the versioned rullst.upgrade-plan.v1 envelope.
  • 🟢 [Implemented / Bounded] Versioned Rules: source findings are selected from a versioned rule catalog using detected source majors and the exact target major. Every future major release must extend that catalog, migration documentation, negative tests and process-level fixtures for its supported upgrade paths.
  • 🟢 [Implemented / Bounded] Transaction: the default target is the exact installed cargo-rullst version; --to accepts only the same major train as that CLI. Before writes, the command snapshots workspace manifests, the root lockfile and Rust sources under target/rullst-upgrades. It applies only dependency edits and compiler-provided cargo fix changes, then requires cargo check --workspace --all-targets to pass. A failed gate restores the snapshot by default; --keep-on-failure is explicit, and --restore can recover a persisted, path-validated snapshot after an interruption. Process fixtures independently select the v5, v6 and v11 rule sets, prove atomic restoration across multiple workspace members, preserve a failed edit only when explicitly requested, restore that persisted review state, and reject symlinked Rust sources before starting the transaction.
  • 🟠 [Manual Application Boundary] the command never installs a CLI, changes secrets, executes database migrations, invents authorization or tenant policy, exposes Nexus/Studio, validates providers, or declares an application production-ready. Database restore/migration/rollback, the full test suite, authorization negatives and deployment smoke tests remain mandatory human-owned gates.

📱 13. Omni Packaging Contract

cargo rullst make:omni generates an application-owned Tauri packaging shell; it is not a native-runtime abstraction or store-publication service.

The canonical product is the Rullst web application. Server-side domain rules, authentication, authorization, persistence, realtime policy and security controls remain authoritative and must work without trusting the platform shell. Omni is web-first, platform-enhanced: it may add narrowly scoped native capabilities, but it must not fork the business/security model or move authoritative secrets into JavaScript or an untrusted client.

  • 🟢 [Implemented / Bounded] Deterministic Scaffold: --platform accepts desktop, Android and iOS selections without a prompt. Mobile selections require --backend-url; HTTPS is required except for explicitly bounded loopback/emulator development hosts, and embedded credentials are rejected. Product name and application version default to validated Cargo package metadata. --product-name, --app-version and --identifier provide deterministic overrides. Android/iOS require an application-owned lowercase reverse-DNS identifier and reject framework/reserved example placeholders; desktop-only development may use a clearly documented com.example value.
  • 🟢 [Implemented / Bounded] Reproducible Tooling: the generated manifest pins the Tauri CLI and Rust dependencies, emits a restrictive local CSP and real source-derived platform icons, and treats npm, icon generation or explicitly requested mobile initialization failures as command failures. Explicit iOS initialization requires macOS/Xcode.
  • 🟢 [Implemented / Bounded] Remote-content Boundary: the generated local bootstrap exposes no Tauri IPC API to the remote application. A native navigation callback permits only Tauri’s packaged origin and the exact scheme/host/effective-port tuple of the configured backend; cross-origin links and OAuth must use a separately reviewed system-browser/deep-link flow. The bootstrap provides an accessible initial offline/retry state, but this is not offline application data or synchronization.
  • 🟢 [Implemented / Bounded] Desktop Lifecycle: the one-command local http://localhost:3000 development profile owns its child process, refuses a pre-existing port rather than attaching to an unknown process, stops on early child exit or timeout, and terminates only the child it spawned. HTTPS and other configured origins are treated as externally operated backends.
  • 🟢 [Implemented / Bounded] Shared Wire Contract: rullst::client_contract provides the strict rullst.client v1 JSON marker, positive version negotiation, private typed request/success/failure envelopes, bounded log-safe correlation and idempotency tokens, server time, message-free dotted failure codes and a codec capped at 2 MiB. Unknown outer fields, unsupported versions and oversized bodies fail closed. The same code compiles natively and for wasm32-unknown-unknown. The envelope carries no role, tenant or authorization claim; the server must derive those from its authenticated context and persist idempotency atomically.
  • 🟢 [Implemented / Feature-gated Foundation] Offline State Contract: the native offline-sync feature supplies one account-bound, versioned state with bounded cached records, FIFO idempotent mutations, server revisions and cursors, atomic response application, explicit conflict isolation, full resync, quotas, cache recovery and logical erasure. OfflineSnapshotCipher authenticates and encrypts the closed snapshot with randomized AES-256-GCM, binds it to the exact account and rotation-key id, revalidates every bound after decryption, redacts state payloads from aggregate Debug, and zeroizes its owned key/plaintext buffers where possible. It never treats client time, cached identity/role/tenant, score or local revision as authority. A static-dispatch coordinator bounds foreground push/pull requests, mandates per-request timeout, stops on retryable no-progress and rejects a continuing page whose cursor did not advance. Keychain/Keystore, atomic platform persistence, browser storage, concrete authenticated HTTP, retry/background orchestration, concrete future-schema migrations, physical-device recovery/erasure evidence and application conflict UX remain explicit platform/application work; therefore the generated shell alone is not an offline-first application.
  • 🟢 [Implemented / Hosted Compile Evidence] Compile Evidence: on commit 755fbd61933bed04369e0eb5de50b11275db5e3d, path-aware workflows created disposable hosts and passed fresh desktop shell checks on Linux, macOS and Windows, an Android aarch64 debug APK build, and an iOS simulator build. These are compile gates for that SHA, not physical-device or store evidence.
  • 🟠 [Application / Platform Boundary] bundle identity, signing and provisioning, privacy manifest and usage declarations, native capabilities, production endpoint/auth policy, physical-device testing, TestFlight, Play testing, metadata and store review belong to the generated application. Offline sync, push, biometrics, OS secure storage, deep links and signed updates are not implied by the web shell and require opt-in capability scopes plus platform tests. Simulator/APK compilation must never be described as store acceptance or universal iPhone/Android compatibility.

Capability status and vision decisions

Rullst’s ambitious ideas are not deleted when an implementation is incomplete. They are kept here with an explicit status and an engineering recommendation. User-facing guides describe what can be used today; this ledger preserves the larger vision without presenting roadmap work as a production capability.

Status is evaluated against the current version 12 source tree, not against an old changelog entry or marketing description:

  • Implemented: a real implementation and focused tests exist; deployment responsibilities may still apply.
  • Partial: useful foundations exist, but the advertised end-to-end contract is not complete.
  • Experimental: available only behind an explicit experimental/simulator boundary and carries no production guarantee.
  • Not implemented: absent or intentionally returns a typed Unsupported error instead of simulating success.
  • Do not promise: the absolute claim is not a technically honest product contract; a narrower engineering goal may still be worthwhile.

The recommendation column is intentionally opinionated. “Worth implementing” does not mean “put it in Core”: several excellent ideas belong in optional, independently tested crates. Ideas that additionally need continuous operations, homologation, or hardware can follow the Maybe SaaS incubation strategy.

Architecture and framework contract

Capability or former claimCurrent statusRecommendation and reason
Runtime-only Core with optional ORMImplemented in current hardening (keep validating)Bare rullst-core now defaults to the HTTP/runtime surface without SQLx/ORM; orm and queue-sqlite are independent opt-ins. Studio and Nexus request their database features explicitly, while the application umbrella keeps orm and queue-sqlite in its default set for ergonomic compatibility.
Lifecycle-aware readiness and graceful request drainImplemented / bounded per processApplicationLifecycle holds monotonic startup/ready/draining/stopped state, at most 32 immutable validated required-component bits, fail-closed request admission and a bounded wait for accepted requests. The lifecycle-aware /ready emits only phase/counts and becomes 503 for startup, dependency failure, lock corruption and drain while liveness remains process-only. Server marks ready after binding, begins drain before Axum’s graceful wait, marks startup failure/termination stopped, and exposes a caller-supplied shutdown future for supervisors/tests. Applications still run timeout-protected dependency checks, establish identity/authorization and operate replica, load-balancer, preStop and termination-deadline behavior.
One canonical security stackPartial (composition risk mitigated; high priority)Core and the extended crate now reuse one CspNonce, so nested header layers do not invalidate renderer output. Ownership is still split: the Server mounts Core CSRF/WAF/headers/PII while rullst-security supplies explicit RASP/DLP/abuse/telemetry layers. Introduce a Core stack contract implemented by the dedicated crate, then deprecate duplicates after parity tests; reversing the dependency directly would create a cycle.
Umbrella features for every advertised crateImplemented in current hardening (keep tested)The umbrella exposes optional security, iot, base mail and other domain features, restores the oauth/Connect re-export, keeps SMTP separate through mail-smtp, and has focused facade tests including payment-bound Capital/Mail composition. The extended security suite remains nested under security::runtime because its surface intentionally overlaps the Core baseline; CspNonce itself is already one shared type.
Static dispatch everywherePartial (do not force absolutely)Preserve generic/static paths for common cases, but runtime provider registries legitimately need dynamic dispatch. Document each intentional dyn Trait boundary instead of pretending it does not exist.
A universal environment/configuration policyImplemented (keep)The validated environment model and fail-closed production startup use exact RULLST_ENV → legacy APP_ENV → file precedence. New .env, Kubernetes, Foundry and billing scaffolds emit/read the canonical name first; retain the alias for existing applications and keep negative configuration tests.
Every source file below 500 linesPartial (worth continuous refactoring)Keep 500 lines as a design target, not a release fiction. Split production modules by responsibility; large test fixtures can use a looser limit.
Uniform #[non_exhaustive], fallible builders, and impl Into<String>Partial (worth completing incrementally)Finish this during SemVer-reviewed API work. Mechanical mass changes without compatibility review are not worth the risk.
“Zero lock-in” or complete automatic ejectionDo not promise (migration tooling is worthwhile)Standard Axum/SQLx escape hatches and an inspected eject snapshot reduce coupling, but no full-stack framework can guarantee zero migration cost for every optional subsystem.
“Zero panic/crash-free runtime”Do not promise (the scoped policy is worthwhile)Enforce typed errors and reject panic!/unwrap/expect in declared production paths. Dependencies, OOM, aborts, and host failures make an absolute runtime guarantee impossible.
“Zero latency/zero allocation everywhere”Do not promise (benchmark instead)Bounded allocation and latency goals are excellent, but only reproducible per-operation benchmarks should carry numbers.
ORM performance versus Diesel and SeaORMImplemented reproducible bounded harness; no superiority claimA pinned Criterion target runs five equivalent operations against typed SQLite with one connection per ORM, the same schema/index/100-row seed and identical SQLite policy. CI records Rullst, Diesel and SeaORM under the same commit/runner. The initial local smoke contradicted the historical “negligible overhead versus Diesel” claim, which is therefore rejected rather than marketed away. SQLite microbenchmarks do not establish networked database throughput, concurrency, memory, tail latency or full-application performance.
“100% memory-safe/no unsafe anywhere”Do not promise (an unsafe allowlist is worthwhile)Rust dependencies and the development FFI/hot-swap boundary contain reviewed unsafe. Keep the allowlist tiny, documented with SAFETY, and CI-enforced instead of hiding it.
“100% Pure-Rustls for every feature”Do not promise (a transport inventory is worthwhile)Prefer Rustls-backed first-party clients, then inspect the complete feature-specific lockfile. Transitive and optional stacks make a universal brand claim brittle.
Static competitor matrix declaring other frameworks lack featuresDo not maintain without dated sourcesComparative research can be useful, but ecosystems change quickly and absence claims are easy to get wrong. Prefer a Rullst capability/boundary matrix or a dated, sourced benchmark repository.
Framework-wide “production-ready” labelDo not promise as one booleanPublish stability per crate/capability. Routing can be stable while live fiscal or hardware integration remains unavailable.

ORM and persistence

Capability or former claimCurrent statusRecommendation and reason
SQLx Active Record, transactions, migrations and relationsImplemented / boundedKeep the PostgreSQL, MySQL/MariaDB and SQLite matrices. Executor-aware generated operations and explicit transaction handles have commit/rollback evidence; typed morph_to plus morph_many/morph_one cover declared SQLx polymorphic targets without a runtime type registry. Caller-owned raw SQL must use the supplied transaction rather than assuming pool calls are scoped.
Compile-time query typingImplemented / bounded typed pathGenerated primitive-field filters require the persisted Rust value type and generated column enums reject unknown columns. Dynamic string-column builders, custom conversions and raw SQL intentionally remain runtime checked; do not describe them as SQLx query!-style live-schema verification.
Native relational enum mappingImplemented / bounded per SQLx profile#[derive(Enum)] owns one validated label mapping for Serde, string/RullstValue conversion and SQLx encode/decode. Blueprint::native_enum creates and exact-drift-checks a named type on strict-postgres, emits inline ENUM on MySQL/MariaDB, and emits TEXT CHECK on SQLite. SQLx Any PostgreSQL is rejected before DDL because it cannot decode custom types. Adding, removing or reordering variants, deployment ordering and PostgreSQL type removal remain reviewed migration work.
Fail-closed model tenant scopesImplemented / boundedtenant_column now requires a supported persisted field, denies queries/mutations without with_tenant, binds tenant predicates and rejects cross-tenant instance mutations. Keep unscoped() explicit and require application authorization/database RLS where appropriate.
Stable large-table chunk traversalImplemented / boundedUse fallible chunk_by_id for ascending keyset traversal over generated i32 IDs; deleting processed rows cannot shift later rows behind an offset. chunk remains offset-based, and neither API claims a server cursor, cross-shard snapshot or immunity to unbounded concurrent inserts.
Automatic schema synchronization and destructive migrationPartialThe SQLite AST diff is a useful reviewed scaffold. Do not claim universal type-safe synchronization, autonomous DDL, backup or rollback; add backend-specific plans and approval gates before expanding it.
Strictly post-commit hooks/search/cache invalidationImplemented / bounded process-local contract; durable use is explicitafter_commit, generated committed observers, Redis table-cache invalidation/pub-sub and Scout projections run after direct generated or Orm::transaction commit; rollback drops them and PostCommit distinguishes a failed effect after durable persistence. A raw caller-owned SQLx transaction cannot reveal its later commit decision. These hooks are intentionally not converted to durable events without an application-selected schema/payload/policy.
Durable relational transactional outboxImplemented / bounded opt-in primitiveOutbox::enqueue refuses independent commit and writes an idempotent event with the domain transaction; OutboxMigration supplies reviewed schema registration. PostgreSQL, MySQL, MariaDB and SQLite live matrices cover commit, rollback, duplicate reuse, claim and ACK. SQLite regressions additionally cover conflicting key reuse, stream isolation, exact/unexpired claim tokens, concurrent claim exclusion, lease recovery, bounded retry and dead-letter. Delivery is at least once: consumers must be idempotent, and automatic observer/webhook dispatch, tenant authorization, cleanup, cross-store transactions and distributed operations remain application boundaries.
Native external search and QdrantImplemented / bounded per adapterscout-http supplies Meilisearch, Elasticsearch and Algolia projection/search adapters; only Meilisearch has a live service lifecycle. Separately, qdrant supplies bounded dense-vector create/upsert/delete/cosine query operations, deterministic fallback, authenticated protocol fixtures and a digest-pinned live lifecycle. Generated Scout projection durability, hosted-provider certification, arbitrary Qdrant filters/vectors and ANN tuning remain explicit.
Native Redis data structuresImplemented / bounded Hash, Set and Sorted Set contractredis supplies an immutable application namespace, validated inputs, bounded reads, TLS-required remote configuration, redacted ACL credentials and deterministic fallback. A pinned live lifecycle proves hash increments, set membership/scan, sorted ranking, exact structure deletion and namespace isolation. Lists, Streams, Pub/Sub durability, cluster/failover, eviction and tenant authorization are not implied.
Typed PostgreSQL pgvector queriesImplemented / bounded feature-gated contractpgvector plus strict-postgres exposes SQLx-compatible Vector and parameterized L2/cosine/inner-product helpers. A digest-pinned live matrix installs the extension, inserts typed values and executes bounded similarity queries. Embedding policy, ANN index tuning, RAG orchestration, authorization and production topology remain application/deployment work.
Polyglot MongoDB, DuckDB, Turso and SurrealDB boundaryImplemented / bounded per adapter, with application-operated document recoveryKeep document, OLAP, SQL and graph capabilities separate, with bounds and deterministic offline modes. MongoDB, SurrealDB and the deterministic store now retain IDs through DocumentInventory; an AES-256-GCM envelope binds key ID/application/collection, caps documents/bytes, compares two source observations, resumes only into an exact subset and verifies the final inventory. The live matrix crosses MongoDB→SurrealDB→MongoDB. Applications must quiesce writers and own key/snapshot durability; this is not online snapshot isolation, a universal Active Record facade, managed backup, replication or a cross-store transaction.
Transparent edge replication and latency guaranteesNot implementedSQLx read replicas can be configured, but Turso synchronization, health/failover and consistency remain vendor/deployment concerns. Benchmark measured topology; never promise “1 ms” universally.
Historical ORM roadmap completenessFully classified, not fully implementedAll 45 unique former [x] claims are individually classified in v12.md: 28 bounded integral contracts/evidence items and 17 partial. The comparative benchmark is delivered evidence whose first result refuted, rather than confirmed, the historic performance wording. Continue from that evidence instead of restoring duplicate/absolute roadmap text.

Capital, billing, and fiscal vision

Capability or former claimCurrent statusRecommendation and reason
Deterministic offline DPS/NFS-e previewImplemented (mock only)It is clearly typed as unauthorized and remains useful for UI/workflow development.
Local NFS-e 1.01 preparationImplemented / boundedChecksum-pinned official production/restricted artifacts, strict ordinary-service DPS construction, and closed-catalog validation. After hash verification, one known production DPS-series regex receives an exact documented .NET-anchor compatibility normalization; no other source rewrite is allowed. Protected PKCS#12 parsing, enveloped inclusive-C14N/RSA-SHA256 XMLDSig with local independent verification, signed-tpAmb binding, deterministic GZip/Base64 request JSON, strict signed-authorization and structured-rejection parsing, and bounded rustls mTLS client construction complete the local boundary. A single-active-writer HMAC-chained journal synchronizes idempotent prepared/terminal digests, recovers minimized pending descriptors and supports external exact-tip checkpoints without retaining XML, access keys or response messages. This is cryptographic/schema/protocol/local-recovery preparation, not tax authorization or certificate trust.
Live NFS-e Nacional issuance with authoritative request/outbox storage, restricted-environment evidence, and SEFIN homologationPartial; transmission disabled (extraordinary and worth implementing conditionally)The local command journal is implemented but does not transport, retry, provide a distributed lock or store the actual request. Live issuance still requires deployment-owned outbox/reconciliation, an authorized official test environment, real A1 certificate/emitter and ICP-Brasil lifecycle checks, municipality/contributor parameters, retained official protocol fixtures, independent review and legal/protocol maintenance. Do not rush it into a generic payment adapter or infer authorization from local validity.
Alipay RSA2 signing and verificationNot implemented (worth implementing only with real demand/partner access)Use audited RSA primitives, official canonical parameter rules, replay tests, and provider sandbox contract tests. HMAC fixtures must never masquerade as RSA2.
Uniform live support across all advertised gatewaysPartial (worth a capability matrix)Keep the adapters, but publish method-by-method support and provider contract tests. An adapter name must not imply checkout, subscriptions, payouts, portal, tax, and webhook support all exist.
Shared outbound gateway failure policyImplemented / boundedReviewed live methods use finite connect/request timeouts, disabled redirects and ambient proxies, one-MiB JSON decoding and credential-free HTTPS checkout URL validation. Redacted typed evidence distinguishes permanent, transient and rate-limited failures without automatic mutation retries. Durable provider-forwarded idempotency, backoff/attempt policy, reconciliation, explicit proxy configuration and live acceptance remain application or future adapter work.
Axum and Actix signed-webhook middlewareImplemented / bounded, with opt-in shared SQL replayBoth adapters bound the raw body, call one provider-aware verifier before dispatch, restore the exact body, insert a normalized event and enforce the configured replay store. webhook-sql adds immutable bounded shared claims over SQLite/PostgreSQL/MySQL/MariaDB, with restart, contention, drift, capacity and four-protocol live evidence; a stable event claim can share the caller’s relational domain transaction. Middleware payload admission happens before dispatch and therefore is not exactly-once processing. Cross-system effects still require outbox/idempotent-consumer/reconciliation contracts.
Subscription handle and grace-period valueImplemented / boundedSubscriptionHandle<P> validates and redacts its identifier and delegates cancel/pause through an explicit statically dispatched provider (with a global-provider compatibility path). GracePeriod is a fallible half-open Unix-time window capped at 366 days; Billable and its derive can carry a complete optional pair. The application still owns authoritative loading, authorization, trusted time, persistence, entitlement checks, scheduling/retry and provider-specific live behavior.
Immediate Billable chargeImplemented / bounded for reviewed Stripe Payment Intentscharge_with/charge require integer minor units, normalized currency, explicit provider customer and tokenized payment-method IDs, validated model e-mail and a bounded idempotency key. Stripe forwards that key and confirms off-session; its response must match amount/currency and report succeeded or processing. Exact mock retries are deterministic but carry the distinct non-success Mock status; request/receipt debug views redact identity. Other providers return UnsupportedOperation; mandate/SCA setup, durable idempotency, webhook reconciliation, entitlement changes and live sandbox evidence remain application/deployment work.
Stripe/Lemon Squeezy metered usageImplemented / bounded provider-specific APIMeteredBillingProvider uses StripeMeterEvent for the current Stripe Meter Events form contract and LemonSqueezyUsageRecord for the current Lemon Squeezy JSON:API relationship/action contract. Both validate positive bounded usage, cap responses, bind provider echoes, redact identities and supply deterministic non-live mocks. Stripe gets a rolling-window identifier; Lemon requires the application to claim its event key in a durable outbox because the reviewed provider request has no equivalent key. Live-account acceptance, retries, aggregation configuration, reconciliation and entitlements remain external.
Coupon application and relative trial extensionImplemented / bounded provider-specific APICouponCode is bounded/redacted. Stripe’s current expanded-discount update is bound to subscription and coupon; Lemon Squeezy and unreviewed live adapters fail explicitly because its discount code belongs to checkout. TrialExtension makes extend_trial(15) a 1–730-day operation; Stripe and Lemon Squeezy protocol fixtures bind the returned subscription/expiration, with an explicit-clock retry path. The host owns authorization, stable command time, serialization, webhook reconciliation, billing-cycle policy and live-account evidence.
Shared team/workspace resource quotasImplemented / bounded, with opt-in durable SQL storeBillingSubject::from_tenant binds shared accounting to trusted tenant context and Billable::quota_request derives the limit from the subscription owner. QuotaGate reserves before executing, suppresses exact replay and compensates ordinary operation failure. quota-sql uses a unique event claim plus conditional counter update; SQLite and live PostgreSQL/MySQL/MariaDB concurrency contracts stop exactly at the shared limit. Applications own membership/authentication, tier persistence and reconciliation, migrations, stale-reservation operations and non-relational adapters. Arbitrary domain writes are atomic only when performed through the same caller-owned SQL transaction.
Payment-bound invoice PDF and mail deliveryPartial historical automation; implemented bounded bridgeinvoice-pdf validates the compatibility invoice into exact minor units and renders bounded paginated PDF with embedded WinAnsi or a caller-supplied checked font. PaidInvoice requires final Succeeded evidence matching recipient, amount and currency; Mail’s opt-in bridge attaches the PDF, runs pre-flight and sends through the facade or a static driver. Its stable non-secret key is intended for an application-owned outbox. No webhook is inferred, no key is claimed transactionally, provider acceptance/attachment parity vary, and exactly-once delivery is not promised.
Durable cross-instance webhook idempotency/replay storeImplemented / bounded relational contractThe opt-in webhook-sql store persists bounded provider-scoped payload digests or stable event keys on SQLite, PostgreSQL, MySQL and MariaDB with immutable capacity/TTL, database-time serialized claims, expiry, restart, contention and fail-closed drift/storage/full-state evidence. Middleware admission is not exactly-once; atomic domain mutation requires the caller-transaction event-key path, and cross-system effects still require an outbox and reconciliation.
Static fee, settlement, and tax promises in framework docsDo not promiseProvider terms and regional availability change. Link to official current terms and document only what the adapter itself implements.
“Zero-cost invoicing”Do not promiseRemoving an intermediary fee does not remove certificate, accounting, infrastructure, support, or compliance cost. Preserve cost transparency without advertising zero total cost.

IoT, edge, and cryptography vision

Capability or former claimCurrent statusRecommendation and reason
no_std telemetry/frame helpersImplemented (keep focused)This is a credible small foundation. Preserve the lightweight, transport-neutral core.
Ed25519-signed OTA manifest, firmware hash, and monotonic verification gateImplemented (foundation only)Keep negative vectors and anti-rollback tests. The gate is not a firmware installer.
Persistent anti-rollback counter, download, flash, bootloader slot selection, rollback, and commitPartial: store contract implemented; platform path absentThe no_std adapter requires exact monotonic compare-and-set and has restart/retry/conflict tests. A concrete store, download/flash path, bootloader coordination and power-loss/fault-injection evidence remain essential before calling OTA end-to-end.
MQTT 5, CoAP, and Sparkplug B transportPacket helpers implemented; transport not implemented (worth a separate transport crate)The no_std crate now encodes one bounded MQTT 5 PUBLISH packet and RFC 7252 base requests with protocol/boundary vectors. Connections, TLS/DTLS, acknowledgement/retransmission state, broker/LwM2M/Sparkplug semantics and interoperability remain absent; network runtimes should not bloat the frame crate.
Hardware HSM/secure-element backendsNot implemented; simulators are experimental (worth adapter traits, later)Implement only against named hardware/PKCS#11 interfaces with device tests. Never create home-grown “HSM-like” hashing and call it secure.
NIST ML-KEM/post-quantum encryptionNot implemented; simulators are experimental (worth later, not home-grown)Adopt an audited implementation only when a concrete protocol and threat model justify it. A generic “quantum-safe” badge is not worthwhile.
CAN/J1939, LoRaWAN, GPIO/I2C hardware integrationNot implemented or helper-only (worth separate hardware packages)Preserve the idea, but require target boards, interoperability fixtures, and maintainers for each protocol.
Embassy async executor integrationNot implemented (worth implementing after the transport split)Valuable for embedded ergonomics once the no_std ownership and timer/network abstractions are stable.
Actual QEMU/hardware-in-the-loop CINot implemented (worth implementing with real runtime code)Merely compiling a target is not QEMU testing. Add it when there is boot/flash behavior to execute and assert.
Aerospace/autonomous-vehicle/defense framework claimsNot implemented (do not place in the web-framework Core)The idea is ambitious, but safety-critical systems need independent standards, certification, hardware, and governance. Consider a separate future project only after the IoT foundation is mature.

Connect, real-time, queues, storage, and data

Capability or former claimCurrent statusRecommendation and reason
OAuth2/OIDC/social-login providersImplemented (keep as Connect’s current identity)Continue issuer, redirect, JWKS rotation, offline-fixture, and negative-token contract testing.
Signed local OIDC identity-provider fixtureImplemented / bounded for explicit Axum loopback testsA validated concrete router binds one registered client and callback, keeps authorization codes expiring/one-shot in a 64-record process-local store, verifies optional S256 PKCE, carries nonce into a deterministic-fixture EdDSA ID token, publishes discovery/JWKS and accepts only issued unexpired bearer tokens at userinfo. A real loopback test drives the production OidcProvider through discovery, authorization, token exchange, JWKS verification and replay rejection. The private signing material and credentials are intentionally public/predictable fixtures. It is not safe to expose, has no interactive login/consent, refresh/device/federation lifecycle, durable state, key rotation or OIDC certification.
Server-bound OAuth/OIDC callback challengeImplemented / bounded for Axum + tower-sessionsbegin_oauth_session stores state + PKCE and begin_oidc_session additionally stores nonce for ten minutes. AuthSession removes and immediately saves the one active challenge before constant-time state validation and returns borrowed ExchangeParams; sequential replay, expiry, mismatch, replacement and redacted-debug negatives are tested. The host still owns a durable session store, secure cookie/TLS configuration, account linking, recovery and live-provider conformance. One active challenge per browser session deliberately means a second login invalidates the first. The generic store API is not a distributed compare-and-delete, so simultaneous already-loaded callbacks require idempotent effects or an application atomic-challenge adapter.
Credential-safe normalized OAuth profileImplemented / boundedConnectUser::universal_profile() emits only normalized identity fields. Serde on ConnectUser omits access and refresh tokens; applications must use a dedicated encrypted store for credential lifecycle rather than serializing the whole response as a session.
Automatic OAuth token refreshImplemented / bounded process-local coordinator plus optional shared-local encrypted stateRefreshableTokenState validates and redacts access/refresh credentials, lifetime, trusted receipt time and provider user identity. AutoRefreshingSession<P> uses static dispatch, a bounded early-refresh window and one async mutex to prevent overlapping refresh calls; callers waiting behind a successful refresh reuse it. The coordinator retains an unrotated refresh token, adopts a valid rotation, binds the returned user and swaps state only after validation. EncryptedTokenSnapshot supplies a bounded versioned AES-256-GCM envelope whose tag binds an explicit rotation key ID, provider and trusted local-account identity. The opt-in SQLite store persists only a pseudonymous binding digest, generation/key metadata and ciphertext under an immutable quota; serialized transactions and exact-successor CAS reject stale local writers, with restart/contention/corruption evidence. The application still owns the remote-provider lease and losing-call reconciliation, key/directory/backup operations, authorization, multi-host replication, retry/backoff, revocation and reauthentication. Providers without refresh fail explicitly.
Explicit corporate proxy for OAuth/OIDCImplemented / bounded HTTP(S) transportReqwestClient accepts an authority-only explicit proxy or separately supplied Basic credentials, disables ambient system-proxy lookup and requires HTTPS for authenticated non-loopback endpoints. A loopback protocol fixture proves routing and proxy authorization. PAC/WPAD, SOCKS, proxy mTLS and production network certification remain deployment/roadmap work.
Brokered messaging / old Connect Phase 9Partial bounded foundation in rullst-messagingThe separate crate now provides a versioned envelope, bounded idempotent publication, groups, leases, retry/DLQ, explicit purge, deterministic in-memory broker, canonical bounded envelope codec, allowlisted W3C trace context and fixed-schema local SQLite. SQLite can immutably select plaintext or AES-256-GCM header/payload protection with row-bound AAD and bounded rotation; raw-state/restart/tamper/row-swap/two-instance tests are executable. An opt-in static ORM relay publishes committed outbox claims with their stable event key and proves exact replay across the publish-before-ACK crash window. Routing/idempotency metadata remains visible, delivery remains at least once, neither relay nor codec creates an atomic remote transport, and replication plus RabbitMQ, Kafka, Redis Streams, NATS/JetStream, SQS/SNS, Google Pub/Sub and Pulsar adapters are not implemented.
WebSocket pub/sub and SSEImplemented in Core, not Connect (do not duplicate)Re-export through a coherent facade if desired; one runtime implementation is better than competing copies.
Bounded Memory/SQLite/Redis queues with recoverable leasesImplemented / boundedSQLite and Redis additionally persist dispatch_at timestamps for at most 366 days and never claim them early; Redis server-time promotion has a digest-pinned live contract. Execution is poll-dependent and at-least-once. Continue dead-letter observability, graceful shutdown, and multi-worker fault tests.
NATS JetStream, SQS/SNS, and GCP Pub/SubNot implemented (worth demand-driven optional adapters)Define one queue conformance suite first; add providers only when each can pass the same delivery/lease/idempotency semantics.
Authenticated tenant-local storage namespaceImplemented / bounded in CoreTenantStorage is constructed from a validated TenantContext, prefixes keys with an immutable canonical tenant root and reuses traversal/symlink protections. An exact local test stores the same logical key under two authenticated tenants without interference. Academy attachment/media integration, remote bucket policy and cross-backend conformance remain open.
Authenticated tenant cache namespaceImplemented / bounded in CoreTenantCache is constructed from a validated TenantContext, validates logical keys, applies an immutable namespace for memory or Redis-backed Core caches and deliberately exposes no global flush. Exact same-key non-interference is tested locally. Academy cache integration, Redis cluster/failover and application-wide conformance remain open.
Authenticated tenant realtime namespaceImplemented / bounded in CoreTenantRealtime and TenantPresence bind shared in-process broadcast/presence managers to validated TenantContext, validate logical channel/event/identity names, cap payloads at 64 KiB and prove that the same room name in two tenants does not interfere. The Academy notification scaffold composes an owner/admin-authorized tenant/user subscription and best-effort post-commit projection. Broader room authorization, distributed transport/liveness and other application integrations remain application/roadmap work.
S3 and Cloudflare R2 storageNot implemented (worth optional remote-storage crates)Useful and commercially relevant. Use official/signed clients, path/key constraints, multipart and retry semantics, and deterministic mocks; do not put fake success in the local facade.
Upload admission and quarantine contractImplemented / bounded, storage-agnosticCore validates bounded in-memory bytes against tenant/name/size/type policy, recognized signatures, MIME/extensions and active-text denial, produces a randomized tenant quarantine key plus SHA-256, and releases only after a clean scanner verdict. The bundled scanner is deterministic and mock-only. Multipart streaming, sandboxed deep parsing/transcoding, S3/R2 movement, archives and a production malware adapter remain open.
Image resize pipelineNot implemented (worth an optional media crate, not Core)Media decoding expands attack surface and binary size. Isolate it with strict size/pixel limits and fuzzing.
Transparent SQLite/Turso/database replicationNot implemented (do not implement generically in Core)Integrate vendor-specific replication clients or sidecars. A generic timer that prints “syncing” cannot provide consistency semantics.
Immutable/zero-copy distributed ledger engineNot implemented (interesting, but lower priority)First define the exact consistency, persistence, recovery, and audit use case. The current HMAC audit chain is not a distributed immutable ledger.

Security, authentication, Studio, and Nexus

Capability or former claimCurrent statusRecommendation and reason
Versioned AES-256-GCM field encryptionImplemented (keep)Keep envelope versioning, AAD, random nonces, key rotation, and round-trip/negative tests. Key custody remains external.
Canonical browser security baselineImplemented / bounded in Coreapply_security_baseline is the same composition used by Server: it installs the exact application config outside secure headers/CSP nonce, explicit-origin CORS with separate credential opt-in, WAF, double-submit CSRF and optional PII masking in a stable order. Configuration rejects wildcard/path/query/credential/duplicate CORS origins and malformed CSP/SameSite state. In-process HTTP tests prove matching renderer/header nonce, secure CSRF cookie and write denial/acceptance, preflight allowlist and no grant for a foreign origin. Tune policy per application and test the final browser/proxy/TLS deployment; never promise a universal third-party A+ score.
“OWASP A+ guaranteed”Do not promiseA scanner grade depends on the final page, proxy, cookies, TLS, and deployment. The worthwhile goal is a strict tested baseline, not a badge guarantee.
Bounded body-aware WAF/RASP and DLPImplemented (defense-in-depth)Keep content-type, streaming, compression, overflow, and header-consistency tests. Never position heuristics as a replacement for parsers, binds, validation, or authorization.
Distributed rate limitingImplemented foundation / external proof pendingrullst-security/redis-rate-limit uses one atomic Redis script, namespace validation, hashed client keys, TTLs and typed fail-closed errors. Empty/mock_* configuration is explicitly process-local and require_distributed() rejects it. Real Redis cross-instance, eviction/failover and trusted-proxy deployment tests remain release work; the legacy no-argument selector stays Unsupported.
Deterministic threat assessment and proof of workImplemented / bounded locally; autonomous AI claim remains partialTransparent thresholds classify three caller-supplied aggregate patterns and can issue an authenticated, subject-bound, expiring challenge with bounded difficulty/capacity and atomic one-shot verification. Rullst does not collect the aggregates, attribute botnets, block autonomously or share replay state across processes; accessibility, identity, distributed enforcement and DDoS architecture remain host concerns.
Route-scoped JSON Schema/OpenAPI body enforcementImplemented / boundedJsonSchemaPolicy compiles one JSON Schema 2020-12 document or selected OpenAPI 3.1 component, caps schema bytes/nodes/depth, rejects external references, disables filesystem/network retrieval and uses linear-time regexes. The Axum layer composes exact JSON transport checks and returns a value-free 422 on mismatch. It does not infer schemas from arbitrary routes or replace auth, ownership, domain validation, or query/header/form contracts.
Durable tamper-evident audit storagePartial (worth implementing as a sink interface)Canonical HMAC sequencing exists. Durable append-only storage, key protection, retention, and independent verification remain application/integration work.
Versioned local security-event envelope and spoolImplemented and bounded (v1/local)LiveSecurityEvent has a frozen six-field v1 contract, a packaged JSON Schema, normalized identifiers/IPs/timestamps, a 2 KiB UTF-8 detail limit, and CEF field escaping. DurableSiemSpool preserves the compatible unsigned SHA-256 format. The opt-in AuthenticatedSiemSpool adds a domain-separated HMAC-SHA256 sequence/predecessor chain, one active plus seven historical named zeroized keys, exact byte/record quotas, restart validation and fail-closed forgery, wrong/missing-key, interior deletion/reordering, symlink, external-change and uncertain-durability behavior. A separately trusted checkpoint is required to detect valid-tail rollback; trusted directory/key custody, permissions, rotation retirement, retention, backup, multi-process exclusion, delivery, retry, acknowledgement, dead-letter handling and external SIEM adapters remain application/operations work.
Full normative WebAuthn serverPartial; durable local device lifecycle implementedThe implementation checks the documented ES256/none-attestation ceremony invariants. The opt-in SQLite store adds bounded credential inventory, rename/revocation, restart persistence and atomic counter CAS shared by local processes. Challenge state remains process-local, and an audited library or a full conformance suite is preferable to indefinite custom protocol ownership.
TOTP recovery codesImplemented cryptographic foundation / workflow pendingSecurity generates subject-bound 80-bit codes, returns plaintext only at enrollment, serializes salted HMAC verifiers, compares in constant time and removes a consumed verifier. The application must encrypt the TOTP secret, persist verifier consumption atomically, rate-limit attempts and own enrollment/recovery UX and audit.
TOTP enrollment and Login JailImplemented / bounded in-processMFA secrets contain 160 bits from the OS RNG, verification is constant-time and enrollment can emit a real bounded SVG QR. LoginGuard::record_login_failure_and_wait both records and awaits the progressive tarpit. The host must protect/encrypt the secret and persist/distribute abuse state where required.
CLI security evidence toolsImplemented with bounded analysesSBOM output parses Cargo metadata into CycloneDX 1.5 fields with a valid UUID; the doctor parses the real Rust version against MSRV; requested Geiger/SBOM/network checks now fail on findings or incomplete execution, and hooks run all-feature Clippy plus unsafe/IDOR source checks. These are evidence producers, not certification, attestation or static-analysis completeness proofs.
First-class application JWT service in rullst-authImplemented / bounded shared local optionThe opt-in jwt feature centralizes issuer/audience, required versioned claims, bounded TTL/scopes, strong HS256 keys, kid rotation and sync/async revocation contracts. Production verification rejects the bundled process-local store. The sqlite profile adds a quota-bound shared local adapter with serialized JTI/session-version mutations, expiry pruning, configuration-drift checks and restart/two-instance evidence. Multi-host replication, database encryption/backup/availability and refresh-token workflows remain deployment/application work.
Nexus open-by-default adminRemoved; hardened Nexus is implementedKeep fail-closed construction, TLS boundary, role/field policy, ownership integration, and rate limits. Production readiness still depends on the host application identity model.
Nexus registered-model CRUD and batch actionsImplemented / bounded#[derive(Nexus)] generates tested metadata for named-field models; primitive widgets are inferred and enum/textarea semantics are explicit. try_build() bounds and validates model, field, primary-key, enum, relation and optional text-tenant metadata. Mutation forms accept at most 256 pairs, bound short/long values to 4/64 KiB, reject unknown/protected fields and parameter pollution, normalize Boolean controls and validate registered enum, JSON, number, date, e-mail and HTTP(S) URL semantics before bound SQL. Sort/search identifiers are registry-allowlisted, batch delete/deactivate is capped at 1,000 selected IDs, and metadata is escaped at HTML boundaries. Tenant-scoped models bind every built-in read/mutation/batch to a trusted Core TenantContext, inject it on create and deny missing context; models without the metadata remain global. with_required_audit couples minimized committed-mutation evidence to the same transaction and fails closed when its fixed schema is unavailable. Deactivation is available only for a writable Boolean is_active/active; external enum reflection, multiline inference, host identity/membership, within-tenant ownership, global/custom-route authorization, database schema/type compatibility and privileges remain host contracts. The same-database audit table is mutable, not append-only/tamper-evident, and host retention/backup/replication/immutable export remain external.
Autonomous AI admin that can mutate production dataNot implemented as a safe contract (do not enable by default)A read-only, explainable assistant is worthwhile. Mutations need explicit human approval, scoped capabilities, audit records, dry-run previews, and rollback.
Studio with real telemetryImplemented with bounded authenticated ingestion and unavailable statesLinux and Windows expose delta-based process CPU alongside RSS, Tokio and local span probes; KPI cards poll the local JSON source. Each separately mounted push-only router binds one producer name/key, accepts only bounded attribute-free v1 spans after exact-body/source HMAC, clock-window and atomic nonce-replay checks, then stores them in bounded process memory. The viewer derives 100 ms slow-label and three-repeat possible-N+1 heuristics without SQL text or bindings. This is not OTLP, durable trace storage, remote Studio access or proof of a defect. The local capability still verifies peer plus local Host, and same-origin Origin on mutations. Queue/revenue/security/AI panels expose only supplied state; unsupported operations, disconnected transports and the absent standalone migration registry fail explicitly. Keep the viewer local by default and never fabricate values for missing sources.
Studio data/API/queue/cache/config toolsImplemented with bounded partial surfacesThe SQLx browser reads/filters and, behind the unforgeable verified-local request marker, mutates one primitive non-key value or deletes one complete-PK-selected row with exact confirmation. Tables/columns come from inspected allowlisted schema, values are typed/bound, bodies are capped, exactly one row must change, and live SQLite/PostgreSQL/MySQL/MariaDB matrices cover the contract. Backend-specific types, tenant/RBAC, audit, rollback and shared production access remain outside it. Swagger requires a supplied OpenApi; SSE records method/URI/status/latency without secret-bearing bodies/headers. SQLite removes successful jobs by default or explicitly retains 1–100,000 for the real queue snapshot with atomic pruning and purge; retained payload access/policy belongs to the host, and Redis/custom queue inspection remains capability-specific. An explicitly supplied memory or Redis cache returns metadata only; Studio limits display to 100 opaque HMAC identifiers with size/TTL and permits only individual verified-local invalidation. Values, exact keys and bulk flush remain absent; custom cache drivers fail explicitly until they implement inspection. A successful Studio flag toggle immediately invalidates warm DbFeatureDriver caches in the same process through a constant-size epoch; other processes/direct writers remain TTL-bound without application pub/sub. Relational ER metadata uses parameterized lookups and normalized Mermaid identifiers. Environment values are deny-by-default redacted and typed config omits URLs, paths and secrets.
Studio automatically stripped from every release with zero overheadDo not promiseFeature selection and route mounting determine inclusion. Explicit compile features are clearer than relying on a universal debug/release assumption.
Threat Radar with external reputation and verified audit feedsPartial (worth pluggable connectors)Local counters exist. External intelligence, durable audit verification, and SIEM delivery should appear only when a source is connected and healthy.
“100% OWASP coverage”, “tamper-proof”, or “zero-leak DLP”Do not promiseThese absolutes are not provable framework properties. Preserve the controls and publish exact threat-model/test scope.
Automatic SOC 2/ISO/FedRAMP certificationDo not implement as a PASS generatorThe evidence exporter is worthwhile; certification evaluates the whole organization and deployment. Emit PASS, FAIL, SKIPPED, or NOT_EVALUATED from real checks only.

AI and mail

Capability or former claimCurrent statusRecommendation and reason
DeepSeek providerImplementedKeep it in the same provider contract and offline mock suite as the other cloud adapters.
Mandatory prompt-injection/PII guardrail pipelineImplemented in the high-level client and built-in provider transportsThe versioned deterministic offline corpus fixes implemented injection/jailbreak/PII regressions across built-in transports. Custom low-level providers remain an explicit extension boundary; heuristics and this corpus cannot prove a prompt safe. AdaptiveAiEvaluator<P> now supplies bounded multi-turn evaluation orchestration, but operators still own the domain corpus and exact live-model execution/review.
Adaptive AI evaluationImplemented / bounded runnerA static-dispatch strategy can use one bounded response to choose its next prompt under turn, prompt/response, deadline and cancellation limits. Results distinguish pass, fail and inconclusive and serialize only low-cardinality metadata without raw prompts/responses/provider errors. Deterministic fixtures prove orchestration, not the safety or quality of a live model; the caller-supplied subject label and scenario assertions require operator governance.
Tenant-bound RAG orchestrationImplemented / boundedRagPipeline::answer composes guarded embedding, a static application retriever, Unicode-safe context budgets, guarded generation, source metadata and mandatory secret-minimized audit in one call. It requires trusted TenantContext, rejects cross-tenant tags and unsafe/empty context, and includes a bounded tenant-partitioned process-local cosine retriever. DurableRagAuditTrail adds a synchronized versioned local file with quotas and restart/corruption checks. The separate AuditDeliveryClient can export a caller-minimized event through a bounded HMAC-authenticated envelope with stable event identity, transient retry, cancellation and a bound acknowledgement. Authoritative datastore authorization, durable/external vector adapters, ingestion/deletion, output policy, receiver/outbox operation and live-model evaluation remain application/deployment work.
Authenticated AI audit exportImplemented / bounded transportCloud delivery requires HTTPS and literal-loopback HTTP(S) is development-only. HMAC-SHA256 authenticates exact JSON bytes plus key/timestamp metadata; event and acknowledgement sizes, retry attempts and cancellation are bounded, and the ACK must repeat the stable event ID. The caller owns event minimization; the receiver owns freshness/signature verification, deduplication, authorization, persistence, retention, key rotation and SIEM availability.
Offline mocks for chat, vision, embeddings, and mail transportsImplementedKeep mocks deterministic and selected only by explicit empty/mock_* credentials; live endpoints must never silently fall back.
Reusable and generated durable chat memoryImplemented / bounded choicesStatefulChat<M> and ChatMemory now live in rullst-ai; the bounded offline store is always available and opt-in sql-memory atomically persists tenant-bound exchanges with revision CAS on SQLite/PostgreSQL/MySQL/MariaDB. Live matrices prove the four protocols. cargo rullst make:chat-session remains an application-owned SQLx/Turso-primary model/migration path. Raw-text encryption, ownership inside a tenant, retention/erasure, provider audit, backups and conflict UX remain host contracts; CAS rejects rather than retries a billable provider request.
Native JSON Schema structured output on every LLMPartial (worth capability-typed support)Separate parseable JSON from provider-enforced schema. Return UnsupportedCapability when native enforcement is unavailable.
Machine-readable AI provider capabilitiesImplemented / boundedAiProvider::capabilities() and AiClient::capabilities() report transport support for text, chat, embeddings, vision, JSON/schema, streaming, tools, timeout, retry, and explicit cancellation. The public matrix states model-dependent and unsupported boundaries.
Guarded local AI tool dispatchImplemented / boundedToolRegistry::execute requires an exact allowlist, principal authorization, closed bounded JSON, a call budget and audit sink. Destructive/financial approvals are one-use and payload-bound. DurableToolAuditTrail adds bounded synchronized local persistence with restart/corruption checks; it is not a multi-process writer or authenticated external sink. Provider-native tool calling, approver authentication, domain authorization, rotation/retention and distributed delivery remain application/roadmap boundaries.
Strict AI egress fetcherImplemented / opt-in bounded transportEgressPolicy::strict() denies all hosts until an exact allowlist is configured. EgressFetcher validates HTTPS URL/port and every DNS answer, pins all accepted addresses in a proxy-free client, verifies the connected peer, revalidates manual redirects and bounds deadline/declared and streamed bytes. It does not wrap arbitrary application/provider clients automatically; tenant-aware destination authorization, response schema/content handling and a deterministic successful live-origin contract remain integration work.
OpenAI-compatible local/cloud LLM endpointImplemented / bounded, capability-declaredOpenAiCompatibleProvider sends the named chat/embedding/vision/response-format shapes. It defaults to chat-only; optional paths are explicitly declared for the configured model. Unauthenticated HTTP is restricted to a literal loopback IP, cloud requires HTTPS/Bearer, offline mode is explicit, responses/images are bounded, and redirects/environment proxies are disabled. This is not automatic model discovery or arbitrary-HTTP compatibility.
“Any local LLM over any HTTP API”Do not promiseOllama and the bounded OpenAI-compatible adapter cover two explicit protocols. Arbitrary APIs still have incompatible authentication, streaming, schema, tool, and error semantics; applications implement AiProvider for those contracts.
Autonomous AI self-healing/DevOps changesPartial recommendation tooling (do not auto-apply by default)Diagnostics and patches can be valuable, but infrastructure/code mutation needs review, capability scopes, preview, audit, and rollback.
Tenant-aware secure mail, typed failover, durable scheduling and expiring tracking tokensImplemented / boundedThe mandatory pipeline and purpose-bound TTL tokens are real. TenantMailResolver accepts the trusted Core TenantContext directly, validates registration, fails closed on unavailable registry state, and proves context isolation with separate drivers. FailoverDriver distinguishes permanent, transient and rate-limited outcomes, sends only eligible failures to another provider, bounds/redacts HTTP error details and emits structured tracing; its state remains process-local. SQLite/Redis queues preserve tenant envelopes and bounded due times without early claims; unsupported real direct transports fail closed, while offline fixtures may retain schedule metadata. Credentials still live in an application-configured in-process registry; encrypted persistence, rotation and cross-process distribution are not implied. Token payloads are HMAC-authenticated but base64-readable rather than encrypted.
Attachment inspection, recipient suppression and delivery observationsImplemented / bounded opt-in controlsAttachmentInspectionGuard fails closed before transport using a static scanner contract; its strict local implementation recognizes bounded safe types and rejects executable magic, spoofing, active PDF/SVG, secrets and unsafe links. SuppressionGuard checks process-local or sqlite shared-local manual/bounce/complaint state; SQLite binds verified provider/event payloads, preserves monotonic reasons and enforces exact quotas across restart/two instances. ObservedMailDriver emits only low-cardinality terminal metadata through a non-failing sink. Provider-specific webhook authentication, antivirus/sandbox/CDR, encrypted or multi-host suppression state, external telemetry and alert operations remain host/deployment work.
Direct AWS SES v2 deliveryImplemented / bounded protocol contractBehind aws-ses, the official AWS SES v2 SDK supplies SigV4 and supports temporary or caller-owned rotating credentials, HTML/text, RFC 8058 and attachments/CID. The adapter enforces field/encoded-size bounds and response binding, with a signed regional loopback contract. Offline and explicit bearer-proxy modes remain visibly distinct. Live-account acceptance, verified identities/domains, sandbox exit, IAM, quotas, reputation and inbox delivery remain external.
Safe generated mailablesImplemented / bounded scaffoldThe five make:mail variants plus make:mail-invoice and make:mail-dunning validate names, reject traversal/collisions, enable the exact facade features, register modules and escape dynamic HTML. The fiscal constructor consumes typed FiscalResponse provenance and keeps OfflineMock unmistakably unauthorized; dunning exposes D+1/D+3/D+7 without inferring billing state. A materialized project passes Clippy and adversarial rendering/link/provenance contracts; delivery credentials, official authorization, scheduling, entitlement mutation and billing policy remain application/external boundaries.
Mailgun, Brevo, MailerSend, Plunk, and Scaleway transportsNot implemented (worth demand-driven adapters)Add only with maintainers and the shared offline/live transport conformance suite; provider count alone is not product quality.
“Air-gapped/zero-leak AI”Do not promise automaticallyLocal endpoints can avoid cloud LLM calls, but host networking, logging, model runtime, and telemetry determine the real data boundary.

CLI, generated applications, and release engineering

Capability or former claimCurrent statusRecommendation and reason
Stable blueprint IDs and corrected Nix/Buildah/Island/Resource flagsImplemented in current hardeningKeep snapshot tests because generated CLI identifiers are public compatibility surface.
Omni desktop/mobile packaging shellImplemented / bounded experimental web shell with hosted compile matrix; offline foundation opt-inmake:omni deterministically selects platforms, validates backend URL/product/version/application-owned mobile identity, pins Tauri, emits real icon assets and fails on requested setup errors. Its local page has a restrictive origin-specific CSP, exposes no remote IPC and a native callback rejects navigation outside the exact backend origin. The local managed desktop child fails closed on a pre-used port, early exit and timeout. Desktop Linux/macOS/Windows, Android aarch64 APK and iOS simulator gates passed on 755fbd61933bed04369e0eb5de50b11275db5e3d. rullst.client v1 supplies bounded typed envelopes/version negotiation shared with Wasm, without client authority. The native offline-sync feature adds bounded account state, idempotent FIFO proposals, explicit conflicts/resync/recovery/erasure, account-bound authenticated AES-256-GCM snapshots and a static-dispatch push/pull coordinator with request budgets, timeout and cursor-stall checks; it is not automatically mounted by the shell. Keychain/Keystore, atomic platform persistence, browser storage, concrete HTTP/retry/background integration, deep links, push, physical devices, signing, privacy declarations, store acceptance and a rich native UI remain explicit platform/application work.
LMS/Academy starter domain foundationImplemented / bounded scaffoldGenerates curriculum, enrollment/progress, immutable publication/pins/rollback, assessments, assignments/rubrics, completion/certificates, roles, leaderboard, automation/outbox/worker/scheduler and notifications. The SSR catalog provides bounded ORM-parameterized title/category filtering and CSP-nonce-compatible, dependency-free auth/catalog/course/player shells with keyboard landmarks and visible focus. Lesson presentation distinguishes video/audio, accepts only HTTPS or same-origin absolute sources, requires a WebVTT caption track for video and a bounded transcript/language for both; materialized SQLite covers unsafe-source rejection, missing metadata, injection-shaped input, HTML escaping and nonce identity. Persisted school membership and course scope bind learning and critical mutations to authenticated UserContext; outbox, derived automation and notification state retain school_id, while tenant cache/realtime projections remain best-effort over database authority. The materialized journey covers cross-school denials across the implemented verticals. This is not a complete product or multi-tenancy claim: attachment/upload/media hosting/transcoding, advanced/localized search, metrics, exports, Nexus, distributed failover, caption/transcript quality and localization, WCAG/browser evidence, PostgreSQL/MySQL isolation and the separately operated Academy remain open.
Academy activity evaluationImplemented / bounded single-choice + matching + typed verticals; broader activity system partialevaluate_activity uses static dispatch to turn an untrusted submission into an opaque server-authored result; points are absent from submission APIs. Owner-only HTTP accepts one selected option, a complete bounded pair-ID permutation or bounded typed text, deriving learner/activity identity, answers, policy, points, evidence and time from authenticated/server state. Typed comparison trims, optionally lowercases Unicode and stores a policy-bound SHA-256 replay key rather than raw input; NFC/accent/fuzzy semantics are not claimed. record_activity_result transaction-locks exact persisted evaluator configuration, writes an exact-replay bounded attempt, appends ScoreEvent v2, updates the leaderboard and emits strict score_recorded v2. Materialized SQLite rejects cross-user, actor/evidence/policy mismatch, malformed pairs/text and conflicting replay. Retained attempt/digest state remains application privacy data. The separate persisted quiz path and listening/game evaluators remain open.
Academy spaced-review schedulingImplemented / bounded deterministic foundation; adaptive learning partialAn enabled, validated rullst-box-v1 activity policy updates one durable learner/activity review state inside the authoritative score transaction. Passes grow a capped interval/ease, lapses reset repetitions to a bounded retry interval, algorithm drift fails closed and exact activity replay cannot move the due time. Owner-only GET /reviews/due derives learner/time server-side, caps results at 50 and rechecks school membership, course scope and active enrollment. Materialized SQLite proves deterministic pass/lapse transitions, three exercise kinds, replay safety, durable state, future queue ordering and cross-user denial. It is not FSRS/SM-2 compatibility, efficacy validation, personalized pedagogy or speech/listening support; visual review UX, policy migration/experimentation, privacy-product integration and PostgreSQL/MySQL contention remain open.
Selectable LMS profilesImplemented / bounded; broader matrix partial--lms-modules auth emits fewer than 15 files for identity/session/login/registration, CSRF/headers, authenticated middleware and user Nexus without course or enrollment state. --lms-modules auth,learning emits fewer than 30 files with auth, catalog, enrollment, protected lesson playback and monotonic/idempotent progress, without quiz/gamification/automation/notification files. --lms-modules auth,learning,assessment remains below 40 files and adds owner-only quiz presentation plus server-authoritative, versioned, idempotent grading with a hard attempt limit; it excludes score, leaderboard, achievement, outbox, automation and notification verticals. All three record the selection and materialized projects pass offline Cargo tests. The complete starter remains default. Other combinations and profile hot reload fail explicitly until their dependencies are genuinely detached; realtime and billing are not claimed as selectable modules yet.
Academy privacy lifecycle foundationImplemented / bounded scaffoldGenerates minimized age-band policy without birth date, school-scoped versioned retention, purpose-bound guardian consent/revocation and idempotent export/delete request records. A bounded admin/owner sweep schedules durable delete requests. The school-scoped fulfillment protocol adds exact leased claims, abandoned-claim recovery, delayed retry/dead-letter and actor/SHA-256-bound completion; an expired claim already at the hard ten-attempt ceiling is CAS-transitioned to dead-letter rather than reclaimed forever. A supervised static-dispatch executor adds adapter timeout, explicit shutdown and local metrics; its deterministic mock exercises only the protocol and never touches subject data. SQLite proves success/failure transitions, the hard bound, cross-school non-interference, stale-token denial and replay. The scaffold does not supply the product adapter that executes export/deletion/anonymization over application tables, verify a legal guardian, guarantee PII-free application logs or establish legal compliance.
mdBook documentation hubImplemented in current hardeningKeep mdBook build in CI pages deployment; retired legacy internal SSG in favor of industry standard mdBook.
Parameterized browser RPC through server_functionImplemented / bounded transportConcrete async free functions return RpcResult<T> and generate both a matching explicit Axum router and Wasm caller over rullst.client v1. The contract limits owned Serde arguments/results and same-origin paths, caps bodies at 256 KiB, correlates responses, rejects media/version/schema drift, forwards the CSRF cookie in a header and exposes only bounded failure codes. Native HTTP/security tests, compile-fail diagnostics, both Wasm compile targets and generated-project verification are executable. Authentication, authorization, tenant policy, application idempotency, actual browser engines and network/provider operation remain separate evidence; island hydration is not completed by this RPC transport.
Every generator/blueprint combination compiled in a tempdirPartial (highest remaining CLI priority)A structural matrix validates the 18 public v12 shapes across six blueprints, materializes every blueprint, parses extracted templates, and inventories every public command. Representative source-tree projects cover all six blueprints plus detached LMS auth, auth+learning and auth+learning+assessment profiles, the fixed Active Record + SSR html!/HTMX profile, API, database, hot reload and release. The former 270-case matrix crossed incomplete frontend/ORM labels and is retained only as prerelease history. A separate packaged-distribution gate installs the extracted CLI and compiles all six default blueprints without monorepo paths. CI still needs to prove this on the final RC SHA. Expand fmt/smoke only where it adds a distinct contract and do not treat provider/deploy commands as offline tests.
Generated Auth/JWT/Billing production defaultsImplemented for the audited regressions; still application codeAsync Argon2, issuer/audience/strong-secret checks, authenticated billing identity, CORS allowlists, and signed webhooks are foundations; generated apps still need deployment review.
SQLx/Turso billing scaffoldImplemented / bounded scaffoldmake:billing --model enables exact umbrella features, generates reversible backend-specific persistence, pricing, authenticated checkout/portal with a required production plan allowlist and mandatory signed-webhook code for Stripe/LemonSqueezy, registers modules and refuses collisions. Materialized SQLite and Turso projects pass Clippy, migrate, persist a normalized event and deny a cross-owner subscription reuse without partially binding the conflicting customer. Route mounting, correct plan configuration, durable provider-event idempotency/reconciliation, live sandbox tests and other gateway adapters remain application/provider work.
Fully compliant automatic OpenAPIPartial (do not claim completeness)A syntax-derived draft is useful. Full fidelity needs a typed schema/route contract and validation, not regex confidence.
TypeScript SDK that eliminates contract breaksPartial (worth contract tests)Generation reduces duplication; add serialization golden tests and API compatibility tests instead of promising elimination of drift.
Total framework ejectionPartial (keep as migration aid)Generate an inspectable entry point, list remaining Rullst dependencies, and run cargo check. Do not promise automatic removal of every subsystem.
Opt-in Git pre-commit/commit-message installerImplemented / bounded (CI remains authoritative)hook:install writes managed format/Clippy/IDOR and Conventional Commit wrappers, finds repository roots from nested directories, supports linked-worktree common metadata, preserves and chains active hooks instead of overwriting them, stages replacements before activation, and fails with typed errors on missing worktrees or backup conflicts. Temporary-repository regressions prove idempotency, executable permissions, preservation and fail-before-mutation behavior. Client-side hooks remain deliberately bypassable and are not a substitute for protected CI.
One-click, zero-downtime deploymentPartial (guided deploy is worthwhile)Manifest/SSH helpers are useful, but availability, secrets, migrations, rollback, DNS, and cloud credentials remain operator concerns.
IDOR scanner that proves authorizationHeuristic (keep as a warning tool)AST patterns can find omissions, not prove ownership semantics. Pair findings with route-level negative tests.
Compliance report that prints unconditional PASSRemoved; evidence-oriented report implementedThis is the correct direction. Keep raw evidence, tool versions, skipped states, and commit digests.
Release packaging in dependency order with preflight gatesImplemented in workflow; unreleasedKeep package-all-before-publish and topological publishing. The tag job now bundles Cargo metadata/lockfile, governed Cargo Audit output, CycloneDX, bounded compliance evidence, policies, context and checksums, then attests the bundle and packages. Do not call version 12 released until a matching green tag, crates and notes exist.
SLSA Level 3 certificationNot established (do not claim)Build provenance is worth keeping. Pursue a named SLSA level only after every requirement is independently evaluated for the actual release platform.
RustSec/OSV exception governanceImplementedKeep only unavoidable exceptions, each with owner, compensating control, and expiry; patched findings must block regressions again.
Full Kani/Miri/mutation proof of the frameworkNot implemented (do not promise)Scoped harnesses are valuable. Promote stable, deterministic subsets to blocking gates and label exploratory jobs honestly.
“100% test coverage”Do not promise (measured coverage is worthwhile)Report the exact commit, features, targets, excluded/generated code, and coverage tool output. Line coverage is not behavioral completeness.

Suggested sequencing

  1. Finish version 12 hardening: complete the generated-project matrix, workspace tests/Clippy/format, and release evidence.
  2. Architecture cycle: preserve the new Core/ORM and umbrella feature gates, consolidate Security, and evolve the bounded rullst-messaging and remote-storage boundaries through provider-specific conformance evidence.
  3. Demand-backed integrations: durable rate limits/idempotency, S3/R2, and selected message brokers with shared conformance suites.
  4. High-assurance programs: WebAuthn conformance and, if Brazil is a core market, a dedicated officially homologated NFS-e implementation.
  5. Hardware program: only then expand OTA into real flash/boot lifecycle, MQTT/Embassy, named HSM devices, and audited PQC protocols.

This ordering keeps the extraordinary vision while making the stable surface smaller, testable, and trustworthy.

Rullst v12 release program

Reabertura da auditoria em 2026-09-05: o relatório CLIFIX.md e novas regressões identificaram lacunas na experiência gerada, no estado do ORM durante troca de DLL e em limites de segurança. A RC permanece NO-GO. Os percentuais e a conclusão de teto abaixo são a fotografia anterior, pendente de reavaliação; não representam a prontidão atual. A auditoria de release registra a revisão de todas as crates, com triagem leve de IoT, e as correções/gates restantes.

Status em 4 de setembro de 2026: programa de trabalho auditável, não anúncio de lançamento. A decisão atual é NO-GO para 12.0.0 estável e preparação de uma 12.0.0-rc.1 somente depois dos gates P0 abaixo.

Este documento transforma a ambição da versão 12 em tarefas verificáveis. Ele deve ser atualizado na mesma alteração que conclui uma tarefa. A especificação continua sendo a fonte normativa; o capability ledger registra o limite das capacidades, o hardening status registra evidências de validação e o roadmap preserva a visão de longo prazo. O comparativo técnico separa diferenciais já comprovados das prioridades competitivas sugeridas para a RC e a v13.

Como marcar o programa

  • [x] concluído: há código ou documento, teste proporcional ao risco e uma afirmação pública compatível com a evidência;
  • [~] parcial: existe uma base útil, mas a condição restante está escrita ao lado e ainda não deve ser anunciada como pronta;
  • [ ] pendente: não foi implementado ou ainda não foi verificado;
  • [!] bloqueador de release: a RC ou a estável não pode avançar enquanto o item estiver aberto.

Uma execução local verde prova apenas o commit e o ambiente executados. Ela não substitui CI multi-OS, ambientes reais de provedores, homologação, auditoria externa ou experiência de usuários independentes.

Fotografia de progresso

Em 2026-09-04, este documento contém 204 itens: 77 concluídos, 64 parciais e 63 abertos. A contagem estrita é 37,7%; atribuindo metade do peso aos parciais, o avanço mecânico é 53,4%. O inventário também inclui o programa Omni web-first/platform-enhanced, publicação irreversível, observação pós-RC e gates da versão estável que só podem ser fechados depois; por isso essa contagem não é a prontidão da RC. Depois de elevar o gate para A em todas as crates exceto IoT/B e esclarecer que 90 é piso, não alvo, a auditoria separou o piso da campanha de teto local. A estimativa de prontidão de engenharia/operacional da RC é agora cerca de 91,8%: 8,2% permanece. Essa estimativa inclui elevar cada crate ao melhor teto v12 local responsável, congelar e repetir os gates no SHA exato. A versão, a inspeção dos 16 pacotes e o consumidor externo passaram no commit limpo 27e81152; a revisão documental e a repetição no futuro SHA final da RC continuam abertas. Ela não inclui operar a RC por semanas, homologar provedores/fisco, testar hardware/lojas ou cumprir gates exclusivos da versão estável.

Essa estimativa não esconde os contadores objetivos: há 28 linhas marcadas [!] (oito parciais e vinte abertas), embora várias sejam repetições do mesmo gate em sua seção detalhada e no resumo da versão estável. No scorecard, Core, ORM, Security, Connect, Auth, Mail, Messaging, AI, Capital, Nexus, as duas crates de macros, cargo-rullst, Studio e a facade rullst já estão em A, e IoT satisfaz a exceção B. A lacuna até os pisos aprovados é agora zero e a campanha de tetos locais está completa; isso não substitui os gates do SHA candidato. A tabela de qualidade registra cada distância sem tratá-la como quantidade de tarefas. A campanha não parou no piso: os tetos locais das 15 crates ativas somados ao IoT/B aceito totalizam 1.509/1.600, exatamente os 1.509 pontos agora apoiados por evidência versionada. Assim, a campanha de teto local chegou a 100% e zero pontos de planejamento permanecem. A pontuação de um SHA ainda depende de todos os gates aplicáveis ficarem verdes e não é sinônimo dos 8,2% de prontidão operacional restantes.

O gate geral de cobertura foi alcançado no candidato sem testes artificiais. Com o trabalho restante agora delimitado, a previsão responsável é 1–2 dias de trabalho efetivo para concluir auditoria documental/semântica e eventuais correções, mais 1–3 dias de relógio para matrizes pesadas e repetição final. É uma faixa, não uma promessa: falhas nos testes pesados, dependências/advisories ou lacunas descobertas na auditoria podem ampliá-la. O piso A/IoT-B já foi alcançado no teto auditado, mas ainda precisa ser confirmado pelo conjunto de gates do SHA exato.

A estimativa de 91,8% usa uma régua explícita de planejamento, distinta do scorecard:

Frente da RCPesoEstado estimadoContribuiçãoEvidência/pendência dominante
Campanha de qualidade até os tetos locais55%100%55,0%1.509/1.509 pontos apoiados por evidência; o SHA exato ainda depende dos gates
Gates repetidos no SHA final20%85%17,0%Tríade local, cobertura e 22/22 workflows automáticos aplicáveis passaram em 27e81152, inclusive a suíte all-feature em Linux/macOS/Windows; os testes pesados manuais e a repetição no SHA final permanecem
Versões, pacotes e documentação da prerelease15%95%14,25%Os 16 manifests, preflight, .crate, auditoria de conteúdo, consumidor externo e seis blueprints instalados passaram no commit limpo 27e81152; falta concluir a revisão semântica e repeti-la no SHA final
Evidência hospedada, controles externos e aprovação10%55%5,5%Codecov, Scorecard e alertas hospedados foram verificados; faltam DAST final, demais matrizes manuais, crates.io, revisão humana e decisão GO/NO-GO
Total100%91,75% ≈ 91,8%8,2% operacional permanece

Os percentuais de estado das três últimas frentes são estimativas conservadoras e serão substituídos por 100% apenas quando a evidência do SHA candidato existir. Isso evita somar como pronto um workflow meramente configurado.

As porcentagens são uma fotografia manual, não uma métrica de segurança. Devem ser recalculadas quando itens mudarem de estado e nunca substituem a decisão GO/NO-GO.

1. Decisão de release

O fluxo permanente de integração é branch curta → main → tag. Durante o programa v12, main contém o trabalho ativo e a branch congelada v5 preserva o último baseline legado. A presença de código numa branch não o transforma em release: somente um pacote do crates.io e sua tag imutável correspondente são artefatos oficiais. Os critérios de desenvolvimento, prontidão e release estão separados no RELEASE_GUIDE.md.

  • Tratar v12 como um programa de estabilização, e não como uma lista de superlativos.
  • Manter a estável em NO-GO enquanto os gates obrigatórios estiverem abertos.
  • [~] Congelar novas funcionalidades antes da RC. Até que o novo gate de notas seja alcançado, somente capacidades delimitadas que fechem uma lacuna auditada de qualidade podem entrar; depois disso, aceitar apenas correções, testes, documentação e mudanças indispensáveis à release.
  • [!] Fazer uma revisão final deste checklist e registrar a decisão GO/NO-GO com data, commit e responsáveis.

A RC vai para o crates.io?

Sim. A proposta é publicar 12.0.0-rc.1 no crates.io, e não apenas criar uma tag privada. Uma prerelease é pública e permanente: ela pode ser yanked, mas o mesmo número de versão nunca pode ser substituído. Usuários precisam optar explicitamente por ela, por exemplo:

[dependencies]
rullst = "12.0.0-rc.1"
  • Sincronizar todos os pacotes publicáveis e requisitos internos em 12.0.0-rc.1 numa alteração exclusiva de preparação da release. Os 16 manifests publicáveis, a facade, o CLI e o lockfile já formam um único trem de versão; o preflight de metadata com --locked passou localmente.
  • [~] [!] Gerar, inspecionar e testar os pacotes exatos antes de qualquer upload irreversível. No commit limpo 27e81152, os 16 .crate registraram o mesmo SHA sem dirty state, passaram a auditoria de conteúdo, compilaram num consumidor externo all-feature e forneceram o CLI que gerou e compilou os seis blueprints. Falta repetir a prova sobre o futuro commit aprovado da tag.
  • Criar a tag v12.0.0-rc.1 somente no commit aprovado.
  • Publicar na ordem topológica documentada em AGENTS.md.
  • Verificar indexação, documentação e instalação usando apenas crates.io.
  • Corrigir os problemas encontrados em rc.2, rc.3, etc.; nunca republicar o mesmo número.
  • Publicar 12.0.0 somente após uma janela real de uso da RC.

Referências oficiais: Publishing on crates.io e SemVer no Cargo.

2. Congelamento, versões e migração

  • Os 16 pacotes publicáveis do workspace usam atualmente 12.0.0-rc.1 nos manifests locais.
  • [~] [!] cargo metadata --locked confirmou 16 pacotes publicáveis em 12.0.0-rc.1, com dependências internas declarando path e requisito da mesma prerelease. O preflight topológico e a auditoria dos arquivos passaram no commit limpo 27e81152; falta repetir a prova no SHA hospedado aprovado da prerelease.
  • Centralizar a ordem topológica em .github/release-order.json e validá-la contra o conjunto publicável, a versão da tag, os requisitos internos e o DAG antes de empacotar; o preflight positivo e a rejeição de versão divergente passaram localmente.
  • [!] Manter o worktree da release limpo e associar toda evidência ao SHA da tag.
  • Curar o changelog de v12 por impacto ao usuário: o topo agora resume migração/compatibilidade, fronteiras de segurança, contratos de IA e observabilidade, enquanto o inventário técnico detalhado permanece preservado e explicitamente separado para rastreabilidade.
  • Criar guias de migração v5 → v12, v6 → v12 e dependências da era v11 → v12, preservando a evidência de que somente v5 tem tag entre essas baselines. O caminho também transformou cargo rullst upgrade numa transação assistida extensível: planejamento humano/JSON sem escrita, descoberta exata do workspace, edição TOML que preserva comentários e reconhece aliases, catálogo versionado de riscos v5, alvo preso à major da CLI, snapshot de manifests/lock/fontes, cargo fix/cargo check, rollback automático e restauração persistida após interrupção. Testes process-level cobrem plano, JSON, sucesso e falha com rollback. O comando não altera banco, segredo, autorização ou fronteiras de produção; esses gates continuam manuais e documentados no tutorial de upgrade assistido.
  • Publicar a matriz de features da crate guarda-chuva e das outras 14 crates, incluindo defaults, aliases, integrações opcionais, seleção estrita de banco e limites dos simuladores.
  • Documentar política de MSRV, depreciação, compatibilidade e suporte de patch/minor em compatibility-policy.md, incluindo limites de prerelease, janela suportada e exceção fail-closed para segurança.

3. CI reproduzível no commit exato

Evidência local atual no commit candidato limpo 27e81152, reexecutada em 2026-09-04 — útil para desenvolvimento, mas ainda não substitui os mesmos gates na futura tag:

  • cargo test --workspace --all-features verde, incluindo testes de integração, dez projetos gerados e doc-tests.

  • cargo clippy --workspace --all-features -- -D warnings verde, exatamente conforme a tríade normativa de AGENTS.md. O gate hospedado acrescenta --all-targets e também passou no mesmo candidato.

  • cargo fmt --all -- --check e git diff --check verdes.

  • [!] cargo fmt --all -- --check verde na tag.

  • [!] cargo clippy --workspace --all-targets --all-features -- -D warnings verde na tag.

  • [!] cargo test --workspace --all-features verde na tag.

  • [!] CI principal verde em Linux, macOS e Windows no mesmo commit.

  • [~] Testes reais de SQLite, PostgreSQL e MySQL, com isolamento e evidência da execução: as três matrizes exclusivas de rullst-orm passaram localmente em 2026-08-28, com PostgreSQL e MySQL em Testcontainers Docker. Isso prova o CRUD ORM delimitado nesse host, não o workspace/Academy inteiro nem o SHA da tag.

  • [~] Matrizes --no-default-features, features mínimas, combinações críticas e MSRV verdes: .github/check-feature-boundaries.sh passou localmente em 2026-08-29 compilando as 16 crates publicáveis sem defaults, 32 fronteiras isoladas de adapters/facade e executando os testes de Core sem defaults. Além disso, cargo +1.96.0 check --workspace --all-features passou localmente no worktree atual. O gate é reutilizado pela CI principal e pela release; as matrizes estritas de banco e o job MSRV já existem, mas todos ainda precisam ficar verdes no SHA final da RC.

  • Doc tests, Wasm/no_std, E2E, benchmarks, fuzzing delimitado, Miri, Kani, sanitizers e os gates Omni verdes para seus alvos declarados. A nova geração desktop executou localmente de ponta a ponta, incluindo npm e ícones, em 2026-08-30. O host não possui pkg-config/GLib, então o shell emitido também passou cargo check num contêiner Rust 1.96/Debian com os pré-requisitos oficiais do Tauri. Desktop Linux/macOS/Windows, Android aarch64 APK e iOS simulator passaram nos workflows hospedados do commit 755fbd61933bed04369e0eb5de50b11275db5e3d; o item agregado continua aberto pelos demais gates nomeados e porque compilação não prova aparelho/loja. No candidato intermediário 45fbdbe7, Miri, Kani e os sanitizers passaram; a campanha de fuzzing revelou três harnesses desatualizados e um panic real de fronteira UTF-8 no redator de URLs de banco. Os quatro achados foram corrigidos localmente, o caso Unicode virou regressão unitária e seed de corpus, e os alvos de parser ORM, redator e sanitizer passaram 100.000 execuções cada com libFuzzer/AddressSanitizer em 2026-09-05. A campanha completa ainda precisa passar novamente no SHA final, portanto o item permanece aberto. Para reduzir o ciclo de correção sem reduzir o gate, o workflow agora compila os 40 alvos em dez preflights antes das campanhas longas e separa um diagnóstico estrito de cinco minutos para exatamente um alvo; o resumo recusa explicitamente esse diagnóstico como evidência de RC. Mutations também aceita um único arquivo Rust de produção para feedback de correção, sem substituir a campanha completa. A tentativa de dezesseis shards usava a superfície default-feature, deixou testes opcionais fora da classificação e projetou 11–17 horas nos grupos mais lentos. O workflow agora exige --all-features e reparte o mesmo inventário medido de 14.380 mutantes em 80 shards de no máximo aproximadamente 180 candidatos. Antes de iniciar essa matriz, um --list --json barato exige a lista exata e única; o agregado final precisa corresponder aos mesmos nomes, além de classificar todos eles uma única vez. Isso é configuração validada, não resultado da campanha final. No SHA 36411ea1, 39 dos 40 alvos completaram integralmente 5h30; somente fuzz_parser encontrou uma terceira árvore válida que fazia a obtenção incondicional de Field::span() renderizar um tipo patológico por mais de dez segundos. Todos os diagnósticos do parser atingíveis por campos e tipos agora usam identificadores delimitados, o terceiro caso foi preservado no corpus e o reprodutor ASan exato caiu de cerca de 12,1 segundos para aproximadamente 30 milissegundos localmente. Uma nova campanha ASan local de cinco minutos completou 1.740.804 execuções sem achados. O diagnóstico hospedado 34495340300 completou mais 1.541.970 execuções em 301 segundos no commit de código corrigido 40c1b083, também sem achados. O resultado ainda não fecha este item: a campanha completa dos 40 alvos precisa passar no SHA candidato congelado.

  • Auditar os dez doctests antes marcados ignore: nove agora compilam como testes ou no_run; o único ignore restante é o exemplo da crate proc-macro, justificado para evitar dependência circular e coberto pelo teste de integração da facade. Um doctest ignorado não é contado como exemplo compilado.

  • Criar um harness Cargo-aware para os 52 tutoriais públicos: o módulo rullst/src/book_doctests.rs, habilitado somente por cfg(doctest), lê os Markdown originais em vez de manter cópias. Em 2026-09-05, cargo test --workspace --all-features identificou 106 blocos Rust, compilou/executou 88, registrou 18 fragmentos deliberadamente contextuais e terminou com zero falhas. Como o CI e a release já executam cargo test --workspace --all-features, a mesma verificação é bloqueante nesses gates; o SHA remoto ainda precisa repetir a evidência.

  • Estender o mesmo harness aos 27 guias públicos não tutoriais que contêm Rust. Em 2026-09-05, rustdoc reconheceu 203 exemplos no livro: 177 compilados/executados e 26 fragmentos ignore com contexto declarado, zero falha. O recorte não tutorial corresponde a 97 exemplos, 89 compilados/executados e oito contextuais. Um teste de inventário descobre os arquivos Markdown na raiz e em docs/src/crates, impedindo que um novo guia com Rust fique fora do gate silenciosamente. A evidência remota ainda deve ser repetida no SHA congelado da RC.

  • Auditoria, licenças, fontes, dependências duplicadas, typos e SemVer sem falhas ou com exceções datadas, justificadas e com prazo.

  • Reexecutar no candidato todos os workflows que falharam ou ficaram sem evidência no commit-base; o nome de um workflow não conta como prova.

3.1 Programa Omni web-first, platform-enhanced

O web app é a experiência canônica e universal. Domínio, identidade, autorização, dados, realtime e segurança continuam sob autoridade do servidor. Omni empacota essa experiência e poderá acrescentar capacidades do dispositivo sem duplicar a regra de negócio nem confiar no cliente. Isso serve aplicativos ricos — colaboração, comunidade, mensagens ou educação no estilo Duolingo — mas não promete gerar o produto, conteúdo ou UX final com um clique. Para jogos, Rullst pode fornecer backend/web/contas/realtime; não substitui um motor gráfico.

  • Tornar o contrato web-first/platform-enhanced normativo no SST e manter segurança/autorização no servidor.
  • Derivar nome/versão do host, aceitar overrides validados e exigir identificador reverse-DNS application-owned no Android/iOS.
  • Remover placeholders publicáveis do caminho mobile e gerar JSON/CSP sem interpolação textual insegura.
  • Negar navegação fora da origem exata no runtime nativo e não expor IPC privilegiado à página remota.
  • Gerar bootstrap local acessível com estado inicial offline/retry, sem chamá-lo falsamente de sincronização offline.
  • Fazer o backend desktop localhost falhar fechado em porta pré-ocupada, child exit ou timeout e encerrar somente o processo que iniciou.
  • Workflow desktop Linux/macOS/Windows passou no SHA 755fbd61933bed04369e0eb5de50b11275db5e3d; ele verifica o crate gerado, não installers assinados nem uma sessão GUI/WebView.
  • Workflow Android aarch64 compilou um APK debug no mesmo SHA; isso não equivale a aparelho, assinatura ou Play testing.
  • Gate iOS compilou o simulador com identidade application-owned no mesmo SHA; ele continua sem evidência de aparelho, signing ou App Store.
  • Implementar opener/deep-link OAuth com allowlists, state/nonce e callback single-use sem alargar a navegação para qualquer HTTPS.
  • Definir perfis opt-in de push, biometria e armazenamento Keychain/Keystore com permissões mínimas, negativos de replay/cross-account e fallback web.
  • Criar contrato tipado/versionado cliente-servidor compartilhável por web, shells e futuros frontends ricos sem tornar o cliente autoridade: rullst::client_contract define rullst.client v1, negociação positiva, envelopes privados tipados, IDs/chaves/códigos delimitados, tempo do servidor, JSON máximo de 2 MiB e rejeição de campos/versões desconhecidos; testes negativos e wasm32-unknown-unknown passam. O contrato não carrega role/tenant/autorização nem oferece idempotência durável ou sync offline.
  • [~] Implementar perfil offline real: a feature nativa opt-in offline-sync agora fornece estado v1 ligado à conta, snapshots autenticados AES-256-GCM, fila FIFO idempotente, revisões/cursor do servidor, aplicação atômica, conflitos sem client-wins implícito, full resync, quotas, recovery e erasure lógico. Um coordinator por dispatch estático limita batches/páginas, exige timeout por request, interrompe retry sem progresso e rejeita cursor travado sobre transport autenticado da aplicação. Ainda faltam adapters Keychain/Keystore e persistência atômica por plataforma, browser/IndexedDB, HTTP concreto, retry/background, migrações concretas futuras, UX da aplicação e testes de processo/aparelho; portanto o shell não é anunciado como offline-first.
  • [~] Criar um aplicativo de referência vertical rico — educação é um bom candidato — que prove mídia/áudio, progresso, gamificação e realtime E2E sem alegar que conteúdo/pedagogia são gerados pelo framework. O Academy gerado agora materializa a fatia web: modelo e migration distinguem vídeo/áudio, player SSR exige HTTPS ou origem local, vídeo exige WebVTT, toda aula exige transcrição/idioma delimitados e o teste executado prova escaping, nonce, ausência de autoplay e rejeições. Isso se integra ao progresso, avaliação/score, leaderboard, conquista, automação e realtime já testados no mesmo projeto. Ainda não é uma aplicação E2E rica: faltam playback/browser, conteúdo e pedagogia, fala/microfone, UX integrada, dispositivos e operação externa.
  • Definir budgets e testes de startup, memória, tamanho, acessibilidade, rede ruim/reconexão e vazamento de segredos por plataforma.
  • Executar contratos em aparelhos Android/iOS reais e registrar versões de SO/WebView, limitações e evidência reproduzível.
  • Preparar assinatura, manifests de privacidade, beta tracks e checklist de submissão; aprovação das lojas permanece externa.
  • Implementar atualização desktop assinada, rollback e política de canais antes de anunciar atualização automática do aplicativo.

4. Cobertura e qualidade dos testes

O número observado anteriormente foi aproximadamente 83%, mas não explicava seu escopo. A baseline oficial inicial, publicada pelo Codecov para o SHA 04006e82 em 2026-09-01, era:

  • 79,37% (54.937/69.212) no agregado público de produção, incluindo CLI e proc-macros, mantido visível como indicador informativo;
  • 84,40% (42.334/50.161) nas bibliotecas do framework selecionadas pelo status bloqueante da RC;
  • 100% no patch do checkpoint, acima do gate de 90%.

O checkpoint completo usado por esta auditoria, publicado para o SHA 28e2cea9 em 2026-09-03, é:

  • 84,97% (68.228/80.289) no agregado público de produção, incluindo CLI e proc-macros; esse agregado passa a ter gate próprio de 90%;
  • 91,26% no componente público framework_libraries, acima do gate de 90%;
  • 95,28% no componente rullst-auth, 93,09% em rullst-security, 62,93% no CLI e 91,28% nas proc-macros informativas.

O checkpoint predecessor completo mais recente, também publicado em 2026-09-03, avançou para 85,16% (70.026/82.227) no repositório inteiro e 91,32% (55.945/61.265) em framework_libraries; Auth ficou em 95,28%, Security em 92,51% e o CLI em 62,93%. O workflow de cobertura concluiu com sucesso, mas esse sucesso prova a geração e o upload do relatório, não o atendimento automático do status de projeto em 90%.

O lote candidato 704b6d4d acrescenta testes de processo com assertivas sobre scaffolds de controller/middleware/worker/LiveView/gRPC, inspeção, OpenAPI/SDK, empacotamento, deploy offline, diagnósticos, auditoria, Foundry, Omni, mail, chat, Academy, banco, migração automática com diferença real de schema e ejeção com backup. Codecov mediu 90,0337% (74.032/82.227) no repositório inteiro: 27 linhas cobertas acima do menor inteiro que satisfaz o corte exato de 90%. O componente framework_libraries ficou em 91,2952% (55.932/61.265), Auth em 95,28%, Security em 92,51%, CLI em 85,48% e proc-macros em 91,29%.

A comparação prévia por arquivo e linha havia encontrado 4.053 linhas de produção antes não cobertas e projetado 90,0908% mantendo o denominador do predecessor. O resultado oficial confirmou o gate, mas com o colapso LCOV em 74.032 hits, razão para nunca publicar a projeção como resultado. O artefato bruto retido pelo workflow registra 88,9181% (76.659/86.213) no resumo JSON do LLVM; o importador LCOV do Codecov registra as linhas únicas usadas pelos status públicos. As duas representações e seus denominadores são preservados, sem mistura ou média. O resultado precisa ser repetido no SHA congelado da RC; este checkpoint não autoriza antecipar a tag.

O candidato de release 27e81152 repetiu e ampliou o gate em 2026-09-04. O relatório oficial do Codecov registra 90,06% (74.219/82.408) no repositório inteiro e 91,33% (56.119/61.446) em framework_libraries; Auth ficou em 95,35%, Security em 92,54%, CLI em 85,48% e proc-macros em 91,29%. O patch medido ficou em 100%. O resumo bruto LLVM do mesmo artefato usa outra deduplicação de linhas e registrou 88,95% (76.858/86.401); ele não substitui nem é combinado com o modelo LCOV oficial usado pelos status públicos.

O primeiro recorte focado posterior a essa baseline elevou localmente rullst-messaging de 85,64% (1.962/2.291) para 90,22% (2.067/2.291). As novas regressões exercitam gramáticas e limites públicos, leases expirados em ACK/retry/dead-letter, teto de tentativas, retenção e inscrições duráveis, indisponibilidade do arquivo e corrupção individual de envelope, fingerprint, timestamp, tentativa, dead letter e versão do schema. Isso é evidência local do crate, não altera retroativamente o agregado oficial nem substitui o relatório do próximo SHA no Codecov.

O segundo recorte focado elevou localmente rullst-studio de 64,11% (1.926/3.004) para 90,28% (2.899/3.211). O relatório mescla os perfis SQLite, PostgreSQL, MySQL e MariaDB e exercita descoberta de schema, relações, dashboard, paginação, busca HTMX, mutações tipadas, feature flags, migrations e estados inválidos ou ausentes. Os testes revelaram e corrigiram descarte de links reais no dashboard, classificação incorreta de TINYINT(1) como inteiro, alias incompatível no diagrama ER do MySQL/MariaDB e chave primária TEXT inválida para feature flags nesses backends. Como no recorte anterior, esse é um resultado local que só se torna baseline oficial depois do upload do SHA.

Esses números são evidência de planejamento, não da futura tag. Line coverage não comprova adequadamente um gerador executado como processo nem o diagnóstico de uma proc-macro: cargo-rullst deve ser bloqueado por scaffolds gerados e compilados; macros, por testes compile-pass/compile-fail. Isso não autoriza ocultar sua cobertura — os componentes continuam publicados no Codecov como informativos. A meta deve ser perseguida por risco e em etapas, sem testes que apenas executem linhas sem validar comportamento.

O relatório total inclui o código de produção de todas as crates do workspace. O novo status bloqueante geral inclui toda a produção do workspace. O status separado framework_libraries seleciona as bibliotecas runtime e exclui somente cargo-rullst e as duas crates de proc-macros, que também mantêm gates funcionais próprios. Exemplos, benchmarks, documentação, artefatos gerados, fixtures auxiliares e arquivos de teste separados não entram no denominador. Testes inline ainda podem aparecer no mapeamento LLVM do mesmo arquivo de produção; o número oficial será o relatório final do Codecov no SHA da RC. O repositório usa um único codecov.yml; a CI autentica o upload pelo OIDC efêmero do GitHub, envia cobertura de linhas estável e produz semanalmente um artefato experimental de branches com Rust nightly. Enquanto a instrumentação de branches do Rust for instável, essa métrica é observacional e não bloqueia sozinha a release.

  • [~] [!] Produzir relatório de cobertura válido e vinculado ao SHA da RC. O candidato 27e81152 é válido, público e passa os três gates; falta repetir a evidência no SHA congelado da RC.
  • [~] [!] Configurar ≥90% de line coverage no agregado completo e nas bibliotecas do framework para RC e estável, sem tolerância. O componente de bibliotecas alcançou 91,33% e o agregado 90,06% no candidato 27e81152; a configuração declara ambos os gates. Falta repeti-los no SHA congelado da RC.
  • Configurar ≥90% de patch coverage como status obrigatório; o candidato 27e81152 alcançou 100%, e falta reproduzi-lo no SHA da RC.
  • Configurar componentes Codecov separados para framework_libraries, rullst-auth, rullst-security, CLI e proc-macros. As bibliotecas, Auth e Security bloqueiam a release em ≥90% de linhas; CLI e macros permanecem públicos e informativos porque seus gates principais são scaffolds e testes compile-pass/compile-fail.
  • [~] Baseline oficial crítica acima da meta: rullst-auth 95,35% e rullst-security 92,54% no SHA 27e81152. Falta comprovar os mesmos componentes no SHA da RC.
  • [~] Medir branch coverage e fazê-la crescer: a coleta semanal/manual em nightly e a retenção dos relatórios estão configuradas; falta estabelecer a primeira baseline válida e uma política de não regressão compatível com a estabilidade da instrumentação.
  • [~] [!] Garantir que nenhum caminho crítico fique com 0% de cobertura. O recorte local agora cobre resolução fail-closed e persistência da APP_KEY, cookie Secure em produção, coordenadas WebAuthn ausentes, Origin WebSocket malformada, continuidade/tamper da audit chain, envelopes Vault inválidos e corpos RASP com encoding, UTF-8 ou tamanho não inspecionável. Ainda falta o inventário completo por threat model e a comprovação no SHA da RC.
  • [~] [!] Derivar dos threat models e testar todos os casos negativos aplicáveis às fronteiras críticas de autenticação, autorização, parsing, criptografia, webhooks, tenancy, SQL e filesystem. O gate .github/check-threat-model-release-minimum.sh liga TM-12.10 a 67 linhas de evidência, 55 abuse-case IDs e 59 execuções de teste exatas em treze crates, cobrindo Core, ORM, Auth, Nexus, Studio, tenancy, Capital, AI, Mail, IoT, deploy e Academy, inclusive identificadores SQL dinâmicos e acesso a arquivos, e falha se um filtro passar a executar zero testes. O mínimo TM-12.10 completo passou localmente em 2026-09-03, inclusive o CAS concorrente e a recuperação do token store de Connect, o negativo de adulteração/rotação do journal SIEM e a compilação dos projetos gerados. A repetição integral no SHA hospedado da RC e os demais casos ainda não inventariados continuam abertos.
  • [~] Cobrir ORM raw/migrations/pool, Core Redis/DB por feature/dylib, Mail facade/attachments, handlers de Nexus/Studio e Security schema/rate/RBAC/headers/timing/vault. A anotação #[orm(encrypted)] agora cobre String/Option<String> de ponta a ponta: insert/read/update parcial, rotação por keyring, contexto autenticado de tabela/coluna, tamper, nullable, pluck_string não-null e recusa de consultas incompatíveis com ciphertext aleatório. O FieldEncryptor emite o envelope normativo RULLST:v2 e ainda lê o prefixo de desenvolvimento ENC:v2; permanecem as demais superfícies listadas e a evidência do SHA final.
  • [~] Cobrir timeouts, cancelamento, erros de provedores e políticas de tools em rullst-ai: os seis transports live agora aplicam deadline configurável de 30 segundos por padrão, ProviderCapabilities fixa o contrato e servidores loopback provam o timeout e o formato compatível (AI-06). O novo caminho OpenAI-compatible prova streaming delimitado e cancelamento explícito durante espera/leitura. Ainda faltam os protocolos diferentes, circuit breaker, concorrência e a matriz de erros reais dos provedores.
  • [~] Cobrir a CLI por execução end-to-end e macros por testes de compilação e diagnósticos, mesmo quando instrumentos tradicionais não medirem bem esses processos. Os testes process-level de academy:doctor provam saída JSON, exit status fail-closed e o caminho completo evidenciado sem certificação; os macros mantêm trybuild. A matriz E2E dos demais comandos públicos continua aberta.
  • Manter componentes informativos separados para CLI e proc-macros, sem misturá-los ao status bloqueante das bibliotecas nem omiti-los do relatório.
  • Manter a matriz estrutural das 18 formas públicas da v12 e a compilação de projetos gerados como gate separado: ela mede validade dos artefatos, não line coverage. A matriz anterior de 270 combinações incluía labels de frontend e ORM que não possuíam paridade entre blueprints e foi substituída junto com esses seletores.
  • Auditar “Hadouken code” em produção por profundidade e complexidade cognitiva. O único alerta acima do limite foi rullst-orm-macros::parser::parse (30/25): o parser foi separado em estados de atributos de modelo/campo, os arquivos resultantes ficaram abaixo de 500 linhas e o alvo de produção passou Clippy estrito com -W clippy::cognitive-complexity. A nova matriz também expôs e corrigiu a desserialização Redis que exigia Default e ocultava cache ausente/malformado.
  • Tornar a gramática de rullst-orm-macros fail-closed: atributos agora usam syn::Attribute::parse_nested_meta; opções desconhecidas, duplicadas, órfãs ou incompatíveis falham em compilação. A derive valida identificadores, id persistido, alvos de tenant/soft-delete/embedding e invariantes de relações antes do codegen. Vinte e quatro fixtures trybuild, a suíte focada da proc-macro e as matrizes rullst-orm --all-features passaram localmente; compatibilidade de compilador/ecossistema além da matriz continua externa.

A meta não deve ser alcançada por testes sem assertivas úteis em funções triviais. A ordem é: caminhos negativos do threat model, fronteiras críticas, erros/cancelamento e, por fim, lacunas legítimas do restante do workspace.

5. Empacotamento e docs.rs

  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps -j 2 passou em 2026-08-29; os doc tests também passaram no último cargo test --workspace --all-features. O mesmo gate continua obrigatório na tag pelo bloco de CI acima.
  • [~] [!] Os 16 .crate de 12.0.0-rc.1 foram gerados e auditados localmente no commit limpo 27e81152; todos registraram esse SHA e dirty=false. O consumidor extraído compilou todos os recursos, instalou o CLI empacotado e compilou Blank, LMS, SaaS, Blog, Portfolio e ERP. O ensaio integral passou com jobs limitados, inclusive a construção nativa do DuckDB. Falta repetir o gate sobre o futuro commit aprovado da RC.
  • [~] A primeira inspeção dos 15 arquivos .crate então existentes revelou licenças ausentes em 13 pacotes e um arquivo .env.test_autofix indevido na crate guarda-chuva. As licenças foram padronizadas, inclusive em rullst-messaging; o .env* foi excluído do pacote e o workflow agora bloqueia quantidade/nome/tamanho inesperados, caminhos inseguros, padrões de segredo, estado SQLite de runtime, conteúdo obrigatório ausente e licença divergente. O gate local passou nos 16 arquivos 12.0.0-rc.1 de 27e81152; falta repeti-lo nos artefatos do futuro commit aprovado da RC.
  • O gate .github/test-packaged-distribution.sh extrai os 16 .crate em diretório temporário, compila offline um consumidor que referencia todos os pacotes e recusa dependências geradas por path antes de aplicar patches que apontam exclusivamente para o conteúdo extraído dos arquivos empacotados.
  • O mesmo gate instala cargo-rullst a partir de seu .crate, usa o binário instalado para gerar Blank, LMS, SaaS, Blog, Portfolio e ERP de forma não-interativa e executa cargo check --offline --all-targets em cada projeto. As dependências internas geradas agora herdam CARGO_PKG_VERSION, inclusive prereleases, em vez de fixar auxiliares em 12.0.0.
  • Confirmar a construção de todas as páginas no docs.rs.
  • [~] Remover, atualizar ou isolar dependências opcionais obsoletas: a dependência Leptos sem uso foi eliminada, proc-macro-error3 foi atualizado e o lockfile substituiu o wnaf 0.14.0 yanked por 0.14.1. cargo audit passou localmente com somente a exceção já governada; falta reproduzir Cargo Audit/Cargo Deny e registrar a evidência no SHA final.

6. Bootstrap e publicação no crates.io

  • [~] O workflow de release empacota antes do upload e publica na ordem topológica. Falta provar o bootstrap das crates ainda não registradas e o fluxo completo numa tag candidata.
  • O validador de tag aceita versões estáveis e prereleases SemVer, incluindo v12.0.0-rc.1.
  • [~] [!] O gate .github/check-crates-ownership.sh consulta o crates.io na verificação e novamente imediatamente antes do upload, exige venelouis nos nomes registrados e só aceita 404 para a allowlist revisada. Em 2026-08-29, seis nomes estavam registrados para o proprietário esperado e nove retornaram 404 dentro da allowlist de bootstrap; falta reexecutar e preservar a evidência no SHA final da RC.
  • [~] [!] O job publish, protegido pelo ambiente GitHub crates-io, usa OIDC nos crates registrados e seleciona CRATES_IO_BOOTSTRAP_TOKEN somente nos nomes classificados como inéditos. O runbook limita o token a publish-new e exige Trusted Publishing, revogação e remoção do secret/allowlist após o bootstrap; faltam configurar os controles externos e executar/revogar a credencial na primeira RC real.
  • [~] O workflow agora aguarda indexação e compara o checksum retornado pelo crates.io após cada publicação; falta observar o mecanismo numa RC real.
  • Documentar recuperação de publicação parcial: inventário/checksum por crate, retomada apenas do prefixo ausente, uso obrigatório de nova prerelease para bytes alterados, yank, comprometimento de credencial e comunicação.
  • [~] O job de publicação baixa os artefatos atestados, valida seus checksums, reproduz os 16 .crate a partir da tag e exige igualdade byte a byte antes do upload; depois confirma o checksum do registry. Falta a evidência de uma execução real e a proveniência continua limitada pelas fronteiras declaradas do GitHub Actions/crates.io.

7. Programa de segurança v12+

O objetivo não é apagar as ambições antigas. É convertê-las em contratos menores, testáveis e honestos. Controles criptográficos e afirmações de conformidade só recebem [x] após revisão especializada proporcional ao risco.

Baseline obrigatório

  • Definir um production preset canônico: o contrato público tipado ProductionPreset::middleware_order() e a arquitetura documentam e testam a ordem externa→interna de proxy, limite de corpo, request ID, tracing, headers, CORS, WAF/RASP, CSRF, sessão, autenticação, tenant, autorização, rate limit e handler. Camadas de identidade continuam explicitamente pertencendo à aplicação, pois o framework não pode inventar sua política de domínio.
  • Tornar readiness e shutdown um contrato executável do Core: ApplicationLifecycle registra no máximo 32 componentes imutáveis, mantém fases monotônicas, nega novas requisições antes de ready, sob dependência indisponível ou durante drain, e aguarda de forma limitada as requisições já aceitas. Server marca ready depois do bind, começa drain antes da espera do Axum, encerra como stopped e aceita um future de supervisor em run_with_shutdown. /ready revela somente fase/contagens; os testes CORE-01/02 cobrem startup, indisponibilidade, lock corrompido, timeout, corrida de admissão e listener real. Probes, auth de domínio, consenso entre réplicas, propagação do load balancer e deadline operacional continuam no host/deployment.
  • Criar threat models versionados para autenticação, Nexus, Studio, tenancy, webhooks/pagamentos, AI/tools, atualização IoT, deploy e Academy: TM-12.10 define ativos, fronteiras, abuse-case IDs, controles, evidências e riscos residuais. A revisão independente do candidato exato continua no gate final.
  • O scanner IDOR agora exige classificação adjacente public|owner|role|admin, recusa mutação classificada como pública e exige um guard reconhecido nas demais. O gate também recusa anotações sem justificativa e ausência de arquivos Rust. Sete negativos unitários, o inventário de rotas parametrizadas do workspace, os seis blueprints e as 18 variantes da matriz estrutural pública da v12 passaram. As mutações do ERP, antes públicas, agora reutilizam NexusAuthPolicy::protect_router; testes HTTP provam a fronteira administrativa (200 local, 403 remoto/sem peer) e ownership real (200 owner, 403 cross-owner, 200 moderator, 401 sem identidade). O ERP materializado passa cargo check --offline --all-targets. Isolamento multi-tenant completo continua no item específico abaixo e a prova deve ser repetida sobre o SHA da RC.
  • Nexus falha fechado sem política explícita; o atalho local exige build de debug, peer loopback verificado e ConnectInfo.
  • Nexus aceita tenant text opt-in por model, propaga o TenantContext confiável por toda rota built-in e recusa contexto ausente/input protegido. O audit opt-in obrigatório compartilha a transação da mutação e falha fechado; a documentação preserva que o storage é o mesmo banco, mutável e sem registro de tentativas negadas ou chave auto-gerada uniforme.
  • Fazer revisão independente da política de acesso do Nexus e dos fluxos CRUD/batch/AI.
  • [~] Implementar rate limit distribuído, idempotência durável e audit trail append-only para deploys com múltiplas instâncias. A feature rullst-security/redis-rate-limit fornece contador fixed-window atômico em Lua, namespace validado, chaves de cliente hasheadas, TTL/retry e erros tipados; o modo vazio/mock_* é explicitamente local e require_distributed() o rejeita em startup de produção. Um contrato opt-in passou localmente contra Redis real e roda em CI/release com duas instâncias independentes do limiter e imagem fixada por digest. Faltam cluster/failover, idempotência compartilhada e sink de auditoria durável.
  • [~] Consolidar headers seguros, CSP nonce, CORS e CSRF numa baseline testada em navegador e proxy reais. Core agora expõe uma composição canônica única e o Server a utiliza: a configuração validada é instalada antes dos middlewares, e a ordem externa→interna é headers → CORS → WAF → CSRF → PII opcional → handler. A allowlist CORS aceita apenas origins HTTP(S) exatas, rejeita wildcard/path/query/credenciais/duplicatas, limita métodos/headers e habilita credenciais somente por opt-in sobre a lista explícita. O teste HTTP in-process prova nonce CSP igual ao renderer, cookie CSRF Secure/SameSite, POST negado/aceito, preflight permitido e origin estrangeira sem grant. Ainda faltam browser E2E e proxy/TLS reais, além das camadas de sessão/auth/tenant que permanecem contrato da aplicação.
  • [~] Ampliar DAST representativo para os artefatos que o CLI realmente entrega. O workflow manual agora compila em release e migra um REST API e o LMS completo recém-gerados; alertas WARN/FAIL bloqueiam e nenhuma regra é ignorada. O showcase do blog é separado e informativo porque sua CSP relaxada e assets externos são um limite deliberado e documentado. A execução local de 2026-09-01 encontrou e corrigiu exposição de diagnóstico SQL no status do blank/API e acrescentou Cache-Control: no-store somente quando o handler não definiu política própria. Depois das correções, REST cobriu 3 URLs com 65 regras em PASS, 5 INFO, 0 WARN e 0 FAIL; LMS cobriu 25 URLs com 63 regras em PASS, 7 INFO, 0 WARN e 0 FAIL. Falta repetir a execução hospedada no SHA congelado da RC; três superfícies não provam todos os blueprints, papéis autenticados, browsers, proxies ou deployments. A passagem informativa do blog encontrou formulários sem o campo CSRF: o middleware Core passou a expor o mesmo token validado aos handlers, o blog passou a renderizar e testar esse valor, e a nova varredura já não reportou o alerta 10202. Com a triagem registrada sem nenhuma regra IGNORE, essa superfície cobriu 28 URLs com 61 regras em PASS, 7 INFO, 2 WARN deliberadamente visíveis para dependências CDN sem SRI e 0 FAIL. A auditoria de scaffolds estendeu o mesmo contrato aos POSTs/HTMX dos blueprints Blank e ERP; LMS, SaaS e Nexus já possuíam integração equivalente, enquanto Studio conserva sua fronteira debug-only de loopback e mesma origem.

Autenticação e criptografia

  • Concluir WebAuthn/passkeys com biblioteca auditada, casos normativos e conformance suite; a fundação atual não deve ser anunciada como conformidade completa.
  • [~] Implementar política JWT de aplicação, rotação/revogação, TOTP com recovery codes e gerenciamento de sessões/dispositivos. rullst-auth/jwt agora fornece claims versionadas obrigatórias, issuer/audience, TTL/scopes limitados, chaves fortes com rotação por kid e contrato estático de revogação por token/versão de sessão; o modo de produção rejeita o store de memória local. A feature sqlite acrescenta revogação JTI/versão de sessão compartilhada entre processos locais, quota/configuração persistidas, verify_async e inventário/rename/revogação de passkeys com CAS de counter. Reinício, replay, concorrência entre duas instâncias e quotas possuem testes. rullst-security também gera recovery codes de 80 bits subject-bound, guarda apenas verificadores HMAC com salt e remove o verificador consumido. Ainda faltam refresh tokens, consumo transacional persistido de recovery codes, challenge WebAuthn compartilhado, replicação multi-host e conformance normativa.
  • Integrar KMS/HSM e rotação de chaves apenas por adapters reais e testes de falha; simuladores permanecem claramente experimentais.
  • Pesquisar PQC apenas para um protocolo e threat model concretos, usando primitivas auditadas; não criar criptografia própria.

Runtime, pagamentos, dados e SOC

  • Ampliar WAF/RASP/DLP com parsers corretos, limites explícitos, falsos positivos medidos e testes de bypass; nunca prometer cobertura total OWASP.
  • Tornar entrega SIEM externa observável e com retry/dead-letter antes de chamá-la de integração operacional. A fundação local agora tem contrato LiveSecurityEvent v1 congelado, um DurableSiemSpool compatível unsigned e um AuthenticatedSiemSpool single-process que encadeia HMAC, sequência, chave ativa/histórica, predecessor e payload sob quotas e sync_data. Restart, forgery, substituição/ausência de chave, reorder, remoção interna e mudança externa falham fechados. Checkpoint confiável contra whole-tail rollback, transporte, compaction, retenção, retry, confirmação e dead-letter continuam deliberadamente em aberto.
  • [~] Levar a NFS-e Nacional até um contrato pronto para homologação em uma campanha fiscal dedicada. O marco local agora fixa por SHA-256 os pacotes oficiais de produção v1.01-20260209 e produção restrita v1.01-20260727, modela uma DPS ordinária delimitada sem float, valida CPF/CNPJ/IBGE/limites, carrega apenas fontes XSD oficiais verificadas por hash em catálogo fechado em memória e valida a DPS gerada. Depois do hash, o perfil de produção recebe uma única normalização explícita: remove os anchors .NET ^...$ do padrão conhecido de série, pois eles seriam literais na gramática regex XSD. O certificado PKCS#12 fica em containers redigidos e zeroizados quando pertencentes ao Rullst; o signer produz XMLDSig envelopada RSA-SHA256 com C14N inclusiva 1.0 sobre infDPS/@Id, passa verificação criptográfica local e o XML assinado passa o XSD oficial fornecido. A mesma credencial constrói identidade/cliente mTLS rustls limitado a HTTPS, sem redirects e com timeouts. O codec offline agora valida a XMLDSig incorporada da DPS, gera deterministicamente o objeto JSON dpsXmlGZipB64 e distingue autorização HTTP 201 de rejeições 400/403/500 com limites estritos. Uma autorização só emerge quando ambiente, DPS submetida, chave de 50 dígitos, infNFSe/@Id e XMLDSig incorporada concordam; JSON/XML/Base64/GZip malformado, campos desconhecidos, tamper e expansão acima de 4 MiB falham fechados. Ainda faltam vínculo certificado-emissor e cadeia ICP-Brasil completos, idempotência/auditoria duráveis, fixtures oficiais retidas, teste com A1 real na produção restrita e revisão independente. Homologation e Production continuam Unsupported e sem I/O até essas evidências; parâmetros do contribuinte/município são dependências externas e nunca podem ser simulados como autorização.
  • Manter Alipay RSA2 em Unsupported até assinatura/verificação interoperável e contract tests oficiais.
  • Testar isolamento multi-tenant, SQL parametrizado, migrations, backups e restauração contra bancos reais.
  • Obter uma auditoria de segurança externa e publicar escopo, versão, achados corrigidos e limitações — não um selo absoluto.

AI segura e útil

  • [~] Versionar evals de prompt injection, PII, jailbreak, tool selection, alucinação e regressão por provedor/modelo. O corpus rullst-ai-guardrails-v1 fixa casos determinísticos de injection, jailbreak e PII, é validado por schema/IDs/categorias e roda contra os seis transports offline no CI e na release. AdaptiveAiEvaluator<P> acrescenta estratégias multi-turn por dispatch estático, feedback bounded, deadline/cancelamento, estados pass/fail/inconclusive e relatório JSON sem prompt/response/error raw; o teste usa a saída de um turno para construir o seguinte. Corpora mantidos de tool selection, groundedness/alucinação e política de output, assim como execução/revisão contra cada modelo live exato, continuam responsabilidade operacional e não são alegados por um fixture offline.
  • Criar uma matriz de capacidades por provedor para streaming, JSON/schema, vision, embeddings, tools, timeouts, retries e cancelamento. ProviderCapabilities, AiProvider::capabilities() e AiClient::capabilities() tornam o contrato verificável em código; testes fixam as seis implementações, as capacidades declaradas do adapter compatível e a restrição de schema por modelo no DeepSeek.
  • Implementar streaming/cancelamento no primeiro protocolo delimitado: StreamingAiClient<P> mantém dispatch estático, guarda o input e impõe limites independentes de chunks/bytes; o adapter OpenAI-compatible só anuncia SSE após opt-in explícito, exige text/event-stream/[DONE] e disputa o sinal AiCancellation contra request e cada leitura. Testes loopback cobrem fragmentação LF/CRLF, content type, truncamento, overflow e cancelamento em voo. Protocolos diferentes e calls não-streaming continuam corretamente abertos, e cancelar localmente não prova cancelamento/billing upstream.
  • Implementar export remoto autenticado de auditoria AI sem prometer um SIEM: AuditDeliveryClient limita o envelope JSON, assina seus bytes exatos com HMAC-SHA256/key ID/timestamp, preserva um event ID em retries transitórios delimitados, aceita apenas ACK fechado vinculado ao evento e disputa cancelamento contra request, response e espera. Cloud exige HTTPS; HTTP(S) local exige IP literal de loopback; credenciais vazias/mock_* são offline. O caller minimiza o evento e o receiver valida freshness/deduplicação e opera autorização, persistência, retenção e chaves; não há outbox/SIEM distribuído implícito.
  • Exigir autorização explícita, schema, allowlist, limites e audit trail em chamadas de tools: ToolRegistry::execute agora requer política e contexto do principal, valida JSON fechado e limitado, consome budget e falha fechado sem audit sink. Operações destrutivas/financeiras exigem aprovação de uso único vinculada ao payload exato; identidade e auditoria durável continuam contratos da aplicação, documentados em Guarded Local AI Tools.
  • [~] Bloquear SSRF e exfiltração em fetchers/RAG/conectores com egress policy, resolução segura e limites de conteúdo. EgressPolicy::strict() exige HTTPS, allowlist exata de host (vazia por padrão), porta permitida, host não local/metadata e resolução inteira para IPs públicos. EgressFetcher resolve sob deadline, fixa todas as respostas num client sem proxy/redirect automático, verifica o peer conectado, revalida cada redirect e limita tamanho declarado e chunks em streaming. Testes negam DNS privado/misto antes do transporte e cobrem formatos IPv4/IPv6, redirect privado, allowlist e overflow. O fetcher é opt-in; AiClient::prompt_with_image_url agora o exige explicitamente para imagens remotas e ainda valida assinatura/MIME, enquanto arquivos locais exigem raiz canônica allowlisted. Ele não envolve transports de provider, RAG ou clientes arbitrários; autorização tenant-aware do destino, decodificação segura e contrato live de sucesso/redirect continuam abertos.
  • Separar filtros heurísticos de garantias: GuardrailReport agora expõe passed_heuristics, mantém is_safe somente como alias depreciado e a integração de IA declara que filtros, schema e similaridade não tornam prompt, resposta, tool ou decisão confiável por definição.

Supply chain

  • Workflows usam actions pinadas por SHA e a release gera SBOM/evidência delimitada.
  • Provar o pipeline completo de provenance e Trusted Publishing na RC.
  • Definir SLA por severidade para advisories e política de exceções com owner, controle compensatório e expiração; dois ignores obsoletos foram removidos dos workflows junto com suas dependências.
  • Manter fuzzing, Kani, Miri, sanitizers e mutation testing com alvos e resultados descritos precisamente; nenhum deles prova segurança universal. As matrizes agora distinguem diagnóstico rápido, achado informativo e falha de infraestrutura/baseline, mas ainda faltam os resultados do SHA final.

8. Experiência local: Nexus e Studio em um clique

A conveniência deve existir sem transformar uma configuração esquecida em uma porta administrativa de produção.

  • Criar NexusAuthPolicy::local_development_or_basic_from_env(): desenvolvimento usa loopback verificado; release exige credenciais válidas do ambiente.
  • Reutilizar LocalNexusAccess, que recusa peers não-loopback e requests sem ConnectInfo.
  • Ligar o helper nos blueprints Blog, Portfolio, LMS, ERP e SaaS.
  • Iniciar o Studio standalone em 127.0.0.1:5555 apenas em builds de debug geradas pelos blueprints; Studio::into_router exige agora a capability explícita LocalStudioAccess, recusa builds release e cada request sem peer loopback verificado por ConnectInfo. A capability também valida Host local, exige Origin de mesma origem nos métodos mutáveis e rejeita mutações sem Origin, cobrindo o limite local contra DNS rebinding e CSRF.
  • Remover estados operacionais fabricados do Studio: AI sem transporte conectado fica indisponível; segurança não deduz guardas ativos ou alcance de provider por variáveis de ambiente; receita só exibe métricas fornecidas pela aplicação; filas propagam operação não suportada; e as ações HTTP legadas de migration/seed retornam 501 porque o servidor standalone não recebe um registry executável.
  • Tornar purge_failed_jobs o nome canônico da operação da fila e fazer os defaults de listagem/retry/purge falharem com Unsupported, em vez de retornarem sucesso vazio. O alias legado purge_completed_jobs permanece depreciado apenas para compatibilidade de fonte.
  • Fixar o asset CDN do Scalar, escapar a URL OpenAPI, tornar seu fallback status-only e recusar documento ausente/malformado com 503; corrigir build:client para falhar fechado quando o toolchain Wasm falta, mesclar cdylib via TOML estruturado, respeitar lib.name, localizar apenas um artefato real e gerar um hidratador separado que aguarda a inicialização do wasm-bindgen.
  • Exibir botões claros para Studio local e Nexus nos dashboards aplicáveis.
  • Atualizar examples/blog: Studio standalone local, Nexus de um clique em debug e sem credenciais fictícias obrigatórias no setup. A auditoria do showcase também adicionou CPU real do processo no Windows, atualização dos KPIs por /api/radar, corrigiu a dupla contagem de prompts e removeu selos HMAC positivos quando a fonte contém somente eventos locais não assinados. Os seis botões do sandbox de segurança e o /wp-admin agora têm regressão que prova a execução das primitivas locais instrumentadas; a interface deixou de transformar uma detecção em falso 403, chamada de provider, garantia universal de timing ou nota de scanner.
  • Remover da interface do Studio a sugestão incorreta de que STUDIO_PASSWORD já fornecia autenticação embutida.
  • Remover os aliases legados /tools/*; todas as páginas do Studio usam as rotas limpas /studio/* exigidas pela especificação.
  • Acrescentar ingestão distribuída sem abrir o painel remotamente: cada produtor recebe um router push-only separado, vinculado ao seu nome e chave, que aceita lotes v1 sem atributos sob 128 KiB/128 spans, autentica corpo/source/timestamp/nonce por HMAC-SHA256, rejeita replay atomicamente e alimenta um store in-process limitado e idempotente. O viewer local sinaliza operações SQL de 100 ms e três labels repetidos por trace como heurísticas, sem receber SQL, bindings, headers, bodies ou erros. TLS, rotação da chave, relógio, autorização do produtor e durabilidade continuam responsabilidades da aplicação/operação.
  • Concluir o inspetor de cache no limite seguro: Core fornece snapshots metadata-only de no máximo 200 entradas para Memory/Redis, com contrato Redis live pinado; Studio exibe no máximo 100 fingerprints HMAC process-local, tamanho e TTL, nunca valor ou chave lógica, e permite somente invalidação individual atrás da marker local verificada. flush all não é exposto e drivers custom falham explicitamente sem opt-in.
  • Padronizar novos .env, Kubernetes e Foundry em RULLST_ENV, corrigir a precedência do template de billing e manter APP_ENV apenas como alias legado testado para aplicações existentes.
  • Tornar o Foundry fail-closed nas etapas de build, provisionamento, upload, configuração e health check: TOML e identificadores são validados, segredos seguem por stdin, curl, systemd e Caddy precisam estar previamente instalados, a configuração candidata é validada e os arquivos da aplicação ficam isolados em /opt/rullst/<app>. O sucesso local não é apresentado como prova de DNS/TLS público. A versão atual substitui o Caddyfile global e ainda exige root ou sudo não interativo; não promete migrations, checksum remoto, rollback automático nem zero downtime.
  • Rodar a matriz estrutural das 18 formas públicas da v12, materializar os seis blueprints públicos em diretórios temporários e compilar os projetos representativos SaaS e Blank/hot. A antiga matriz de 270 variantes permanece apenas como evidência histórica da fase anterior à simplificação.
  • [~] [!] Os gates lentos materializam dez casos que cobrem os seis blueprints, Active Record, SSR html!/HTMX, API, banco e hot reload. Seis executam cargo check --offline --all-targets; o LMS executa cargo test --offline --all-targets, inclusive o negativo owner/cross-user embutido no scaffold, e os perfis LMS separados auth, auth,learning e auth,learning,assessment também passam seus testes gerados. O último prova apresentação sem gabarito, owner boundary, correção autoritativa, replay, conflito de idempotência e limite de tentativas em SQLite, sem materializar gamificação, automação, outbox ou notificações. O middleware JWT injetado usa a mesma major 11 do workspace, permitindo a resolução offline num runner limpo; relatórios de upgrade e chaves de storage também preservam / canônico no Windows. Todos passaram localmente em 2026-08-29, mas ainda falta CI verde no SHA final da RC.
  • [~] Um dos dez projetos gerados passa cargo check --release; testes release focados provam que Nexus nunca escolhe a política sem credenciais e que Studio recusa sua capability local. O job generated-release-access está configurado; falta a evidência de CI no SHA final e um E2E do processo gerado sem variáveis.
  • Criar E2E de navegador para os dois botões e para a rejeição de peer externo.
  • Medir tempo até a primeira página, primeiro CRUD e primeiro diagnóstico de erro com usuários novos.

9. Correção responsável do changelog

O changelog é registro histórico; apagar silenciosamente alegações extraordinárias seria tão ruim quanto mantê-las como fatos atuais. A correção adotada é preservar a alegação original e acrescentar imediatamente o escopo auditado:

- **Alegação original de desenvolvimento:** descrição histórica.
  - **Escopo auditado na v12:** o que o código e os testes realmente sustentam.
  - **Ambição restante:** o que falta e se vale implementar.
  - **Evidência:** teste, workflow, issue ou documento vinculado à versão/commit.
  • Adicionar no topo de v12 um aviso de que o inventário não é certificação nem evidência congelada de testes.
  • Corrigir ao lado, sem apagar, as alegações de latência zero, Alipay RSA2, Kani universal e imunidade a DoS por fuzzing.
  • Todas as afirmações históricas de 100% test pass rate agora trazem ao lado que são alegações de execução histórica cujo commit/artefato exato não foi retido nessa entrada; não são tratadas como cobertura nem prova da RC.
  • [~] As entradas atuais de SECURITY.md, headers, DLP, RASP, compliance, Kani, SLSA e inventário TLS foram delimitadas sem apagar os controles; ainda falta concluir a mesma revisão de linguagem absoluta em toda a documentação histórica.
  • [~] As páginas canônicas de Capital e IoT agora distinguem adapters/helpers implementados, mocks offline, homologação e hardware/boot/transporte pendentes. A auditoria item a item de SIEM, OpenAPI/SDK, deploy, replicação e de cada método/fee/provedor continua aberta.
  • Manter correções também nas seções de versões já lançadas; o cabeçalho de v12 não deve reescrever silenciosamente a história.
  • Ligar cada claim de segurança de v12 a código, teste e limite conhecido no ledger canônico de alegações e evidências; o documento também declara que uma afirmação ausente não é garantia da release.

9.1. Preservação da documentação original

  • Identificar por histórico Git o snapshot imediatamente anterior à criação de gpt.md: 96222fbd31bec3d20bc50db68c41bb85ca595779.
  • Criar um índice de preservação com o snapshot exato, as famílias de ambições recuperadas, estado atual, opinião e destino canônico.
  • Manter as ideias nos roadmaps e manter spec/tutoriais copiáveis limitados ao comportamento atual.
  • Excluir tutoriais antigos da reconstrução por decisão do mantenedor; eles permanecem apenas em sua versão atual e segura.
  • Classificar individualmente as 190 alegações históricas deduplicadas dos roadmaps; os quadros auditados abaixo prevalecem sobre [x] antigos e medem classificação, não implementação integral.
  • Revisar toda remoção futura de conteúdo contra o snapshot e registrar no índice quando houver valor de visão ou de decisão.

10. Prova operacional

  • Publicar a RC e criar uma aplicação de referência que dependa apenas de crates.io.
  • Operar deploy, migration, backup, restore, rotação de segredo e rollback num ambiente semelhante à produção.
  • Executar testes de carga reproduzíveis e publicar hardware, configuração, dataset, percentis e limites; evitar “o mais rápido” sem comparação válida.
  • Ter usuários externos construindo aplicações sem acesso ao monorepo e registrar problemas de onboarding.
  • Ter ao menos um ciclo de RC com correções observadas em uso real.
  • Definir manutenção, triagem de vulnerabilidades, resposta a incidentes e suporte para a linha v12.

11. Gates para 12.0.0 estável

A versão estável só recebe GO quando todos estes itens estiverem fechados:

  • [!] Trifecta local e CI multi-OS verdes no mesmo SHA.
  • [!] Pacotes e aplicação crates-only reproduzidos.
  • [!] Sem advisory crítico/alto sem exceção governada e sem segredo no pacote.
  • [!] Baseline de produção e threat models revisados.
  • [!] ZAP hospedado verde no SHA da RC para REST API e LMS gerados, com os relatórios do showcase informativo revisados e sem transformar achados reais em exclusões.
  • [!] Scorecard no SHA da RC com A (90) ou mais em todas as crates, salvo a exceção aprovada de B (80) ou mais para rullst-iot. A+ só pode ser publicado quando a evidência objetiva alcançar 97. No estado atual, Core tem 96/A, ORM tem 96/A, ORM Macros, Auth, Mail e Nexus têm 95/A, Macros tem 94/A, Messaging e Security têm 96/A, Connect e AI têm 95/A, a facade rullst tem 90/A, e IoT tem 83/B. Capital alcançou seu teto local de 93/A com o ledger relacional de webhook, o contrato compartilhado de egress/falhas dos gateways e o journal fiscal local autenticado. cargo-rullst alcançou seu teto local de 95/A com a matriz estrutural pública, oito aplicações geradas executando testes e construindo os routers hot selecionados, perfis pelo binário público e fixtures transacionais de upgrade v5/v6/v11 com rollback, retenção/restauração e rejeição de symlink. Studio alcançou seu teto local de 94/A com ingestão de traces push-only autenticada/replay-bound, profiler de labels limitado, inspeção metadata-only de cache Memory/Redis e invalidação individual opaca. ORM alcançou seu teto local de 96/A com snapshots documentais autenticados e limitados, verificação fail-closed de inventário e ensaio live bidirecional MongoDB → SurrealDB → MongoDB, sem prometer snapshot online, PITR ou operação gerenciada. Security alcançou seu teto local de 96/A com journal SIEM local opcional HMAC-chained, rotação explícita por chave ativa/histórica e negativos exatos de forgery, substituição de chave, reorder, remoção interna e restart, sem prometer checkpoint externo, entrega/ack remotos, SOC ou certificação. Assim, todos os pisos atuais estão atendidos. Esses valores são tetos atuais, não metas finais: a campanha v12 pretende alcançar os tetos locais provisionais documentados no scorecard (93–96/A nas 15 crates ativas); IoT permanece na exceção aceita de 83/B, sem fabricar os pontos que dependem de hardware, homologação, operação live ou auditoria externa. O gate permanece aberto até haver implementação real e o artefato do SHA candidato comprovar todos os gates condicionantes.
  • [!] Na estável, line coverage das bibliotecas do framework ≥90%, patch coverage ≥90%, componentes Auth e Security ≥90%, cobertura total do código de produção publicada sem omissões, nenhum caminho crítico com 0% e baseline de branches publicada. 95% permanece meta de excelência, não gate artificial.
  • [!] Changelog, spec, capability ledger, README e exemplos concordam sobre o que existe e o que não existe.
  • [!] Concluir a auditoria documental da RC: o mdbook build já passa e o scan Lychee repository-wide verificou 1.228 referências, 695 únicas, com 769 válidas, 459 exclusões offline intencionais e zero erro em 2026-09-01. O workflow documentation.yml torna o build e os links/anchors locais bloqueantes, mantendo o scan externo periódico/manual como evidência informativa. Os 52 tutoriais contêm exatamente 106 exemplos Rust: o harness Cargo-aware compilou/executou 88 e os 18 ignore restantes declaram a dependência contextual (módulo/arquivo gerado, adapter da aplicação ou dependência circular), sem serem contados como exemplos compilados. O livro completo contém 203 exemplos reconhecidos pelo rustdoc — incluindo três blocos indentados no spec.md que a contagem anterior de fences omitia — e o mesmo gate compilou/executou 177, classificou 26 como contextuais e terminou sem falhas. No recorte fora dos tutoriais, são 97 exemplos: 89 verificados e oito contextuais. Repetir a evidência no SHA congelado e validar também comandos, versões, caminhos e toda alegação semântica contra spec.md e as APIs reais.
  • [!] RC operada por tempo suficiente e sem bloqueadores conhecidos.
  • [!] Plano de publicação parcial, rollback operacional e resposta a incidentes ensaiados.
  • [!] Aprovação humana final registrada.

12. Rullst Academy: fronteira externa futura

O plano do produto separado está em Rullst Academy product programme. Esta seção preserva o inventário de capacidades reutilizáveis do framework; não transforma conteúdo, sandbox, operação ou UX da aplicação em escopo implícito da crate rullst.

O objetivo vertical de longo prazo é permitir que uma equipe pequena construa um LMS excelente, divertido e seguro sem remontar autenticação, progresso, ranking, automações e operação a partir de primitivas desconectadas. Este inventário não amplia a v12 nem cria trabalho de Academy neste repositório: o produto será desenvolvido em outro repositório e outra conversa. Nenhum item desta seção bloqueia a RC ou a estável; quando o projeto externo existir, seu link poderá ser acrescentado à documentação do framework.

Os estados abaixo são um registro arquivado do que já existe e do que caberá ao futuro produto externo. Caixas abertas nesta seção não são tarefas do workspace, não entram no percentual de prontidão da v12 e não devem ser implementadas aqui.

“Superar Laravel” significa vencer uma aplicação de referência equivalente em tempo até o primeiro fluxo seguro, clareza de código, consumo de recursos, falhas fechadas, cobertura adversarial e facilidade de operação. Não significa declarar superioridade universal nem comparar benchmarks, hardware ou escopos diferentes. Recursos verticais devem permanecer em módulos opcionais ou numa aplicação de referência; somente abstrações comprovadamente reutilizáveis devem entrar no Core.

Produto de referência e fronteira arquitetural

  • [~] O blueprint LMS gera as fundações de currículo, identidade, matrícula, progresso, avaliação, atividade, conquista, leaderboard e automação, além de catálogo público, cadastro/login, matrícula idempotente, entitlement de aula, progresso monotônico com histórico/correção auditada, player protegido com variantes de vídeo/áudio, legenda WebVTT obrigatória no vídeo, transcrição e idioma delimitados, e administração Nexus. O projeto materializado passa cargo test --offline --all-targets, inclusive owner/cross-user. O starter agora possui uma fronteira escolar persistida e testada nas mutações críticas, ainda deliberadamente incompleta nos demais subsistemas; os arquivos WebVTT de demonstração são locais, mas a mídia seed continua remota apenas para desenvolvimento e exige política CSP explícita. O quiz single-choice já é avaliado no servidor, inclusive com início/expiração e ordem aleatória persistidos quando temporizado. O worker possui loop supervisionado, shutdown explícito e métricas locais; conclusão é derivada do snapshot fixado e emite certificado público revogável; tarefas textuais e rubricas persistidas já têm submissão/feedback autenticados e limites do servidor. Jogos, canais externos, billing, UI de autoria e tenancy transversal não são fluxos completos.
  • Manter a aplicação oficial Rullst Academy em repositório separado, dependente somente dos pacotes publicados e sem paths do monorepo, e operá-la como consumidor real durante a RC/estável. O checkout, os artefatos e a matriz E2E da Academy não pertencem ao workspace nem aos pacotes do framework; o monorepo conserva apenas contratos reutilizáveis, scaffolds pequenos e testes de compatibilidade proporcionais.
  • [~] Manter o blueprint LMS pequeno e rápido, permitindo selecionar módulos opcionais (auth, learning, assessment, gamification, automation, realtime, billing) em vez de incorporar um produto monolítico ao Core. O CLI aceita agora --lms-modules auth, --lms-modules auth,learning ou --lms-modules auth,learning,assessment com o blueprint LMS. O primeiro gera uma fronteira de identidade com menos de 15 arquivos — usuário, sessão, login/registro, CSRF/headers, middleware e Nexus — sem modelos de curso/matrícula. O segundo gera um perfil foundation real com menos de 30 arquivos: autenticação, catálogo, matrícula, player protegido e progresso monotônico/idempotente, sem arquivos de quiz, conquista, automação ou notificação. O terceiro fica abaixo de 40 arquivos e acrescenta somente quiz, questão, opção, tentativa/resposta, endpoints owner-only e correção transacional autoritativa com regras versionadas, chave idempotente e teto de tentativas; ele não inclui score, leaderboard, conquista, outbox, automação ou notificação. Cada seleção fica registrada em rullst-lms-modules.json e os três projetos materializados passam cargo test --offline --all-targets. O starter completo continua sendo o default. Gamification, automation, realtime e billing ainda não estão desacoplados em combinações independentes; combinações não suportadas e hot reload desses perfis falham explicitamente em vez de incluir módulos silenciosamente.
  • [~] Versionar uma jornada de aceitação completa: professor publica, aluno se registra e matricula, autorização protege conteúdo, progresso é salvo, uma avaliação gera pontuação idempotente, ranking muda, conquista dispara automação e notificação, e o administrador audita o fluxo. O teste SQLite materializado já cobre publicação versionada e pin de matrícula, além do slice score → leaderboard → outbox → claim → regra rederivada → conquista transacional → redelivery idempotente → ACK, poison retry/dead-letter e correção administrativa. O mesmo teste cobre avaliação autoritativa e sua projeção transacional em ScoreEvent, leaderboard e outbox, conquista e notificação in-app entregue. O mesmo fluxo deriva conclusão apenas depois dos requisitos da versão fixada, emite certificado opaco sem PII pública, prova replay e entrega os envelopes de conclusão/revogação pelo worker. Uma continuação cobre tarefa textual → submissão owner-only → avaliação humana limitada pela rubrica persistida → feedback/notas por critério → outbox, com prazo, replay, adulteração de pontos e fronteira HTTP. A autoria agora possui handlers autenticados para draft, envio a revisão, revisão/agendamento e rollback administrativo auditado, sempre usando ator e relógio do servidor. O rollback publica uma nova revisão a partir de um snapshot histórico sem mover matrículas existentes. Registro, autoria visual no Nexus e player/quiz no navegador ainda não formam uma única jornada E2E.
  • Publicar, fora do monorepo do framework, uma implementação Laravel de referência com o mesmo domínio, dataset, frontend, banco, cache, hardware e testes para comparar DX, latência, throughput, memória, segurança e operação sem cherry-picking. Nenhuma das duas aplicações de benchmark integra o pacote ou o gate cotidiano da RC.

Domínio de aprendizagem e autoria

  • [~] Modelar organização/escola, tenant, usuário, instrutor, aluno, curso, módulo, aula, turma/coorte, matrícula e entitlement com migrations reversíveis, constraints e políticas de ownership explícitas. O starter agora gera School, membership com janela/default, vínculo único curso→escola, coorte, membership de coorte e entitlement temporal, com índices de unicidade/consulta e down migration. O middleware resolve X-School-ID somente como seletor contra memberships persistidas, vincula o tenant ao UserContext e falha fechado em membership ausente/ambígua; papéis são persistidos e consultados por escola. Curso aberto ou entitlement ativo controla matrícula. Ainda faltam provisionamento/invites, políticas de responsável, constraints FK portáveis, ciclo de entitlement pago e a aplicação Academy separada.
  • [~] Implementar progresso transacional e idempotente por aula/atividade, com retomada, percentual, histórico, correções administrativas auditadas e concorrência testada. O starter usa chave explícita de idempotência, upsert monotônico por banco, histórico append-only, correção exclusiva de admin e evento lesson_completed no mesmo commit. O teste materializado cobre replay, regressão ignorada, conclusão e correção; faltam atividade além de aula e contenção contra os três bancos reais.
  • [~] Implementar pré-requisitos, liberação gradual, datas, expiração, certificados verificáveis e conclusão derivada de regras versionadas. O starter agora exige exatamente uma política ativa versionada por aula, aplica liberação/expiração pelo relógio do servidor e valida progresso do pré-requisito no mesmo curso. Política ausente, duplicada, inconsistente ou cross-course falha fechada; o teste materializado cobre bloqueio, liberação, janela e conflito. Versões publicadas são snapshots imutáveis e matrículas criadas pelo serviço fixam a versão corrente. A conclusão agora lê uma regra v1 fechada do snapshot fixado, exige todo progresso persistido e grava evidência/certificado/outbox atomicamente; replay reutiliza o mesmo certificado. Verificação por chave opaca não expõe o aluno, e revogação exige admin, motivo, relógio do servidor e chave idempotente. O teste cobre incompletude, cross-user, replay, verificação, revogação conflitante e entrega. Ainda faltam servir todo conteúdo pelo snapshot, regras mais ricas e contenção nos três bancos reais.
  • [~] Implementar questionários, banco de questões, tentativas, limites de tempo, embaralhamento, rubricas, tarefas, submissões e feedback sem confiar em resultados calculados apenas no navegador. O starter persiste quiz, questões, opções, tentativas e respostas; autoriza o aluno matriculado, calcula a nota pela chave do servidor, limita tentativas, vincula replay a quiz/aluno/ruleset e grava tentativa, respostas, ScoreEvent, atualização do leaderboard, score_recorded e quiz_graded no mesmo commit. O teste rejeita cross-user e opção inexistente e comprova as contagens/replay da projeção. Quizzes temporizados exigem início persistido no relógio do servidor; replay não estende o prazo, tentativa iniciada consome o limite, e submissão ausente ou expirada falha fechada. O início temporizado persiste uma ordem de questões/opções derivada de seed aleatório do servidor e vinculada ao ruleset; replay devolve a mesma ordem e alteração silenciosa de IDs falha no grading. Rotas autenticadas de início e submissão derivam quiz/aluno da path/sessão e nunca aceitam pontos do cliente. O starter também persiste tarefas, critérios de rubrica, submissões, avaliações e notas por critério. A submissão exige o owner matriculado, prazo/tentativas do servidor e chave idempotente; a avaliação exige evaluator/instructor/admin distinto do aluno, cobre exatamente os critérios persistidos, rejeita pontos acima do máximo, guarda feedback e vincula replay ao request canônico. Submissão e avaliação emitem outbox na mesma transação. Correções administrativas são append-only: preservam a nota original, revalidam a mesma rubrica, registram before/after, motivo, ator, relógio, request canônico e outbox, e effective_grade retorna a correção mais recente. O teste materializado cobre prazo, cross-user, replay/conflito, nota impossível, negação de evaluator na correção, HTTP e evidência rubricada. Ainda faltam anexos, autoria visual, UI e contenção multi-banco.
  • [~] Implementar rascunho, revisão, publicação, agendamento e versionamento de conteúdo sem alterar silenciosamente a experiência de alunos já matriculados. O starter fornece snapshots CourseVersion append-only, transições draft → review → scheduled/published, revisor admin distinto do autor, arquivamento atômico da versão anterior, course_published transacional e pin imutável da versão na matrícula. O teste prova pin antes/depois da publicação e entrega FIFO dos eventos. Handlers autenticados expõem draft, submissão e revisão/agendamento, derivando ator e relógio no servidor, e o teste materializado percorre essa fronteira até published. Um loop supervisionado agora ativa versões vencidas em lotes limitados sob lease compartilhada, renova/libera o token exato, preserva o revisor independente, emite o ator da ativação e expõe shutdown e contadores locais. O teste SQLite prova espera sob contenção, ativação no relógio do servidor e replay sem nova publicação. Rollback administrativo exige motivo e chave idempotente, cria uma nova revisão imutável a partir de uma versão histórica, arquiva a versão corrente e grava auditoria/outbox na mesma transação. Replay exato não duplica; pins já existentes permanecem na versão anterior e novas matrículas recebem a revisão restaurada. O teste cobre negação de instrutor, conflito de chave, fronteira HTTP e evidência durável. Ainda faltam autoria visual no Nexus, snapshot relacional completo de módulos/aulas/avaliações, métricas exportadas/alertas e contenção nos bancos reais.
  • [~] Entregar busca, filtros, navegação por teclado, contraste, legendas, transcrições, localização e critérios WCAG testados na aplicação de referência. Os perfis LMS completo, auth,learning e auth,learning,assessment agora geram catálogo SSR com busca de título e categoria parametrizadas pelo ORM, entrada limitada, ordenação e limite de resultados. O shell remove fontes/scripts/imagens externas, consome o CspNonce da baseline, não usa estilos inline ou HTMX remoto e inclui skip link, landmarks, foco visível, layout responsivo, contraste explícito e redução de movimento; aula e autenticação também recebem o nonce, e o player não inicia mídia automaticamente. O modelo de aula distingue vídeo/áudio; o renderer aceita somente HTTPS ou path absoluto same-origin, exige track WebVTT em vídeos e uma transcrição delimitada para ambos. A regressão materializada cobre filtro, valor com formato de SQL injection, limite, escaping de HTML, identidade do nonce, vídeo/áudio e rejeição de source, idioma, legenda ou transcrição inválidos. Ainda faltam busca textual avançada, localização do catálogo, revisão humana da qualidade de legendas/transcrições, testes automatizados WCAG/browser/teclado e a aplicação de referência externa operada com usuários reais.
  • [~] Implementar mídia remota real com S3/R2, uploads multipart limitados, allowlist de tipos, URLs assinadas, CDN e processamento assíncrono. Core agora fornece o contrato reutilizável de admissão/quarentena com limite rígido, allowlist, assinatura real versus MIME/extensão, chave aleatória por tenant, digest SHA-256 e liberação somente após verdict limpo. Ainda não existem streaming multipart, driver S3/R2, URL assinada, CDN, transcode ou pipeline assíncrono; o storage local continua apropriado apenas para desenvolvimento e instalações simples.

Jogos, gamificação e ranking confiável

  • [~] Definir um contrato de atividades/jogos com estado, versão, tentativa, regras, resultado e evidência de conclusão; começar por quizzes e minigames assíncronos antes de prometer multiplayer de baixa latência. O scaffold gera ActivityAttempt/ActivityResult v1 para quiz, exercício e jogo, vinculando owner, activity, attempt, ruleset, ordem temporal, estado limitado, score e digest SHA-256 de evidência. evaluate_activity usa dispatch estático e não recebe pontos do cliente: um ActivityEvaluator confiável transforma apenas a submissão em outcome e o contrato constrói o resultado. O primeiro SingleChoiceEvaluator cobre exercício correto/incorreto; o MatchingEvaluator exige uma permutação completa de até oito pares, normaliza sua ordem e calcula pontuação parcial a partir do mapa do servidor; e o TypedAnswerEvaluator compara um conjunto fechado após trim/lowercase Unicode opcional, limita a entrada a 512 bytes sem controles e persiste somente um digest SHA-256 ligado à política. Ele não promete NFC, tolerância de acento ou fuzzy matching. A regressão materializada prova owner, kind, JSON, relógio, limites e rejeições. No starter completo, record_activity_result aceita somente o resultado validado opaco, rederiva curso, kind, máximo, ruleset, temporada e digest da atividade persistida e grava ScoreEvent v2, leaderboard e score_recorded na mesma transação. A mesma transação agora conserva a tentativa/resultados delimitados e exige replay exato, revalidando sob lock a configuração exata usada pelo avaliador. POST /activities/{id}/attempts aceita apenas chave idempotente/opção e POST /activities/{id}/attempts/matching apenas a chave e IDs dos pares; ambos se somam a /activities/{id}/attempts/typed, que recebe texto delimitado sem retê-lo em claro. Todos derivam aluno/activity do contexto/path e mantêm respostas, pontos, ruleset, digest e relógio no servidor. A regressão rejeita ator, digest, IDs duplicados ou retry divergente e prova owner/cross-user no boundary HTTP. O estado retido continua sujeito à política de privacidade da aplicação. O avaliador de quiz persistido permanece separado; ainda faltam listening e jogos.
  • Tornar explícita a fronteira do cliente para jogos: o scaffold gera ActivityClientManifest, cujo default ssr_htmx não aceita bundle e cujo opt-in canvas_wasm exige paths same-origin, artefato .wasm, SHA-256 canônico e tamanho máximo de 16 MiB. Ambos continuam subordinados à validação autoritativa do resultado no servidor; nenhum bundle pesado é dependência do LMS padrão.
  • [~] Criar ScoreEvent autenticado, versionado e idempotente, com origem, tentativa, regra, pontos, timestamp e chave de deduplicação. O scaffold deriva o ator de UserContext, autoriza owner/admin, valida schema, origem, IDs, chaves e limites server-side, grava o evento append-only e atualiza a fonte de verdade do leaderboard na mesma transação. O avaliador autoritativo de quiz agora cria um ScoreEvent v2 por tentativa e a mesma projeção transacional, sem aceitar pontos do cliente. O exercício single-choice também chega a essa projeção somente por um ValidatedActivityResult não construível pelo chamador; a ponte revalida a política persistida e vincula o digest canônico. Índices únicos tornam replay exato de evento/tentativa no-op e um retry com outra opção/pareamento falha fechado. Os endpoints autoritativos de quiz, single-choice, matching e typed answer já existem; ainda faltam endpoint/recomputadores para jogo/listening e testes de concorrência contra os três bancos reais.
  • [~] Fornecer revisão espaçada durável sem transformar heurística em alegação pedagógica. Atividades podem habilitar a política versionada rullst-box-v1: na entrada de um score autoritativo novo, a mesma transação trava e valida política/estado, calcula uma transição determinística de aprovação ou lapso e grava o próximo vencimento. Replay exato retorna antes da transição e não avança a agenda. GET /reviews/due deriva usuário e tempo no servidor, limita a fila a 50 e revalida escola, escopo do curso e matrícula ativa. A regressão SQLite materializada cobre single-choice, matching, typed, persistência, ordenação futura, replay e cross-user. Ainda faltam UX visual, migração/experimentação de algoritmos, integração completa ao ciclo de privacidade, listening/speech e contenção PostgreSQL/MySQL; não alegamos compatibilidade FSRS/SM-2, eficácia ou personalização por IA.
  • [~] Implementar leaderboard com fonte de verdade no banco, temporadas, escopos, desempate determinístico, correção auditada e cache Redis opcional reconstruível. O scaffold atualiza a tabela autoritativa somente quando um ScoreEvent novo entra na transação e consulta curso/temporada com limite 100 e ordem total score DESC, updated_at ASC, user_id ASC. Uma projeção local reconstruível usa TenantCache, valida o escopo do payload e é invalidada depois de score, quiz e correção; o banco continua autoritativo. Correções exigem admin, motivo e ruleset, têm chave idempotente e gravam ator/before/after na mesma transação. Mutações de score/correção e a leitura do ranking validam o school scope autenticado, com negação materializada para escola estrangeira; ainda faltam Redis distribuído/failover e testes nos bancos reais.
  • [~] Implementar XP, níveis, badges, conquistas, streaks, desafios e recompensas por regras versionadas, evitando incentivos manipuláveis ou inacessíveis. O starter agora aplica award_achievement a partir de uma regra v1 e de um outbox claimado, com unicidade learner/achievement, ator/evento registrados e execução idempotente na mesma transação. XP acumulado, níveis, streaks, desafios, recompensas, acessibilidade e políticas antifarming continuam abertos.
  • [~] Criar testes de replay, concorrência, adulteração, relógio, pontuação impossível, automação duplicada e abuso de múltiplas contas antes de anunciar ranking competitivo ou premiação. As regressões geradas já cobrem replay de score/correção/outbox, owner cross-user, schema futuro, pontuação impossível, chaves idempotentes, perda de ACK/redelivery sem conquista duplicada e poison-event dead-letter. Ainda faltam contenção real nos três bancos, relógio adversarial e cenários de farming/múltiplas contas.
  • Tratar multiplayer em tempo real como capability separada: autenticação de canal, servidor autoritativo, presença distribuída, reconexão, ordenação, backpressure e testes de carga são obrigatórios antes de suporte público.

Automações, eventos e notificações

  • [~] Core já possui scheduler e filas SQLite/Redis com leases, retry e dead-letter, e Mail oferece entrega em background. Falta uni-los numa jornada de domínio LMS com semântica operacional documentada; o starter fornece iteração DB claim→plan→execute→ACK/fail e um loop supervisionado com shutdown, métricas locais e entrega in-app com projeção realtime local tenant-scoped. A publicação agendada também possui loop supervisionado próprio, com lote limitado e liderança por lease exata. Integração com Mail/push, realtime distribuído e telemetria operacional permanece aberta.
  • [~] Criar eventos de domínio e transactional outbox para que conclusão, pontuação, conquista, matrícula e publicação não sejam perdidas entre o commit do banco e a fila; handlers devem assumir entrega ao menos uma vez. A gravação de ScoreEvent agora inclui um score_recorded v2 estrito no academy_outbox dentro da mesma transação; chave única impede duplicação no replay, seu digest liga a evidência à política persistida e Nexus expõe o estado somente leitura. Progresso concluído também grava lesson_completed transacionalmente. O serviço gerado usa lease limitado, vincula ACK/falha à claim exata, agenda backoff limitado, recupera claim expirada, rejeita token obsoleto, incrementa tentativas e move para dead_letter no limite; o teste SQLite materializado cobre esses estados. Matrícula, conquista, publicação, rollback editorial, submissão/avaliação/correção de tarefa, conclusão e revogação de certificado também emitem eventos no mesmo commit, e o worker valida/entrega esses envelopes versionados, inclusive lesson_completed. Cada evento carrega school_id; eventos derivados, regras, execuções e conquistas preservam o escopo, e uma regra fixture de outra escola não reage ao evento. Faltam transporte externo, eventos de alterações editoriais intermediárias e telemetria durável.
  • [~] Criar regras de automação trigger/condition/action com schema, versão, dry-run, limites, idempotência, autorização, audit trail e aprovação humana para ações destrutivas ou financeiras. O starter agora transforma o outbox score_recorded v2 e regras v1 estritas em planos puros, ordenados e com execution_key determinística, aceitando somente award_achievement e falhando em campos, versões, scores ou ações desconhecidas. Um threshold global válido acima do máximo de uma atividade agora produz zero ações, sem envenenar o evento. O executor exige a claim exata, busca somente regras da escola do evento, rederiva o plano atual e grava execução/conquista no mesmo school_id; redelivery vira no-op. Ainda faltam transporte, backoff/reaper de claims, auditoria externa e envelopes de autorização/aprovação para novas ações. Lease, backoff e recuperação de claim abandonada pertencem ao outbox. O loop supervisionado usa configuração validada, tokens de claim únicos, shutdown aguardável, aborto seguro no drop e contadores locais; ainda faltam exportação de métricas/alertas, transporte e prova de contenção multi-instância nos três bancos.
  • [~] Unificar notificações in-app/database, realtime, e-mail e push sob um contrato por usuário/tenant, com preferências, localização, opt-out, retry e estado de leitura. O starter emite achievement_awarded no mesmo commit da conquista e o worker cria uma notificação in-app idempotente a partir da claim exata, com chave de localização, locale/preferência, estado unread/suppressed/read e leitura owner/admin. Rotas autenticadas oferecem listagem filtrada/paginada, leitura idempotente e preferência/locale/opt-out, sempre derivando o sujeito da sessão. Tabelas, consultas e preferências levam school_id; o teste materializado prova que o mesmo usuário em duas escolas não observa a notificação alheia e mantém preferências independentes. Uma assinatura autenticada usa TenantRealtime; após o commit de uma notificação nova e não suprimida, o processo publica um envelope limitado na sala do tenant/usuário, e a jornada materializada valida o recebimento. O banco continua fonte de verdade; o broadcast é uma projeção best-effort local. O evento de conquista usa um catálogo fechado/versionado com renderização em português, espanhol e inglês, fallback determinístico para inglês e texto projetado tanto na listagem quanto no realtime; payload ou chave desconhecida falha explicitamente. Ainda faltam catálogos para os demais eventos, transporte realtime distribuído/replay, Mail, push e operação.
  • [~] Impedir execução duplicada do scheduler em múltiplas instâncias por lock distribuído/lease, e expor atraso, falha, retry e dead-letter no Studio. O starter agora fornece lease compartilhada no banco com chave/holder/token, expiração, renovação e liberação compare-and-set; o teste bloqueia concorrente, rejeita token obsoleto e permite takeover somente após expirar. O snapshot é consultável/Nexus readonly. O scheduler de publicação usa essa lease em cada ciclo, renova antes de toda mutação, falha fechado se perder liderança, limita lote e oferece shutdown aguardável, abort seguro e contadores locais; o teste materializado prova standby sob contenção, ativação única e replay vazio. Ainda faltam Studio, métricas/alertas exportados e contenção PostgreSQL/MySQL.

Segurança nativa específica para educação e jogos

  • [~] Rullst já oferece sessões criptografadas, RBAC/ownership, CSRF, headers, WAF/RASP/DLP, Login Jail, guardas de AI e telemetria versionada. Isso é uma fundação de defesa em profundidade, não prova a segurança do domínio LMS.
  • Criar threat model versionado para aluno, professor, moderador, escola, conteúdo, avaliação, ranking, automação, pagamento e administração: o TM-ACADEMY-1 em TM-12.10 registra 12 abuse cases, evidência atual, mínimos negativos e riscos residuais sem promover fundações a garantias de produto.
  • [~] Exigir entitlement e ownership em toda rota de curso, aula, tentativa, progresso, submissão, certificado e mídia. O starter protege matrícula, player, progresso, início e submissão de quiz com identidade de sessão, enrollment ativo e RbacGuard; quiz/aluno são derivados da path/sessão e o teste materializado nega contexto cross-user. A rota de conclusão deriva o sujeito da sessão e da matrícula/version pin, a verificação pública aceita somente chave opaca e não serializa identidade do aluno, e revogação exige admin. A fronteira escolar persistida agora abrange matrícula, aula/progresso, score/correção, submissão/avaliação, conclusão/revogação, autoria/rollback, scheduler e papéis; o teste materializado nega um admin de outra escola na fronteira HTTP e nos serviços antes de efeitos. Ainda faltam mídia real e a enumeração independente de toda rota da aplicação separada.
  • [~] Provar isolamento multi-tenant também no banco, cache, filas, realtime, arquivos, buscas, métricas, exports e Nexus; prefixos sem testes de não-interferência não constituem isolamento. O scaffold agora prova em SQLite que escola vem de membership autenticada, seleção arbitrária/ambígua falha, IDs de curso/aula não atravessam a escola, entitlement é school-scoped e um admin estrangeiro não causa efeitos em matrícula, autoria, rollback, avaliação, score, certificado, scheduler ou revogação de papel. A prova é Outbox, regras/execuções de automação, conquistas derivadas e notificações agora preservam school_id, inclusive para o mesmo usuário em duas escolas e com uma regra estrangeira habilitada. A prova continua deliberadamente parcial: Core possui wrappers de storage, cache, realtime e presence imutavelmente ligados a TenantContext, com provas da mesma chave/canal entre dois tenants, sem flush global no wrapper de cache e com nomes/payload realtime limitados. O leaderboard Academy usa agora esse cache tenant-scoped como projeção reconstruível, valida o payload e invalida após score, quiz e correção; a regressão materializada prova cache/invalidations. Anexos/mídia, demais caches, autorização de salas além da assinatura de notificação owner/admin e realtime distribuído, cache distribuído, bucket remoto, busca, métricas, exports, Nexus e PostgreSQL/MySQL continuam sem claim integral.
  • [~] Criar papéis e separação de deveres para owner da escola, administrador, instrutor, avaliador, moderador, suporte, responsável e aluno, com elevação temporária auditada e nenhuma mutação autônoma por AI. O starter agora gera assignments duráveis para os oito papéis, registra grantor, motivo e janela, exige expiração para suporte, reserva grants de owner/admin ao owner da escola sem autoelevação, vincula replay à requisição exata e reconstrói no middleware apenas papéis ativos. Endpoints autenticados vinculam o alvo à path e oferecem grant/revoke; revogação usa chave própria, compare-and-set e preserva ator, motivo e instante, com replay exato como no-op. O teste materializado cobre o ciclo HTTP, expiração, revogação, replay conflitante e nega que admin conceda ou revogue papel privilegiado. Assignments e consultas agora carregam school_id, e revogação de outra escola retorna not-found. Ainda faltam cerimônia de step-up, trilha externa compartilhada e ferramentas AI limitadas por esse contexto.
  • [~] Proteger uploads e conteúdo ativo com tamanho/tipo reais, nome de arquivo, parsing isolado, quarentena e adapter de malware scanning. O contrato UploadPolicy valida tenant/nome/tamanho, compara MIME e extensão com assinaturas reconhecidas, rejeita texto ativo, gera chave de quarentena aleatória e tenant-scoped, vincula SHA-256 e falha fechado sem verdict limpo do UploadScanner; o scanner offline é explicitamente apenas um mock determinístico. Ainda faltam streaming multipart, persistência/movimentação remota, parsers/transcoding em sandbox, scanner de produção e políticas profundas separadas para SVG, HTML, mídia, arquivos compactados e documentos.
  • [~] Implementar privacidade por design para menores e adultos: o scaffold agora persiste age_band em vez de data de nascimento, política de retenção versionada e school-scoped, consentimento de responsável por finalidade com revogação e pedidos idempotentes de exportação/exclusão. Uma varredura administrativa limitada e school-scoped marca políticas vencidas por CAS e agenda pedidos duráveis/idempotentes de exclusão, sem apagar dados silenciosamente. O protocolo de fulfillment school-scoped fornece claim com lease, recuperação de abandono, token exato, retry atrasado, limite/dead-letter e finalização vinculada a ator e digest SHA-256 do resultado. Um executor supervisionado de dispatch estático aplica timeout menor que a lease, shutdown explícito e métricas locais sobre um adapter de fulfillment pertencente ao produto; o mock determinístico é explicitamente apenas protocolo e nunca exporta, exclui ou anonimiza dados. A regressão SQLite falha fechado para menor sem consentimento/depois da revogação, nega responsável de outra escola, prova não interferência da varredura/worker entre escolas, replay sem duplicação, lease expirada, token obsoleto, retry e dead-letter; claims abandonadas no teto rígido de dez tentativas são movidas por CAS para dead-letter em vez de serem recuperadas indefinidamente, e também percorre sucesso supervisionado e falha de adapter até dead-letter. Ainda faltam implementar no produto o adapter que executa export/delete/anonymize em todas as tabelas, comprovar identidade do responsável, observabilidade sem PII e revisão legal do produto/deploy; conformidade nunca será inferida apenas do framework.
  • [~] Implementar rate limit/abuse control distribuído por identidade e origem, revogação de sessão/dispositivo, MFA com recovery codes e trilha durável para ações administrativas e alterações de nota/pontuação. O adapter Redis atômico aceita chaves derivadas de identidade/origem sem armazená-las em claro, e as correções de score do starter são append-only; o contrato live prova duas instâncias do limiter contra Redis real. Ainda faltam a composição HTTP Academy, revogação/dispositivos, persistência transacional dos recovery codes, cluster/failover e auditoria durável compartilhada.
  • Publicar um ProductionPreset::academy() fail-closed e um diagnóstico CLI que reporte PASS, FAIL, SKIPPED ou NOT_EVALUATED para as fronteiras verificáveis, sem emitir certificação ou nota de segurança fabricada. O preset tipado rejeita requisitos ausentes/duplicados/não aprovados; cargo rullst academy:doctor normaliza as 12 fronteiras, exige evidência declarada não vazia para PASS, suporta JSON e termina com erro enquanto o contrato não estiver integralmente satisfeito, sempre com certification: false.
  • Submeter a aplicação Academy e suas integrações críticas a revisão externa, corrigir achados e publicar versão, escopo, metodologia e limitações antes de usar alegações comparativas de segurança.

DX, operação e prova de superioridade vertical

  • Adicionar scaffolds coerentes para curso/módulo/aula, matrícula, quiz, atividade, conquista, leaderboard, automação, conclusão e certificado. Os modelos, migrations, constraints/índices, registros Nexus e repositories opcionais são gerados; o projeto LMS materializado passa cargo test --offline --all-targets, com pontuação derivada somente leitura no admin. Fluxos verticais completos e a prova sobre pacotes publicados permanecem itens separados deste programa.
  • Evoluir Nexus para autoria educacional: ordenação de módulos/aulas, preview, revisão, publicação, coortes, matrículas, rubricas, moderação e visão de progresso, sempre com políticas de campo/ação no servidor.
  • [~] Fornecer fixtures offline determinísticas e um seed Academy divertido que exercite toda a jornada sem credenciais externas nem dados apresentados como produção real. As migrations agora semeiam módulo, quiz, o jogo offline Borrow Checker Rescue, conquista e regra de automação com JSON versionado, sem a função datetime() específica de SQLite. Ainda faltam usuário/sessão, tentativas, pontuação/entrega e mídia local para uma jornada E2E completa.
  • Criar E2E de navegador para aluno, professor e administrador, incluindo acessibilidade, perda de conexão, retries, concorrência e todos os casos negativos definidos pelo threat model.
  • Operar a referência com PostgreSQL/MySQL/SQLite conforme suporte declarado, Redis opcional, backup/restore, migrations, rotação de segredo, deploy e rollback ensaiados; publicar limites e combinações realmente verificadas.
  • Medir tempo até primeiro curso seguro, primeiro quiz, primeiro ranking e primeiro diagnóstico com usuários novos, além de benchmarks reproduzíveis e perfis de CPU/memória; resultados devem incluir falhas e custo operacional.
  • Documentar uma trilha única “zero ao LMS em produção”, receitas de extensão, escape hatches Axum/Tokio e migrações SemVer, mantendo exemplos compilados e links válidos como gates de release.

O que significa “10/10”

Nota 10 não significa ter todos os recursos imagináveis. Significa ser excepcional dentro de uma fronteira declarada:

  • Segurança: defaults fortes, limites explícitos, threat models, testes adversariais e revisão externa.
  • IA: integração útil com autorização, evals, observabilidade e controle humano.
  • Experiência: primeiro sucesso rápido, erros acionáveis, blueprints que compilam e uma rota clara de desenvolvimento para produção.
  • Release: artefatos reproduzíveis, proveniência, SemVer e documentação honesta.
  • Confiança real: aplicações independentes operadas, atualizadas e recuperadas com sucesso.
  • Validação externa futura: uma Academy independente poderá demonstrar a jornada segura de aluno, professor e administrador usando somente APIs e pacotes públicos, sem se tornar condição de qualidade ou lançamento da v12.

O caminho mais curto para tornar o Rullst extraordinário agora não é acrescentar mais uma promessa: é tornar cada fronteira já escolhida previsível, comprovável e agradável de usar.

Auditoria dos roadmaps anteriores ao gpt.md

Esta seção registra a classificação solicitada para todos os roadmaps que existiam imediatamente antes da criação de gpt.md. Ela impede que uma caixa histórica marcada como concluída volte a ser interpretada como prova de uma capacidade end-to-end ou como obrigação de acrescentar escopo à v12 RC.

Corte histórico, método e regra de decisão

  • O gpt.md foi criado no commit ecf3ecb6; a fotografia imediatamente anterior e imutável é 96222fbd, de 24 de agosto de 2026.
  • Nessa fotografia existiam 12 arquivos chamados ROADMAP.md ou roadmap.md: um roadmap mestre, uma cópia divergente no mdBook e dez roadmaps de crates.
  • Usando o ROADMAP.md da raiz como cópia canônica, havia 226 caixas [x] e 88 caixas abertas. Várias caixas agregavam muitas capacidades diferentes; a contagem não representa 226 implementações comprovadas.
  • Das 226 linhas marcadas, 36 são repetições textualmente idênticas dentro do roadmap do ORM. A auditoria preserva as 226 linhas históricas para não apagar o registro, mas usa também 190 alegações exatas deduplicadas para que uma cópia documental não conte duas vezes como engenharia entregue.
  • A classificação abaixo foi feita contra código, testes e a SST atuais. Um nome de módulo, trait, scaffold, mock ou teste unitário isolado não basta para promover a alegação inteira a implementada.
  • A evidência e os limites de maior risco permanecem no capability ledger; a preservação do texto original e os links para a fotografia histórica estão em Preservação da documentação anterior ao gpt.md.

As decisões de release usadas nesta seção são:

  • v12 obrigatória: superfície que a v12 pretende suportar sem o rótulo experimental. Precisa de implementação delimitada, teste negativo, docs honestas e todos os gates da release.
  • v12 experimental: fundação útil que pode permanecer apenas atrás de uma feature, simulador ou aviso explícito, desabilitada por padrão quando houver risco. Não bloqueia a RC e não recebe promessa de produção.
  • v13+: trabalho valioso, porém grande, dependente de novo contrato, infraestrutura, hardware, provedor ou mudança arquitetural. Não deve ampliar o escopo da v12 RC.
  • não recomendar/prometer: a redação absoluta ou a automação autônoma não é um contrato tecnicamente honesto. Quando existir um núcleo útil, ele deve ser substituído por uma meta menor, mensurável e revisável.

Inventário documental anterior ao gpt.md

Roadmap histórico[x]Alegações exatas únicasAbertosLeitura auditada atual
ROADMAP.md242411Tracker mestre; cada milestone M1–M32 é reclassificado abaixo.
docs/src/roadmap.md259Era uma segunda cópia divergente; hoje apenas incorpora o roadmap mestre. Não é contado novamente nos totais.
rullst-ai/ROADMAP.md773Cliente/providers e guardrails têm fundações reais; várias garantias end-to-end eram mais amplas que o código.
rullst-auth/ROADMAP.md223RBAC/Gates têm base real; a maturidade de sessões, JWT, TOTP e WebAuthn precisa ser avaliada separadamente.
rullst-capital/ROADMAP.md13133Billing possui adapters e mocks delimitados; cobertura uniforme e NFS-e live não estavam concluídas.
rullst-connect/ROADMAP.md23230OAuth/OIDC era substancial; conveniências avançadas e mensageria não eram todas implementadas apesar de não haver caixa aberta.
rullst-iot/ROADMAP.md15159Frames/helpers existiam; transporte, hardware e OTA end-to-end eram simulados ou ausentes.
rullst-mail/ROADMAP.md181833Transports e pipeline tinham base real; deliverability/IA/observabilidade avançadas continuavam roadmap.
rullst-nexus/ROADMAP.md442CRUD existia, mas a fronteira administrativa histórica precisava ser tornada fail-closed.
rullst-orm/ROADMAP.md81457Era o documento mais supermarcado; 36 linhas eram repetições textuais e várias capacidades eram apenas helpers/fundações.
rullst-security/ROADMAP.md323215Controles úteis existiam, porém “zero”, “A+”, certificação, SIEM live e inteligência autônoma não estavam provados.
rullst-studio/ROADMAP.md772Algumas telas existiam; telemetria hardcoded e integrações ausentes não podiam ser chamadas de live.

Reclassificação integral do roadmap mestre M1–M32

Quando um milestone histórico misturava uma base real com uma visão futura, a decisão é deliberadamente dividida. Isso é mais preciso que trocar apenas [x] por [ ].

IDEstado verificável atualDecisão para a v12 RC e depois dela
M1 CLI e generatorsParcial. Comandos e os blueprints principais existem e a distribuição empacotada compila os seis starters; isso não prova toda combinação de flags e generators.v12 obrigatória para a matriz principal e regressões de segurança; cauda combinatória fica para v13+.
M2 linkers/build sub-100 msParcial e dependente do ambiente. Configurações de linker/build existem; tempo universal não é comprovável.Otimização e benchmark são contínuos; não prometer “sub-100 ms” sem máquina, cenário e resultado reproduzível.
M3 escape hatches/features/zero lock-inParcial. Axum/Tower, Core runtime-only e features granulares são reais; migração sem custo universal não é.Fronteiras e testes de features são v12 obrigatória; não prometer zero lock-in absoluto.
M4 make:resource e IgnitionImplementado em escopo delimitado. Scaffold e console local existem.v12 obrigatória no escopo documentado; qualquer autofix autônomo é experimental e exige preview/rollback.
M5 docs/OpenAPI/SDK TypeScriptParcial. mdBook e generators existem; inferência AST não é um contrato completo de API.Documentação copiável é v12 obrigatória; SDK heurístico é v12 experimental; schema tipado canônico fica para v13+.
M6 Active Record/Repository/seeders/TursoParcial. O ORM SQLx continua sendo a superfície relacional ampla. Turso/libSQL agora possui perfil primary delimitado para o starter blank/API: #[derive(Orm)] #[orm(backend = "turso")], CRUD/query tipados, migrations reversíveis com drift detection, generators e provas local/remota. Relações, hooks, auto-diff/seeds, demais blueprints e replica sync transparente não têm paridade.ORM SQLx suportado, adapters explícitos e o perfil Turso-primary delimitado são v12 obrigatória. Paridade Turso adicional e replicação vendor-specific permanecem trabalho posterior, sem bloquear a RC nem serem inferidas do derive comum.
M7 edge/Wasm/dados distribuídos/upgrade autônomoParcial. Runtime portátil tem base e cargo rullst upgrade hoje é transacional e assistido; replicação distribuída e operação autônoma continuam ausentes.Upgrade assistido e com rollback é v12 obrigatória; edge é experimental; replicação é v13+; atualização sem aprovação é não recomendada.
M8 modelagem por intenção/índices auto-otimizadosNão implementado end-to-end.Um advisor read-only pode ser v13+; DDL autônomo em produção é não recomendado.
M9 auth/OAuth/TOTP/passkeys/WebAuthnParcial. Sessões, RBAC/OAuth, JWT opt-in, TOTP/recovery e cerimônias WebAuthn possuem fundações de maturidade diferente. SQLite agora fornece revogação JWT e lifecycle/CAS bounded de passkeys compartilhados localmente; conformance WebAuthn completa não foi provada.Sessão/RBAC e contratos estáveis são v12 obrigatória; WebAuthn permanece experimental/parcial até conformance. Challenge compartilhado, inventário completo de cookie/refresh sessions e fluxos de produto ficam em v13+.
M10 Mail/DTO/rate limit/ShieldParcial. Transports, validação e controles locais existem; invariantes distribuídas e cobertura uniforme não.Superfície declarada é v12 obrigatória; adapters/controles sem backend real são experimentais ou v13+.
M11 Nexus/Omni/billing/entitlementsParcial. Nexus fail-closed e billing delimitado são reais. Omni gera deterministicamente um shell web-first com Tauri pinado, identidade/URL validadas, CSP por origem, navegação nativa exact-origin, ausência de IPC remoto, ícones reais, lifecycle desktop fail-closed e bootstrap inicial offline/retry. Gates desktop Linux/macOS/Windows, Android APK e iOS simulator passaram em 755fbd61933bed04369e0eb5de50b11275db5e3d. O perfil opt-in offline-sync acrescenta estado/conflito/resync criptográfico delimitado, mas ainda sem platform store/secure key/network orchestration; nada disso prova aparelho, assinatura, privacidade, offline-first ou publicação. Cobertura uniforme e entitlements declarativos completos não existem.Segurança de Nexus e billing anunciado são v12 obrigatória; o shell Omni delimitado permanece experimental na v12. Capacidades platform-enhanced entram somente conforme o programa 3.1 obtiver contratos e evidência; device/store e entitlements amplos não podem ser prometidos por calendário.
M12 suíte de segurança autônomaParcial. Há controles reais e testados, mas não cobertura OWASP universal, zero leakage, reputação externa, SIEM durável ou certificação.Controles delimitados e defaults são v12 obrigatória; sinks/estado distribuído ficam em v13+; absolutos e certificação automática são não recomendados.
M13 PQC e sandbox WasmNão implementado. Simuladores não são criptografia de produção.v13+ pesquisa, somente com protocolo e primitives auditadas; criptografia caseira é não recomendada.
M14 HTMX/Leptos/DioxusParcial. SSR/HTMX é real; adapters são wrappers, não integrações E2E completas.SSR/HTMX é v12 obrigatória; adapters ficam experimentais até testes de interoperabilidade; “zero bundle” universal é não recomendado.
M15 queues/cache/scheduler/Docker/mensageriaParcial. Memory/SQLite/Redis de Core, cache, scheduler e scaffold Docker existem. A crate separada rullst-messaging acrescenta envelope v1 delimitado, idempotência por tópico, grupos, leases de ACK descartáveis, retry/DLQ, purge explícito, relógio injetável, broker determinístico, codec canônico de envelope, trace context allowlisted e persistência SQLite local de schema fixo. SQLite pode escolher imutavelmente plaintext ou proteção AES-256-GCM de headers/payload, com AAD por linha e keyring de rotação; restart, raw-state, chave errada, tamper, row-swap, symlink e disputa entre instâncias têm provas. O codec não é transporte; replicação/failover e RabbitMQ/Kafka/Redis Streams/NATS/Pulsar/clouds não existem.Os backends declarados e a fundação local durável de mensageria são v12 obrigatória; brokers remotos ficam após a fundação, com matriz de restart/falhas própria.
M16 Wasm islands/client_componentParcial no pilar amplo. #[server_function] agora possui transporte delimitado integral: RpcResult<T>, envelope rullst.client v1, argumentos/resultados Serde owned, rota Axum explícita correspondente, caller Wasm, 256 KiB, correlação, erros redigidos, diagnósticos, CSRF de produção e matrizes native/Wasm/scaffold. Hydration de islands, browser E2E real, reconnect e ABI estável continuam incompletos.O transporte RPC delimitado é v12 implementado; islands/hydration permanecem experimentais, e interoperabilidade ampla fica em v13+.
M17 realtime/storage/media/package registryParcial. WebSocket/SSE e storage local delimitado existem; S3/R2, media pipeline e registry de produção não.Fundações locais são v12 obrigatória; drivers remotos/media são v13+ em crates opcionais.
M18 LiveView-style UIParcial. Loop de componente existe; auth, reconnect, backpressure, diffs e browser E2E estão incompletos.v12 experimental, não bloqueia RC; contrato estável fica para v13+.
M19 Radar/tools/spans/PrometheusImplementado em escopo delimitado. Telemetria local/export surfaces existem e fontes ausentes permanecem indisponíveis.v12 obrigatória dentro do contrato local; waterfall distribuída é item separado de v13+.
M20 event streaming/ledger imutávelNão implementado. HMAC audit chain local não é ledger distribuído.v13+ pesquisa, depois de definir persistência, consistência e recovery; não é escopo da RC.
M21 Omni/mobile hypermediaParcial com empacotamento seguro, wire contract v1 e fundação offline delimitados. O shell web-first passou seus gates de compilação; rullst.client v1 fornece envelopes tipados/bounded compartilháveis com Wasm; offline-sync nativo fornece estado account-bound, fila idempotente, revisão/cursor, conflito/resync/recovery/erasure lógico, snapshots AES-256-GCM e coordinator foreground bounded/timeout/cursor-checked sobre transport estático. Ainda não existem adapter Keychain/Keystore/persistência/browser, HTTP/retry/background concreto, opener/deep-link, push/biometria, cliente educacional de referência ou validação de loja/aparelho.O programa experimental começou na v12 com fronteiras e protocolos reais. Offline só será chamado completo após adapters, integração e testes de referência; universalidade sem evidência permanece não recomendada e a cauda segue para v13.
M22 Agentic DevOpsParcial apenas como recomendações/telemetria.Advisor/dry-run pode ser v12 experimental; mutação autônoma de infraestrutura é não recomendada e qualquer evolução fica em v13+.
M23 core polimórfico/auto-healingParcial apenas como diagnóstico.Sugestões read-only podem ser v12 experimental; mutação automática de código/schema é não recomendada e pertence a v13+ se ganhar aprovação e rollback.
M24 IoT/no_std/OTAParcial. Frames e codecs bounded MQTT-PUBLISH/CoAP-request no_std, gate Ed25519 e contrato de store com CAS monotônico/durável são reais; implementação física do counter, download, flash, boot, HSM/PQC e transports não.Fundação delimitada pode permanecer v12 experimental/bounded; integrações reais são v13+ e dependem de hardware.
M25 Embassy asyncNão implementado.v13+, depois de estabilizar traits de hardware e transporte.
M26 deploy PaaS/VPSParcial. Scaffolding/guias existem; credenciais, DNS, migrations, health e rollback continuam operacionais.v12 experimental/assistido; “one click” e zero downtime universais são não recomendados.
M27 Kubernetes/probesImplementado no escopo de scaffold.v12 obrigatória para geração e validação; não constitui certificação de produção.
M28 DI/Inject<T>Implementado como fundação typed.v12 obrigatória no escopo atual; “zero cost” só pode ser uma conclusão de benchmark.
M29 Scalar/OpenAPIParcial. UI/router/generator existem; fidelidade de schema não é completa.v12 experimental se descrito como scaffold; contrato tipado completo fica em v13+.
M30 gRPC/TonicParcial. make:grpc gera um ponto de partida; não existe uma crate rullst-grpc suportada com matriz completa.Generator fica v12 experimental; superfície first-class é v13+.
M31 aerospace/autônomos/defesaNão implementado.Não incorporar ao Core nem à v12/v13 web. Somente um programa independente safety-critical com equipe, hardware, normas e governança.
M32 Axum/Tower e diagnósticos de macroImplementado em escopo delimitado.v12 obrigatória, com testes contínuos de compatibilidade.

Classificação dos dez roadmaps detalhados por crate

Esta tabela fecha as alegações que os milestones compostos não enumeravam individualmente. O status sempre se refere ao contrato inteiro da frase histórica, não apenas à existência de um arquivo com nome parecido.

CrateImplementado e obrigatório para o escopo estável da v12Experimental/parcial na v12Manter para v13+Não recomendar como promessa
AICliente guardado; providers OpenAI/Gemini/Anthropic/DeepSeek/Ollama e adapter OpenAI-compatible local/cloud declarado; mocks determinísticos; PII e corpus de regressão; política de egress opt-in; RAG tenant-aware auditado; streaming SSE/cancelamento explícito no protocolo compatível delimitado; export de auditoria HMAC; runner adaptativo multi-turn bounded com relatório sem conteúdo raw.Vector/tools locais, memória SQL e schema output dentro dos limites documentados; receiver/outbox de auditoria, corpora de domínio e execução/revisão live pertencem ao host.Protocolos streaming não compatíveis, tool loop nativo autorizado e adapters RAG first-party para vector DB externo.Firewall “invulnerável”, ausência total de leakage, resultado offline tratado como certificação de modelo live, SIEM distribuído implícito ou suporte a qualquer API arbitrária.
AuthArgon2 não bloqueante, sessões versionadas/expiráveis, RBAC/Gates, política JWT opt-in e estado SQLite bounded para revogação JWT e lifecycle/CAS de passkeys compartilhado por processos locais.WebAuthn custom até conformance; challenges continuam process-local; recovery codes não possuem workflow transacional completo da aplicação.Magic links, refresh/session UX, challenge store/replicação multi-host e adoção/conformance WebAuthn completa.Chamar a fundação de FIDO2/WebAuthn universal, confundir SQLite local com distribuição multi-host ou assumir ownership de device.
CapitalAdapters/métodos realmente suportados, mocks explícitos, assinatura/freshness de webhooks, analytics documentada, cupons/trial provider-specific delimitados, quota Team/Workspace idempotente com store SQL transacional em quatro protocolos e preparação NFS-e local delimitada com DPS/XSD/XMLDSig/mTLS, codec de resposta e journal HMAC de comando/recovery.NFS-e sem transmissão/homologação oficial; journal local não substitui request/outbox/reconciliação multi-writer; membership/tier e migrations da quota permanecem do host.Idempotência distribuída de webhooks, Alipay RSA2, proration/tax completos e conclusão do programa separado de homologação NFS-e live.Paridade implícita entre gateways, taxas estáticas, “custo zero”, validade local tratada como autorização ou resposta mock indistinguível de autorização fiscal.
ConnectOAuth2/OIDC/social login, PKCE/state, discovery/JWKS, mocks, retries e construção fail-closed no escopo testado.Conveniências/provider helpers cuja redação antiga excedia o contrato.SAML, SCIM, DPoP, JWE, mTLS, risk ML e mensageria em uma futura crate coerente.Misturar OAuth e brokers no mesmo limite ou prometer identidade “enterprise-grade” sem conformance.
IoTFundação delimitada: frames/telemetria no_std, codecs bounded MQTT 5 PUBLISH/CoAP request, verificação Ed25519 do manifest OTA e contrato CAS para store de counter persistente.Simuladores deterministicamente rotulados e store fornecido pela plataforma; não representam hardware validado.Transports e session state MQTT/CoAP/Sparkplug, implementação hardware-backed do counter, flash/boot/rollback, hardware, HSM e PQC auditados.Certificação industrial, HSM/PQC “simulados” ou OTA end-to-end sem device tests.
MailTransports suportados, pipeline de segurança/deliverability, fila/worker, tracking tokens, mocks offline, inspeção bounded opt-in, suppression process-local/SQLite compartilhado-local e observações terminais minimizadas.Scheduling/failover seguem os adapters testados; scanner local não é antivírus/CDR e SQLite não é replicação multi-host.Webhooks autenticados de bounce/complaint, scanner produtivo, CSS inlining, inbound MIME, DKIM/DMARC/S-MIME, Mail Radar e gateways adicionais com contract suite.Deliverability universal, inbox garantido, zero-panic absoluto ou conformidade automática por possuir um transport.
NexusConstrução fail-closed, auth/role/field policy server-side, CRUD/search/sort/paginação e batch limitado.Assistente AI somente dentro da política e autorização do host.Dashboards customizados e visual SQL builder com preview, limites e auditoria.Admin aberto por padrão ou AI com mutação autônoma de produção.
ORMPools SQLx, binds, Active Record/repository/query/schema, transactions, migrations, relações/scopes, soft delete, audit/privacy e encrypted fields no escopo testado; perfil Turso-primary blank/API; adapters delimitados para MongoDB, DuckDB e SurrealDB; pgvector tipado/parametrizado em PostgreSQL; Qdrant dense-cosine e Redis Hash/Set/Sorted Set com matrizes live pinadas.Turso não tem paridade SQLx em relações/hooks/auto-diff/seeds ou demais blueprints; GQL SurrealDB é read-only e bounded; Scout HTTP tem Meilisearch live pinado e Elastic/Algolia por fixtures de protocolo, sem durabilidade automática; Qdrant/Redis não provam cluster, failover ou autorização da aplicação.RAG completo, Qdrant named/sparse/multivectors e filtros arbitrários, Redis Lists/Streams como datastore, graph mutation/traversal completa, Wasm drivers, replication vendor-specific e migrations/index advisor.API universal que esconda semânticas incompatíveis, auto-DDL/auto-migration autônoma, replicação “transparente” genérica ou PQC caseiro.
SecurityHoneypot/sanitizer/CSP, RBAC, HMAC chain, RASP/DLP limitados, AES-GCM, headers, login guard, TOTP, CSWSH, schema/log guards, SRI, evento v1 e ferramentas CLI orientadas a evidência.Redis rate limit exige prova operacional externa; WebAuthn, recovery workflow e sinks duráveis permanecem parciais.KMS/rotation, audit/SIEM durável, adaptive WAF, SQL firewall, containment/eBPF e Wasm sandbox após threat model.“A+ garantido”, zero leakage/latency/unsafe, cobertura OWASP total, certificação automática ou PQC próprio.
StudioBrowser SQLx read/filter e mutação primitiva single-row atrás da capability local verificada, ER relacional, ambiente/config tipado redigido, histórico SQLite opt-in, invalidação in-process de flags e telemetria local real com estados Unavailable.OpenAPI fornecido pelo host, Redis/custom queue inspection e logger sem corpos/headers.N+1 profiler, Cache/Redis inspector e waterfall OTel distribuída.Capturar secrets por padrão, inventar métricas, chamar estado desconectado de live ou afirmar zero overhead universal em release.

Decisão final de escopo da v12 RC

Esta auditoria não presume que um antigo [x] esteja implementado. Ela o transforma em entrada obrigatória da campanha, mas o resultado possível pode ser integral, parcial, ausente, dependência externa/hardware ou não recomendada. Para a RC, a ordem correta é:

  1. deduplicar e classificar cada alegação, mantendo as 226 linhas originais rastreáveis;
  2. implementar e testar por risco e valor toda parcela localmente executável que pertença a uma fronteira coerente do framework;
  3. manter fundações incompletas atrás de limites experimentais explícitos, sem apresentá-las como garantias estáveis;
  4. registrar providers, certificação, hardware e operações externas como tais, usando mocks somente quando forem determinísticos e inequivocamente rotulados;
  5. congelar a superfície somente depois da campanha executável e manter verdes os gates, pacotes, upgrades e blueprints; rejeitar absolutos de marketing e automação autônoma sem preview, aprovação, auditoria e rollback.

Em resumo: a documentação histórica realmente dava a entender que muito mais estava concluído. A campanha solicitada passa por todas as 190 alegações deduplicadas e tenta fechar o máximo tecnicamente honesto e localmente executável. Isso não autoriza fabricar integrações live, certificação, hardware ou garantias absolutas: tais parcelas precisam ser classificadas com sua dependência externa e mantidas fora da superfície estável até existir evidência. Cada ampliação da v12 continua subordinada aos mesmos gates e limites públicos.

Painel rastreável da campanha de implementação

Este painel mede o esforço solicitado para transformar em contratos reais as capacidades que apareciam marcadas como concluídas antes de gpt.md. Ele não é uma porcentagem de “todo software imaginável” nem uma previsão de prazo.

Camada de macroentregas — estado em 2026-09-01

O denominador provisório contém as 24 macroentregas historicamente marcadas [x] no roadmap mestre mais o lote Polyglot acrescentado durante esta auditoria:

RéguaFórmula atualImplementadoFaltaInterpretação
Estrita6 integralmente encerradas / 2524%76%Parcial continua contando como não concluído.
Maturidade ponderada(6 integrais + 19 parciais × 0,5) / 2562%38%Indicador de engenharia; não converte parcial em promessa estável.

Um item só muda para integral quando código, testes relevantes, documentação e gates aplicáveis estiverem fechados. O lote de persistência tornou-se a sexta macroentrega integral depois das matrizes live, testes completos, Clippy estrito, formato, fronteiras de features e mdBook passarem em 29 de agosto de 2026. Essa é evidência do worktree local; os mesmos gates ainda precisam passar no futuro SHA imutável da RC.

Na régua das 190 alegações históricas exatas, o fechamento dos dois scaffolds fiscais/dunning, da ponte autenticada/failover tipado e do scheduling durável de Mail, do proxy explícito de Connect, da invalidação imediata de feature flags/histórico opt-in de jobs do Studio e do RAG tenant-aware auditado mais o lifecycle state/PKCE/nonce de sessão do Connect, os adapters canônicos de webhook Axum/Actix do Capital, o scaffold de billing SQLx/Turso, o handle delimitado de subscription/grace period, a mutação relacional delimitada do Studio, a política JSON Schema/OpenAPI explícita do Security e a memória de chat SQL tenant-aware/CAS do AI e o Sentinel determinístico/PoW delimitado do Security, o harness Criterion equivalente Rullst/Diesel/SeaORM, o Mock IdP OIDC local assinado, os widgets semânticos Nexus validados e a cobrança imediata segura/delimitada do Billable, o billing medido tipado/delimitado e as quotas compartilhadas concorrentes/duráveis do Capital e os contratos delimitados de cupons/trial, o refresh OAuth concorrente delimitado e o transporte AWS SES v2 nativo assinado pelo SDK oficial e o contrato bounded de attachments/CID comum aos transports nomeados levam o inventário a 102 integrais, 86 parciais e 2 ausentes. O fechamento delimitado de ORM-31 — ator/tenant obrigatório, revisão restaurável e recusas transacionais — leva o inventário a 103 integrais, 85 parciais e 2 ausentes. O fechamento de IOT-03 como codecs bounded MQTT 5 PUBLISH e CoAP request, sem alegar transporte, levou a régua a 104/84/2. O fechamento de AI-05 com entradas explícitas de bytes, arquivo allowlisted e URL sob egress policy levou a régua a 105/83/2. O fechamento delimitado de ORM-41 — codecs derivados, ENUM nativa nos perfis suportados, constraint SQLite, recusa fail-closed de PostgreSQL via SQLx Any e matrizes live — leva o estado atual a 106 integrais, 82 parciais e 2 ausentes: 55,8% estrito ou 77,4% ponderado. Essa régua inclui as 24 macroalegações históricas e as 166 alegações dos roadmaps de crates, portanto não deve ser somada à camada de 25 macroentregas acima. Dependências externas, hardware, certificações e absolutos tecnicamente não recomendáveis continuam parciais/ausentes em vez de receber crédito artificial.

Horizonte ampliado — backlog canônico até a v13

Esta é uma segunda pergunta, separada da campanha das 190 alegações antigas: quanto falta se também mantivermos no programa todo milestone canônico ainda sem [x] e planejado até a v13?

O denominador é o tracker M1–M39 do ROADMAP.md, recalculado em 4 de setembro de 2026. Entram hardening v12, manutenção corretiva 12.0.x, o próximo ciclo SemVer, trabalho contínuo, v13 e pesquisa v13. M31 não entra, pois o próprio roadmap destina aeroespacial/autônomos/defesa a um programa futuro separado, com outra governança. As caixas dos roadmaps detalhados de crates não são somadas: elas repetem ou decompõem esses milestones e produziriam dupla contagem.

Estado canônicoMilestonesParcela do horizonte de 38
[x] integral em escopo delimitado513,2%
[~] fundação útil, ainda incompleta2463,2%
[ ] ainda não implementado923,7%
Total programado até v1338100%
  • Na régua estrita, 33/38 ainda não estão fechados: faltam 86,8%.
  • Na régua de maturidade ponderada, (5 + 24 × 0,5) / 38 resulta em 44,7% implementado e 55,3% faltando. Esses 55,3 pontos equivalentes são os nove itens ainda vazios (23,7 pontos) mais a metade não concluída dos 24 parciais (31,6 pontos).

Portanto, a resposta operacional para o horizonte ampliado é aproximadamente 55,3% de engenharia restante, sem esconder que 86,8% dos milestones ainda não têm fechamento integral. Isso não é prazo: contas de provedores, hardware, lojas, homologação fiscal, auditorias independentes e criptografia de pesquisa exigem evidência externa. Também não se soma esta porcentagem aos 22,9% restantes da campanha histórica nem aos cerca de 30% da preparação da RC, pois os três denominadores se sobrepõem fortemente.

Lote concluído localmente: fundação de mensageria por contrato

rullst-messaging passa a ser a fronteira separada de OAuth/OIDC para eventos brokered. O escopo v12 implementado é propositalmente delimitado: envelope rullst.messaging.v1, nomes/headers/payloads/batches/retenção limitados, publicação idempotente por tópico com conflito fail-closed, fan-out entre grupos, consumidores concorrentes, leases de ACK expiráveis e de uso único, retry com teto de tentativas, dead-letter, purge terminal explícito, tempo injetável e broker em memória. O adapter opt-in SQLite persiste publicação, inscrição, claim, ACK/retry/DLQ, idempotência e purge sob transações de escrita serializadas. Testes provam restart com lease expirado, duas instâncias concorrentes, configuração imutável por namespace e corrupção fail-closed seguida de reparo. O lote também congela um codec binário canônico v1 para o envelope com fixture de digest determinística, rejeição de versão desconhecida, truncamento, oversize, ordem não canônica e namespace incorreto. traceparent W3C v00 e um subconjunto conservador de tracestate podem atravessar somente esses dois headers; baggage, sampling e exportação continuam sob responsabilidade do host.

O perfil SQLite padrão continua plaintext; o perfil explícito encrypted usa AES-256-GCM com nonce aleatório e prende namespace, tópico, sequência, ID, evento/content-type, timestamp e key-id na AAD. Ele protege valores de headers e payload, prova raw-state/restart/tamper/row-swap/rotação/symlink e exige chaves anteriores enquanto houver registros que as referenciam. Metadados de rota, idempotência e delivery continuam visíveis; custody, backups protegidos, rollback e migração de perfil são do host.

O perfil opt-in orm-outbox liga um stream relacional exato a um tópico por static dispatch. Ele valida o claim JSON, publica usando event_key como idempotência e só então reconhece o lease ORM. A regressão interrompe após o primeiro publish, recupera o claim expirado e prova replay com o mesmo ID e uma única mensagem. Isso não transforma commit, publish e ACK numa transação distribuída atômica; supervisão, cleanup, autorização e idempotência no destino permanecem da aplicação.

Isso melhora M15, mas não o fecha: a entrega continua at least once, e autorização/tenant, backup, retenção e idempotência do efeito externo pertencem ao host. O codec de envelope não é um transporte, não preserva sozinho a chave de publicação nem mapeia ACKs de provedor. Replicação/failover, Kafka, RabbitMQ, Redis Streams, NATS/JetStream, SQS/SNS, Google Pub/Sub, Pulsar e suas matrizes live continuam abertos. A porcentagem do horizonte não muda porque M15 permanece [~], agora com evidência mais forte.

Camada detalhada

As 226 marcações históricas canônicas permanecem preservadas e suas 190 alegações exatas deduplicadas estão 100% classificadas abaixo como integral, parcial, ausente, dependência externa/hardware ou não recomendada, com arquivo/teste de evidência quando aplicável. A leitura mantém dois denominadores:

  1. visão histórica total, preservando tudo que foi prometido;
  2. engenharia executável localmente, que orienta implementação e exclui apenas certificação, hardware, contas/credenciais e mudanças de estado externas que este repositório não pode honestamente fabricar.

Lote concluído localmente: matriz de persistência

Implementação, matrizes live e gates completos foram concluídos no worktree atual:

  • contratos separados para SQLite, PostgreSQL, MySQL e MariaDB;
  • Turso/libSQL remoto pelo protocolo oficial Hrana HTTP v3, com parâmetros, batch transacional atômico, deadline, redirects recusados, resposta e resultados limitados, migrations reversíveis com detecção de drift e fallback persistente de SQL real; a matriz live também prova rollback após falha intermediária;
  • perfil Turso-primary blank/API com #[derive(Orm)], CRUD/query tipados, make:model, make:migration, migrate/status/rollback e provas local/remota;
  • MongoDB documental, DuckDB analítico e SurrealDB documental/grafo por APIs de capacidade explícitas;
  • inventário documental estável e recuperação portátil MongoDB/SurrealDB em envelope AES-256-GCM versionado, ligado a aplicação/coleção/key ID, com scan duplo, limites de páginas/documentos/plaintext, restauração idempotente sem overwrite e ensaio live MongoDB → SurrealDB → MongoDB; writers devem ser pausados e schema/backup gerenciado continuam externos;
  • assistente cargo rullst new dividido entre banco relacional principal e integrações opcionais, inclusive flags determinísticas; a seleção interativa agora explicita zero ou mais add-ons, omite capacidades já escolhidas e não oferece Turso-primary aos blueprints ainda específicos de SQLx;
  • CLI pré-release compilado do source checkout conserva esse mesmo checkout versionado como origem de dependências quando invocado fora do repositório; falhas da migração inicial preservam o scaffold e exibem status/comando de repetição, em vez de esconder o diagnóstico atrás de um aviso genérico;
  • matrizes live em containers onde existe servidor redistribuível adequado, seguidas pelos gates completos de teste, Clippy, formato, docs, fronteiras de features, empacotamento e inspeção do conteúdo/licença dos pacotes.

Evidência local de fechamento:

  • cargo test --workspace --all-features passou, incluindo testes unitários, integração, projetos gerados e doctests;
  • cargo clippy --workspace --all-features -- -D warnings passou sem avisos;
  • cargo fmt --all -- --check e git diff --check passaram;
  • .github/check-feature-boundaries.sh passou para todos os pacotes mínimos e features isoladas declaradas, e mdbook build docs construiu o livro;
  • PostgreSQL, MySQL, MariaDB, MongoDB, SurrealDB 3.2.4 e Turso/libSQL passaram suas matrizes com containers reais e com RULLST_REQUIRE_TESTCONTAINERS=true. No CI/release, falha de inicialização agora é fatal; somente execuções locais sem essa variável podem pular Docker;
  • os 16 arquivos .crate de 12.0.0 foram gerados localmente sem upload e passaram .github/audit-packages.sh 12.0.0: nomes e quantidade exatos, caminhos seguros, Cargo.toml/LICENSE/README/src presentes, licença idêntica à raiz, nenhum padrão de segredo e nenhum pacote acima de 10 MiB. Esse resultado não substitui o empacotamento verificável no SHA limpo da RC.

Apoio de MariaDB não significa um driver Rust distinto: ele usa corretamente o protocolo MySQL do SQLx, mas só é anunciado depois de passar um contrato live em imagem MariaDB. Turso/libSQL também é relacional e pode ser a única fonte de dados no perfil primary delimitado; o derive comum exige #[orm(backend = "turso")] para não trocar o driver implicitamente. MongoDB, DuckDB e SurrealDB não fingem implementar a mesma semântica do Active Record relacional.

Fechamento delimitado do M6: Turso como primary

cargo rullst new --database turso agora gera um blank/API que compila, usa TursoOrm, executa migrations checksummed/reversíveis e não conserva um DATABASE_URL SQLx fictício. A matriz cobre o fallback persistente offline e o contrato tipado no servidor libSQL oficial; os generators posteriores mantêm o backend. --turso continua corretamente aditivo quando existe outro primary. Os demais blueprints ainda são SQLx-specific e rejeitam a seleção, em vez de simular paridade. Replica sync transparente também permanece fora do contrato.

Inventário item a item — rullst-ai histórico

Com o roadmap mestre já individualizado acima, esta primeira tabela detalhada levou o inventário exato a 31 alegações; as tabelas Auth, Capital, Connect, IoT, Mail, Nexus, Studio, Security e ORM seguintes elevam o total a 190/190 alegações classificadas individualmente (24 do mestre, 7 de AI, 2 de Auth, 13 de Capital, 23 de Connect, 15 de IoT, 18 de Mail, 4 de Nexus, 7 de Studio, 32 de Security e 45 de ORM), isto é, 100% do inventário deduplicado. Esse número mede conclusão da auditoria, não implementação integral: no ORM, por exemplo, 25 itens têm contrato integral delimitado, 19 são parciais e um está ausente. “Integral” continua significando somente o limite descrito na evidência; não herda os absolutos da frase antiga.

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
AI-01Wrappers assíncronos OpenAI, Anthropic e Gemini com JSON tipadoIntegral no escopo delimitadorullst-ai/src/ai/providers/, client.rs e as suites offline exercitam os providers nomeados, DeepSeek, Ollama e o adapter OpenAI-compatible. Este último separa loopback sem autenticação de cloud HTTPS/Bearer, limita respostas/imagens e exige declaração explícita de capacidades opcionais. Protocolos diferentes continuam extensíveis por AiProvider; JSON parseável e JSON Schema nativo permanecem capacidades distintas e reportadas.
AI-02#[ai_embedding] sincroniza embedding automaticamente em todo save/update e em backgroundParcialrullst-orm-macros/src/models/ai_ops.rs oferece save_with_embedding, explicitamente aguardado. Não há hook automático/outbox durável; executar rede silenciosamente em todo save não será prometido sem política de falha e consistência.
AI-03RAG “in-a-box” consulta cosine distance, monta contexto e responde numa chamadaIntegral no escopo orquestrado delimitadoRagPipeline::answer agora faz embedding guardado, retrieval por trait estática, orçamento Unicode por documento/total, geração guardada, fontes e auditoria minimizada em uma operação com TenantContext; recusa tags cross-tenant, contexto injetado e retrieval vazio. InMemoryRagRetriever prova cosseno e partição local. DurableRagAuditTrail e DurableToolAuditTrail acrescentam arquivos locais versionados, sincronizados e delimitados com provas de reinício/corrupção/quota/concorrência. O opt-in AuditDeliveryClient acrescenta export HMAC autenticado, event ID estável, retry/cancelamento e ACK vinculado, sem prometer writer multiprocesso, outbox durável, receiver/SIEM, rotação ou operação de chaves. Autorização no datastore, adapters first-party pgvector/Qdrant, ingestão/remoção durável, política de output e evals live continuam explícitos.
AI-04Chat Memory embutida persiste automaticamente a conversa em SQLIntegral no escopo SQL explicitamente configuradoStatefulChat<M> usa dispatch estático, TenantContext, ID delimitado e histórico par; após a geração guardada, ChatMemory::append_exchange persiste user/assistant atomicamente. O adapter opt-in SQLx usa revisão monotônica par e compare-and-swap transacional em SQLite/PostgreSQL/MySQL/MariaDB, recusando writers stale entre pools/processos sem repetir automaticamente a chamada cobrável. O store local é bounded e determinístico; o scaffold continua cobrindo Turso/modelos customizados. Texto raw, ownership dentro do tenant, retenção/erasure, audit do provider, backups, migrations e UX de retry são da aplicação.
AI-05Abstração vision aceita arquivo local, URL ou bytes em todos os modelos anunciadosIntegral no contrato de fonte/provider declarado e delimitadoprompt_with_image preserva bytes admitidos pela aplicação; prompt_with_image_file exige LocalImagePolicy com raiz canônica exata e teto de até 10 MiB; prompt_with_image_url exige EgressFetcher HTTPS deny-by-default com allowlist, DNS pinning, peer/redirect, deadline e limite de stream. Os novos caminhos verificam capability e guardam o texto antes do I/O, reconhecem JPEG SOI, assinatura PNG completa, RIFF-WebP e GIF87a/89a e vinculam Content-Type remoto quando presente. Escape, excesso, formato/MIME e dispatch prematuro têm regressões. “Todos os modelos” significa somente transport/configuração que declara vision; modelo upstream, autorização da origem, confiança contra rename race local e segurança do decoder continuam externos e falham tipadamente quando não suportados.
AI-06Fallback troca providers e garante 100% de uptimeParcial; absoluto não recomendadoFallbackProvider e AiClient::auto fazem fallback ordenado e possuem testes. Nenhuma biblioteca pode garantir disponibilidade quando todos os providers, rede ou host falham.
AI-07make:chat-session gera modelos e migrations prontosIntegral no escopo delimitadocargo-rullst/src/generators/chat.rs gera SQLx/Turso-primary, migration reversível, módulos registrados, erros propagados, histórico limitado e recusa colisões. chat_scaffold_cli.rs materializa, roda Clippy, migra e persiste uma conversa mock nos dois backends.

O fechamento de AI-07 também corrigiu uma caixa que antes era apenas nominal: o generator antigo usava tipo de chave e métodos incompatíveis com o ORM, chamava utilitários inexistentes, não criava migrations e descartava os dois erros de persistência. A classificação acima usa o novo contrato executado, não a mera existência anterior do subcomando.

O fechamento delimitado de AI-05 move uma alegação de parcial para integral sem fingir descoberta universal de modelos: os três formatos de origem agora existem, mas capability declarada e aceitação real do modelo continuam coisas distintas. A régua global correspondente é 105/83/2, ou 55,3% estrito e 77,1% ponderado.

Inventário item a item — rullst-auth histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
AUTH-01RBAC com roles e #[require_role("Admin")] em rotasIntegral no escopo delimitadorullst-auth/src/rbac.rs nega ausência de identidade com 401 e role insuficiente com 403. A facade agora exporta #[rullst::require_role], valida role/handler/binding user em compilação e preserva a assinatura; rullst/tests/rbac_macro.rs prova negação e autorização inclusive com Extension(user). Persistência e atribuição de roles pertencem à aplicação.
AUTH-02Policies declarativas como PostPolicy::can_edit(&user, &post)Integral no escopo delimitadorullst-auth/src/policy.rs fornece Policy<User, Resource> por struct nomeada, default-deny, com owner/admin e negativas testadas. Gate<Resource> permanece apenas como compatibilidade. Carregar o recurso e estabelecer tenant/ownership continuam obrigações do controller/repository.

Hardening posterior ao snapshot, fora do denominador histórico de 190 alegações: a feature sqlite agora habilita SqliteJwtRevocationStore, com JTI expirável, versão monotônica por subject, quota/configuração persistida, transações BEGIN IMMEDIATE e verify_async; e SqlitePasskeyStore, com cadastro/listagem/rename/revogação, reinício e CAS do signature counter após cerimônia ES256 válida. Testes exercitam duas instâncias no mesmo arquivo, concorrência, replay, quota e corrupção/configuração. Esse contrato é integral somente no escopo SQLite local delimitado: challenge state permanece process-local; identidade/ownership, cifragem/permissões/backup do arquivo, replicação multi-host, refresh workflow e conformance WebAuthn continuam externos.

Inventário item a item — rullst-capital histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
CAP-01Stripe e LemonSqueezy sob uma trait uniformeIntegral no escopo delimitadoBillingProvider, StripeProvider e LemonSqueezyProvider compartilham o contrato e mocks determinísticos. Cada operação ainda precisa respeitar a matriz do provider; a trait não implica paridade live.
CAP-02#[derive(Billable)] fornece charge e subscribe instantaneamenteIntegral no contrato seguro delimitado; atalho inseguro refutadoO derive exportado como rullst::Billable valida estruturalmente email: String, preserva generics e herda checkout/subscription e charge_with/charge. Cobrança exige minor units inteiros limitados, moeda, customer e payment method tokenizados e chave de idempotência; Stripe Payment Intents encaminha a chave, confirma off-session e vincula resposta a valor/moeda/status. O mock determinístico tem status Mock distinto e não bem-sucedido; Debug é redigido. Outros adapters falham UnsupportedOperation; mandate/SCA, idempotência durável, reconciliação, entitlement e sandbox live continuam explícitos. O antigo charge(amount) não será restaurado porque inventaria dados financeiros obrigatórios.
CAP-03Middleware Actix/Axum valida e decodifica webhooks Stripe/LemonSqueezyIntegral no escopo delimitadoAxum e Actix Web chamam o mesmo verificador canônico antes do handler, limitam o corpo a 2 MiB, preservam os bytes assinados, inserem WebhookEvent, recusam configuração vazia/mock em produção e aplicam freshness/replay. O store local agora falha fechado no teto sem expulsar prova ativa; webhook-sql compartilha claims limitados de payload ou ID semântico em SQLite/PostgreSQL/MySQL/MariaDB, com restart, concorrência, drift, capacity e matrizes live. O claim de evento pode usar a mesma transação relacional da mutação de domínio. Como o middleware reivindica o payload antes do dispatch, ele não promete exactly-once; efeitos externos ainda exigem outbox, consumidor idempotente e reconciliação.
CAP-04Gera HTML/PDF e envia a fatura automaticamente após pagamentoParcial; fundação pós-pagamento delimitada implementadaInvoice valida limites, casas decimais e soma exata em minor units antes do HTML; invoice-pdf gera PDF A4 paginado/limitado com fonte embutida ou TTF/OTF verificada. PaidInvoice aceita somente recibo final Succeeded com e-mail/valor/moeda exatos e deriva chave estável. rullst-mail/capital-invoice anexa o PDF, passa pelo pre-flight e envia pela facade ou driver estático. O host ainda precisa acoplar o webhook/reconciliação, reivindicar a chave atomicamente em outbox durável e lidar com entrega at-least-once/paridade do provider; por isso a alegação automática histórica continua parcial.
CAP-05Grace period, cancelamento e pausa por subscription handleIntegral no escopo delimitadoSubscriptionHandle<P> valida/redige o ID e expõe cancel()/pause(); subscription_with(&provider) preserva dispatch estático e subscription() mantém compatibilidade com o provider global. GracePeriod valida janela half-open de até 366 dias, e o derive reconhece o par opcional start/end ou falha em compilação se incompleto. Persistência, relógio confiável, autorização/entitlement, scheduling e semântica live de cada provider não são automáticos.
CAP-06Reporte de uso para billing medido em Stripe/LemonSqueezyIntegral no contrato provider-specific delimitadoMeteredBillingProvider usa dispatch estático e um request associado por provider, evitando inventar identidade uniforme. StripeMeterEvent implementa o Meter Events atual com customer/event/value/timestamp/identifier, encaminha o identifier também como Idempotency-Key e vincula todos os campos do response. LemonSqueezyUsageRecord envia o JSON:API atual com relationship de subscription item e ação increment/set, vinculando item/quantidade/ação no retorno. Ambos limitam resposta a 1 MiB, redigem Debug, têm mock determinístico não-live e fixtures HTTP exatas/negativas. Stripe expõe a deduplicação rolling do provider; como Lemon não recebe a chave da aplicação, o receipt exige outbox durável antes do envio. Conta sandbox/live, retry/reconciliação, agregação configurada, outbox concreta e entitlement continuam limites explícitos, não lacunas ocultas da API. O método uniforme legado falha fechado em live em vez de adivinhar esses campos.
CAP-07Link direto ao portal do cliente nos dois providersIntegral no escopo delimitadobilling_portal_url delega ao provider e possui testes determinísticos para Stripe/LemonSqueezy. O host deve autenticar e vincular a identidade antes da chamada.
CAP-08make:billing gera página completa usando providers ativosIntegral no escopo delimitadomake:billing --model gera e registra modelos, migration reversível, pricing, checkout/portal autenticados com allowlist explícita de planos em produção e webhook obrigatório para o provider selecionado entre Stripe/LemonSqueezy. O contrato materializado passa Clippy, migra, persiste, recusa mutação cross-owner e colisões tanto em SQLite/SQLx quanto em Turso-primary. Ele não implica a matriz dos outros adapters, montagem automática de rotas, reconciliação/idempotência distribuída ou provider sandbox live.
CAP-09Entitlement por tier com can_accessIntegral no escopo delimitadoBillable::can_access é um comparador fail-closed, exercitado por testes. Hierarquia de planos e catálogo de features pertencem à aplicação.
CAP-10Uma Team/Workspace compartilha assinatura e limitesIntegral no escopo autenticado delimitadoO modelo Team/Workspace continua sendo o Billable que possui assinatura/tier. BillingSubject::from_tenant deriva a identidade compartilhada do TenantContext já autenticado, e todos os membros autorizados reservam no mesmo contador por subject/feature. Testes provam isolamento entre workspaces e limite compartilhado sob concorrência. Estabelecer membership e reconciliar o tier a partir de webhooks continuam fronteiras explícitas de autenticação/aplicação.
CAP-11Quota consulta o banco e bloqueia criação automaticamenteIntegral no substituto seguro delimitadoBillable::quota_request deriva o limite do dono da assinatura; QuotaGate::execute reserva antes de chamar a criação, não executa em over-limit/replay e libera em falha ordinária. O store local é determinístico e quota-sql usa claim idempotente mais update condicional atômico em SQLite/PostgreSQL/MySQL/MariaDB; fixtures concorrentes nos quatro protocolos param exatamente no limite. Para atomicidade com a tabela de domínio, reserve_with_transaction recebe a mesma transação do insert. O framework não intercepta writes arbitrários fora dessa fronteira, não inventa membership/tier e não fornece adapter Turso/NoSQL automaticamente.
CAP-12Valida e aplica cupons por API nativaIntegral no escopo provider-specific delimitadoCouponCode limita e redige o identificador. Stripe envia o contrato atual discounts[0][coupon], pede expansão e só aceita resposta vinculada à assinatura e ao coupon solicitados. Lemon Squeezy documenta discount code para checkout, não aplicação posterior na assinatura; ele e adapters sem contrato revisado retornam UnsupportedOperation no live em vez de falso sucesso. Mocks vazios/mock_* permanecem no-op offline explícito, e aceitação por conta real continua evidência externa.
CAP-13Estende trial facilmente por códigoIntegral no escopo provider-specific delimitadoBillable/SubscriptionHandle::extend_trial(15) agora significa 15 dias inteiros, limitado a 1–730; extend_trial_days_at fixa o relógio para retries determinísticos e set_trial_end preserva a operação absoluta explícita. Stripe envia trial_end; Lemon Squeezy envia PATCH JSON:API com trial_ends_at; ambos limitam e vinculam a resposta à assinatura/expiração. Adapters live não revisados falham explicitamente. Autorização, persistência do tempo do comando, concorrência, efeitos no ciclo de cobrança e reconciliação por webhook pertencem ao host.

O lote Capital também fechou cinco lacunas locais sem ampliar artificialmente o contrato: o derive genérico passou a compilar pela fachada pública, o renderer de fatura passou a escapar ID, e-mail, descrição e moeda, e Axum/Actix passaram a compartilhar a mesma fronteira de webhook testada. O scaffold de billing agora seleciona SQLx/Turso-primary, ativa features exatas, preserva arquivos e nega conflito de ownership antes de vincular customer. O ledger relacional opt-in agora cobre idempotência compartilhada limitada de webhook sem armazenar payload/ID em claro. Orquestração automática por webhook, exactly-once entre sistemas, reconciliação e operações live sem contract test continuam explicitamente fora da classificação integral; PDF e a ponte de entrega pós-pagamento já existem no limite descrito em CAP-04. O grace period agora é um valor tipado/derivável, não uma alegação de persistência ou automação do provider.

O hardening posterior dos gateways unificou todos os métodos HTTP live revisados numa única fronteira: pool compartilhado, timeouts finitos de conexão e request, redirects e proxy ambiente desabilitados, JSON limitado a 1 MiB e URL de checkout absoluta/HTTPS/sem credenciais ou fragmento. Request inválido, falha de transporte, status HTTP, rate limit com Retry-After numérico limitado, corpo excessivo, JSON inválido e resposta semanticamente incompatível agora produzem ProviderFailure redigido com classe permanente/transiente/rate-limited. Fixtures reais cobrem redirect, desconexão, 429, 4xx, 5xx, oversize, JSON e vazamento negativo de segredos. O framework não faz retry automático de mutação financeira; idempotência encaminhada pelo provider, backoff e reconciliação continuam obrigatórios no host. Essa evidência elevou Capital de 89/B para 92/A.

O cluster fiscal local subsequente passou a exigir e expor o tpAmb dentro da DPS assinada, impedindo reinterpretação entre homologação e produção. O FiscalCommandJournal registra antes de qualquer transporte do host um comando opaco e depois exatamente um resultado autorizado/rejeitado já vinculado pelo parser. A cadeia HMAC nomeada, os limites de 4.096 eventos/16 MiB, replay exato, conflito de chave, reinício, recuperação de pendentes, symlink, adulteração, writer concorrente, quota e checkpoint externo têm regressões executáveis; XML, chave de acesso, mensagens, corpo e certificado não entram no arquivo. Isso alcança honestamente o teto Capital de 93/A, a sétima das 15 crates ativas a fechar sua meta local. Transporte, retry, request/outbox autoritativo, reconciliação multi-writer, confiança ICP-Brasil e homologação SEFIN continuam fora da alegação.

O avanço de CAP-04 não alterou sozinho os totais anteriores: a alegação histórica inclui orquestração automática após qualquer pagamento, enquanto o contrato entregue exige recibo final explícito e deixa o claim durável/exactly-once fora da biblioteca. A classificação só sobe para integral se essa lacuna for fechada sem inventar garantias de transporte.

CAP-06, em contraste, descrevia a API de reporte nos dois providers. O contrato provider-specific, os parsers vinculados e as fixtures de protocolo fecharam esse escopo delimitado sem transformar fixture em aceitação por conta live nem fornecer a outbox da aplicação. CAP-10/CAP-11 agora acrescentam uma identidade de billing compartilhada, uma reserva idempotente que bloqueia o callback antes do over-limit e um store SQL transacional exercitado em quatro protocolos. Isso atualizou o inventário para 97/91/2, 51,1% estrito e 75,0% ponderado; membership, tier/reconciliação, migrations e writes que não usam a fronteira continuam deliberadamente pertencendo ao host. CAP-12/CAP-13 acrescentam valores delimitados, protocolos e binding de resposta para cupons/trial, levando o total a 99/89/2, 52,1% estrito e 75,5% ponderado, sem inventar paridade entre gateways ou aceitação por conta live. CONNECT-10 adiciona o coordenador process-local de refresh com binding de identidade e troca de estado pós-validação, atualizando a régua para 100/88/2, 52,6% estrito e 75,8% ponderado; persistência cifrada e coordenação distribuída continuam fronteiras explícitas. MAIL-06 substitui o antigo falso caminho bearer direto por SES v2 nativo opt-in no SDK oficial, SigV4, credenciais temporárias ou provider/config do chamador e paridade local de payload/attachments/RFC 8058, levando a régua a 101/87/2, 53,2% estrito e 76,1% ponderado. O contract loopback prova protocolo e assinatura; conta AWS live, identidade, sandbox, quotas, reputação e entrega continuam evidência externa. MAIL-08 acrescenta limites comuns antes do encoding, metadata inequívoca, Content-ID único e referenciado, Debug sem bytes e a árvore MIME SMTP mixed/alternative/related, levando o total a 102/86/2, 53,7% estrito e 76,3% ponderado. O contrato histórico “zero-copy” é refutado: os bytes são owned e os transports copiam/Base64-encodam conforme o protocolo; limites de provider e inspeção do conteúdo opaco continuam fora. ORM-31 fecha depois o contrato histórico delimitado de revisões auditáveis e leva o total a 103/85/2, 54,2% estrito e 76,6% ponderado. Isso não transforma auditoria relacional em backup, histórico bulk ou export durável.

Inventário item a item — rullst-connect histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
CONNECT-01Macro define_provider! reduz boilerplateIntegral no escopo delimitadomacros.rs gera configuração validada, scopes, state, PKCE, transporte injetável e modo de credencial; os providers padrão e testes do macro usam a mesma superfície.
CONNECT-02Extractor nativo AuthCallback para Axum e ActixIntegral no escopo delimitadoextractors.rs implementa os dois traits atrás das features correspondentes, preserva code/state/error reais e possui negativas para parsing e estado.
CONNECT-03Revogação remota de token nos providers suportadosParcial; seis adapters delimitadosA API distingue access/refresh token, limita e valida o valor antes da rede e redige credenciais/payloads de HttpRequest/HttpResponse::Debug. Google aceita ambos sem hint; Discord e Apple enviam o hint; GitHub aceita access; Auth0 e Cognito aceitam refresh conforme seus protocolos. Fixtures capturam método, endpoint percent-encoded, auth e form/JSON, além de erro redigido/offline. Facebook, LinkedIn, Microsoft, X e OIDC genérico continuam unsupported; sucesso idempotente é aceitação do protocolo e não encerra a sessão/persistência local, portanto a alegação universal permanece parcial.
CONNECT-04MockProvider para testes de aplicaçõesIntegral no escopo delimitadoproviders/mock.rs fornece fluxo determinístico e testes; credenciais vazias/mock_* também selecionam transporte offline sem rede.
CONNECT-05Validação OIDC automática para Google/Apple sem HTTP adicionalParcialGoogle, Apple e OIDC custom validam assinatura e claims por JWKS. A frase “sem chamada HTTP adicional” era incorreta: chaves precisam ser obtidas e atualizadas, embora o cache reduza chamadas.
CONNECT-06URL estrita, zero panics e PKCE em todos os providersParcial; absoluto não recomendadoValidação HTTPS/loopback, geração/verificação PKCE, state e propagação do verifier existem com negativas. Ausência universal de panics e integração correta do host não podem ser inferidas dessa fundação.
CONNECT-07Transporte agnóstico por HttpClientIntegral no escopo delimitadoclient::HttpClient, request/response types e with_http_client permitem transporte injetado; o adapter é responsável por TLS, proxy, redirects e demais políticas.
CONNECT-08Integração pronta com rullst-orm, SQLx e DieselParcialIntoDatabaseUser<T> é apenas o contrato que a aplicação implementa. Connect não depende desses ORMs nem fornece upsert, transação, tenant ou schema.
CONNECT-09Proxy corporativo configurável nativamenteIntegral no contrato HTTP(S) delimitadoReqwestClient::try_with_proxy e try_with_proxy_basic_auth instalam um proxy explícito para todo o tráfego e desativam a descoberta ambiente do sistema. URL credentials/path/query/fragment e schemes não HTTP(S) são recusados; autenticação remota exige HTTPS (HTTP só em loopback), o password fica em SecretString durante a construção e erros não o ecoam. Um servidor proxy local prova request-target absoluto e Proxy-Authorization. PAC/WPAD, SOCKS, proxy mTLS e certificação de rede corporativa continuam fora.
CONNECT-10Refresh automático quando access token expiraIntegral no escopo process-local, com persistência shared-local opcional delimitadaRefreshableTokenState exige access/refresh token, identidade do usuário no provider, relógio confiável e lifetime positivo/limitado. AutoRefreshingSession<P> detecta a janela de expiração, impede chamadas sobrepostas e faz quem aguardava reutilizar o primeiro refresh válido; mantém o token anterior quando o provider não rotaciona, adota rotação válida e só substitui estado após vincular a mesma identidade e validar token/lifetime. EncryptedTokenSnapshot preserva a geração em envelope AES-256-GCM versionado, delimitado e redigido, autenticando key_id, provider e conta local confiável. A feature sqlite acrescenta quota persistida, transações BEGIN IMMEDIATE, CAS de sucessor exato e delete condicional; restart, duas instâncias, stale writer, chave/tamper/corrupção, quota/config, symlink, API pública e facade têm regressões. Autorização, custódia/rotação de chave, lease em torno da chamada remota, reconciliação de uma rotação perdedora, retry/backoff, diretório/backup, multi-host, replay da operação original e reautenticação continuam no host. Providers sem refresh continuam falhando explicitamente.
CONNECT-11Avatar universal sempre em resolução ótimaParcial; garantia não recomendadaParsers normalizam campos conhecidos e alguns providers ajustam URL/tamanho. Vários IdPs não entregam avatar e uma resolução “ótima” não é propriedade controlável pela crate.
CONNECT-12Integrações nativas Leptos e DioxusParcialAuthCallback é um DTO deserializável e a feature Leptos não puxa runtime; não existem extractors/runtime adapters E2E específicos para Leptos/Dioxus.
CONNECT-13Fluxos HTTP reais exercitados por mock serverIntegral no escopo delimitadotests/integration_tests.rs e suites de providers usam Wiremock para sucesso, erro HTTP, token/perfil incompleto e falha de parsing sem credenciais live.
CONNECT-14Retry exponencial para rate limitIntegral no escopo delimitadoA feature retry usa reqwest-retry, limita tentativas configuradas e possui testes Wiremock de retry, headers, auth e corpos. Não substitui orçamento global ou coordenação distribuída.
CONNECT-15Erros de provider normalizadosIntegral no escopo delimitadoResponseWrapper::error_for_status converte status/payload em ProviderApiError { code, message }, limita mensagem e possui matriz de testes. Payloads proprietários ainda podem cair no fallback tipado.
CONNECT-16UniversalProfile estritamente normalizadoIntegral no escopo delimitadoConnectUser::universal_profile() agora retorna somente id/name/email/email_verified/avatar. A projeção é serializável e não inclui tokens, raw payload ou expiração; Serde de ConnectUser também omite access/refresh tokens.
CONNECT-17AuthSession salva e valida state/nonce automaticamenteIntegral no contrato Axum/tower-sessions delimitadobegin_oauth_session gera state + PKCE e begin_oidc_session adiciona nonce; ambos guardam verifier/nonce por dez minutos no servidor e recusam destino não HTTPS/loopback, credenciais/fragment e parâmetros gerenciados conflitantes. O extractor remove e salva imediatamente o único desafio ativo antes da comparação constant-time e entrega ExchangeParams exatos. Regressões cobrem round-trip, replay sequencial, expiry, mismatch, missing state, substituição, URL hostil e redaction. O host ainda configura store durável, cookie Secure/HttpOnly/SameSite, TLS, redirect registrado, linking/recovery idempotente e conformance live; iniciar outro login na mesma sessão invalida o anterior. A trait genérica de store não oferece compare-and-delete distribuído entre requests que já carregaram o mesmo registro; esse cenário exige adapter atômico da aplicação.
CONNECT-18Geração nativa do client secret Apple a partir de .p8Integral no escopo delimitadoO adapter gera JWT ES256 curto com team/key/client IDs, rejeita chave inválida e possui testes de header/claims; o .p8 deve chegar como PEM protegido pela aplicação.
CONNECT-19Mock IdP local simula perfeitamente OAuth/OIDCIntegral como fixture local delimitado; “perfeitamente” refutadoO router Axum valida issuer/callback HTTP loopback e um cliente exato, limita grants/tokens process-local a 64, consome authorization code expirável uma vez, verifica PKCE S256, propaga nonce, assina ID token EdDSA, publica discovery/JWKS e protege userinfo com bearer emitido. Um teste loopback percorre o OidcProvider real e rejeita replay/PKCE inválido. Chave/credenciais são fixtures públicas; não há UI/consent, refresh/device/federação, durabilidade, rotação, exposição pública ou conformance OIDC.
CONNECT-20Spans detalhados para exchange e profile fetchIntegral no escopo delimitadoO transporte comum instrumenta método/URL/status e OIDC instrumenta operações sem registrar form/token. A aplicação ainda escolhe subscriber, redaction, sampling e export.
CONNECT-21OIDC discovery em uma chamadaIntegral no escopo delimitadoOidcProvider::discover valida issuer exato e endpoints HTTPS/mesmo loopback, injeta cliente em testes e recusa metadata divergente/incompleta.
CONNECT-22Device Authorization Flow RFC 8628Integral no escopo delimitadoGitHub implementa request/poll, interval/erros e possui testes Wiremock; a trait retorna unsupported nos demais providers. Isso não implica certificação universal de Smart TVs.
CONNECT-23Validação criptográfica JWKS de ID tokenIntegral no escopo delimitadoGoogle, Apple e OIDC custom restringem algoritmos assimétricos, validam issuer/audience/exp/nonce e usam cache isolado com refresh por kid e stale conhecido limitado.

Os fechamentos de CONNECT-16 e CONNECT-19 corrigiram duas contradições: o primeiro impede que a projeção normalizada reexponha credenciais; o segundo substitui o antigo token opaco por um fluxo OIDC criptograficamente verificável, mas rejeita a palavra “perfeitamente” e permanece estritamente local. Antes, SecretString escondia tokens em Debug, mas serializadores custom voltavam a expor as credenciais em JSON. A serialização agora omite ambos os tokens e a projeção pública exclui também payload bruto e metadados de credencial.

Inventário item a item — rullst-iot histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
IOT-01Modelos e serializers operam em no_stdIntegral no escopo delimitadorullst-iot usa #![no_std] sem a feature std; o gate de fronteiras e os workflows embedded compilam a superfície declarada. Compilar um target não equivale a executar em placa ou QEMU.
IOT-02SensorTelemetry unifica métricas de sensoresIntegral no escopo delimitadoO tipo serializável contém device, métrica, valor e timestamp e possui testes locais. Unidade, qualidade, calibração e schema de transporte permanecem responsabilidades do protocolo/aplicação.
IOT-03Helpers de payload MQTT e CoAPIntegral no escopo de codificação delimitadoMqttPublish emite um PUBLISH MQTT 5 bounded com tópico validado, Remaining Length mínimo, invariantes QoS/packet ID e properties vazias. CoapRequest emite GET/POST/PUT/DELETE RFC 7252 bounded com token, URI-Path/Content-Format ordenados e payload marker correto. Vetores, limites, robustez determinística, API externa e builds no_std são testados. Não há socket, TLS/DTLS, CONNECT, ack/retry, broker/LwM2M ou teste de interoperabilidade; esses transports continuam separados.
IOT-04cargo rullst make:iot <DeviceName> gera arquivos de nodeIntegral no escopo delimitadoO generator agora valida identificadores, recusa traversal/colisão, exige um projeto Rullst, habilita a feature iot, registra os módulos e gera código pela fachada rullst::iot. O teste process-level materializa um projeto, executa Clippy offline e prova as recusas. Ele gera telemetria local, não transporte ou firmware.
IOT-05Helpers GPIO e I2C nativos/cross-platformParcialGpioPin representa estado em memória e I2cHelper constrói bytes de frame. Não acessam registradores, HAL, barramento ou hardware e não constituem drivers nativos.
IOT-06Driver Modbus RTU/TCP completoParcialModbusFrame monta uma requisição RTU e CRC-16. Não há serial/TCP, parser de resposta, timeout, framing MBAP, retries nem contract tests com PLC.
IOT-07Servidor BLE GATT de telemetriaParcialGattService e GattCharacteristic são estruturas de dados. Não há servidor, advertising, radio stack, conexão, segurança ou integração de plataforma.
IOT-08Micro-LLM e engine de anomalia no dispositivoParcialAnomalyDetector é um classificador estatístico por limiar e agora falha fechado para floats/configuração não finitos. Não executa LLM, treino, inferência de modelo ou detecção adaptativa.
IOT-09Micro-dashboard embeddedIntegral no escopo delimitadoIotDashboard renderiza um card HTML local, escapa labels não confiáveis e o identifica como SNAPSHOT; testes cobrem XSS. Não inicia servidor, HTMX runtime, stream live nem mede footprint em microcontrolador.
IOT-10Mesh P2P self-healing sobre ESP-NOW/Thread/ZigbeeParcialMeshTopology é um registro em memória que recomenda o node online com maior RSSI. Não roteia pacotes, detecta falha, repara topologia nem integra os transports nomeados.
IOT-11OTA Ed25519 delta, dual-bank e rollback end-to-endParcialO gate verifica manifest assinado, target, versão, hash, tamanho e counter e só então expõe a partição inativa. O contrato no_std de store carrega estado persistente e exige CAS monotônico/durável antes do commit; regressões públicas cobrem reinício/replay, retry, corrupção e writer obsoleto. Uma implementação hardware-backed, download, delta patch, flash/read-back, bootloader, power-loss recovery e device tests não existem.
IOT-12Bindings HSM para ATECC608A/TPM/STSAFEAusente; depende de hardwareA feature experimental expõe somente bytes determinísticos de SimulatedHsmDevice. Não há binding, key custody, operação criptográfica protegida ou placa testada; a fixture não conta como progresso de hardware.
IOT-13ML-KEM/Kyber compacto para edgeAusente; pesquisa/auditoria externaSimulatedPqcFixture não implementa criptografia. Um protocolo concreto e uma implementação auditada com vetores oficiais são pré-requisitos; PQC caseiro não é recomendado.
IOT-14Power governor controla deep sleep/wake/solarParcial; depende de hardwarePowerGovernor calcula uma recomendação pura a partir de voltagens informadas. Não controla sleep, interrupções, charging, harvester ou PMIC.
IOT-15Digital Twin bidirecional em tempo real com Studio/NexusParcialDigitalTwin mantém readings locais e produz JSON com erro tipado/fallback seguro; não possui transport, actuator command, conflito/ordenação, persistência ou integração Studio/Nexus.

O lote IoT removeu três falsos sinais locais sem fabricar integrações externas: nomes perigosos não escapam mais do projeto pelo generator, labels de telemetria não entram cruas no HTML e um snapshot não é rotulado como dispositivo online. Drivers, redes, flash, HSM e PQC permanecem deliberadamente fora do resultado integral até existirem implementação e provas proporcionais em hardware real.

Inventário item a item — rullst-mail histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
MAIL-01Trait uniforme para Log, SMTP, Resend e SendGridIntegral no escopo delimitadoMailDriver é o contrato comum e os drivers nomeados o implementam, com SMTP atrás de feature. A uniformidade do método send não implica paridade de scheduling, respostas de provider, aceitação ou inbox delivery.
MAIL-02Builder fluente zero-cost para recipients, subject, HTML e textoIntegral no escopo delimitadoMessage possui os builders e testes de composição. É uma estrutura owned que clona/aloca onde necessário; “zero-cost” não é usado como promessa de benchmark.
MAIL-03Kani/property tests eliminam todos os panics de formataçãoParcial; absoluto não recomendadoExiste um harness Kani pequeno e testes/fuzzing delimitados. Eles não cobrem todo driver, dependência, input, generator ou aplicação e não provam ausência universal de panic.
MAIL-04Dispatch assíncrono automático pela Queue com retryIntegral no escopo delimitadoMail::init_queue, envelope versionado e register_mail_handler fazem dispatch não bloqueante e propagam falha ao worker, que possui retry/dead-letter configurável. A aplicação ainda precisa iniciar/operar a fila; a semântica é ao menos uma vez.
MAIL-05Circuit breaker/failover distingue 5xx/rate limit e alerta telemetriaIntegral no contrato in-process delimitadoMailError classifica permanente/transiente/rate-limit; REST mapeia transporte, HTTP 5xx, 429 e 4xx, limita/redige body e captura Retry-After delta até um dia; SMTP separa resposta 4xx de 5xx. FailoverDriver só conta/encaminha transiente ou rate-limit, recusa fallback para config/validation/4xx permanente, guarda count/timestamp no mesmo lock poison-aware e emite eventos tracing de baixa cardinalidade sem body. Testes provam 400 sem fallback, 503/429 com fallback/counter/rate metadata e lock envenenado sem entrega primária. Estado/coordenação distribuída, orçamento global e operação do subscriber/alerta continuam deployment.
MAIL-06Drivers HTTP nativos Postmark e AWS SESIntegral no contrato de protocolo delimitadoPostmark usa sua API live. Com aws-ses, AwsSesDriver usa o SDK AWS oficial para SES v2 Simple e SigV4 regional, aceita credencial temporária/provider rotativo/config oficial do chamador, serializa HTML/texto, attachments/CID e RFC 8058, recusa limites de fields/estimativa encoded acima de 40 MiB, preserva Retry-After limitado e vincula sucesso a MessageId; fixture loopback prova path, escopo ses/aws4_request, session token, payload e erro tipado/redigido. O construtor legado só faz mock ou proxy bearer explícito e nunca envia uma requisição AWS sem assinatura. Conta live, identidade/domínio verificado, sandbox, IAM, quota, reputação, suppression/bounce e inbox delivery não são alegados.
MAIL-07send_at/send_in dão scheduling preciso via queue/providersIntegral no escopo temporal delimitadoQueue::dispatch_at e Mail::enqueue persistem até 366 dias em SQLite/Redis, não liberam antes do milissegundo devido e removem o timestamp apenas após o claim; overflow falha na pre-flight. O Redis passa contrato live checksum-pinned no CI/release. Sem queue, Resend/SendGrid recebem o timestamp nativo; caminhos reais SMTP/Postmark/Log/SES recusam futuro, enquanto fixtures offline podem preservá-lo para assertions. “Preciso” significa nunca antes do due time dentro desse contrato: execução ocorre no primeiro poll posterior, é ao menos uma vez e não garante aceitação/exatamente-uma-vez no provider.
MAIL-08Attachments/CID zero-copy em Resend, SendGrid e PostmarkIntegral no contrato owned e bounded; “zero-copy” refutadoA pre-flight comum limita 32 itens, 20 MiB por item e 25 MiB raw agregados; valida basename, MIME parameter-free, CID ASCII único e sua referência no HTML; e omite bytes de Debug. Resend, SendGrid, Postmark, SES nativo e SMTP consomem esse modelo, com SMTP exercitando MIME mixed/alternative/related, disposition, Content-ID e Base64. Os bytes são owned/copiados e o encoding aloca. O guard opt-in reconhece formatos bounded e bloqueia magic executável, type spoofing, PDF/SVG ativo, secrets e links inseguros; não é antivírus, sandbox, archive recursion ou CDR, e limites/aceitação do provider podem ser menores.
MAIL-09Preflight verifica deliverability e 150+ domínios descartáveisParcialO pipeline executa sintaxe local bounded e uma lista estática ampla antes do envio. Não consulta DNS/MX, reputação, mailbox, bounce ou provider e não garante deliverability.
MAIL-10HTML gera plain-text fallback automaticamenteIntegral no escopo delimitadoMessage::html chama deterministically strip_html_to_plain_text quando texto explícito não existe; testes cobrem tags, entidades e fallback. Não é um parser HTML/MIME normativo.
MAIL-11DLP intercepta secrets em subject/HTML/textoIntegral no escopo delimitadoO pipeline obrigatório aplica redaction aos padrões documentados e testes cobrem AWS keys, credenciais, bearer e PEM. É heurístico e não promete ausência total de vazamento.
MAIL-12Interceptor universal de phishing/homographParcialHelpers rejeitam schemes selecionados e mistura Latin/Cyrillic/Greek em URLs reconhecidas. A extração não é um parser completo de HTML/URL/IDNA e pode ter falsos positivos/negativos.
MAIL-13RFC 8058 obrigatório e compliance Google/YahooParcialDrivers suportados emitem List-Unsubscribe e List-Unsubscribe-Post quando a aplicação fornece URL HTTP(S). Sem configuração não há header; política de lista, endpoint e compliance pertencem ao produto/deploy.
MAIL-14Tracking zero-cookie preserva privacidade/LGPD/GDPRParcial; compliance não recomendadoTokens v2 são purpose-bound, HMAC-verificados em constant time, expiráveis e opcionalmente one-shot em memória. HMAC não cifra: email e target URL continuam base64-readable; consentimento, minimização, IP/logs, retenção e store distribuído são externos.
MAIL-15MailTrap/MemoryDriver com assertions fluentesIntegral no escopo delimitadoO driver em memória captura mensagens offline e as assertions cobrem destinatário, subject, body, attachments, CID, scheduling e unsubscribe. Estado global exige isolamento de teste pelo host.
MAIL-16MailFactory oferece cinco fixtures transacionaisIntegral no escopo delimitadoAs cinco factories existem e agora escapam valores dinâmicos em HTML, com regressão adversarial. São dados de teste/preview, não workflows de autenticação, billing ou segurança.
MAIL-17Resolver seleciona automaticamente credenciais pelo TenantContextIntegral no contrato context-bound delimitadoregister_for_context/send_for_context recebem diretamente o TenantContext derivado de membership autenticado, selecionam o driver in-process sem identidade task-local/global, recusam IDs inválidos e falham fechado se o registro estiver indisponível. A regressão usa dois contextos e dois drivers para provar não interferência. Persistência/encriptação/rotação de credenciais e sincronização entre processos continuam responsabilidades da aplicação/deployment.
MAIL-18make:mail gera Welcome/Reset/OTP/Invoice/NFS-e/DunningIntegral no escopo delimitadoWelcome, Reset, OTP, Invoice e Custom, mais os comandos exatos make:mail-invoice [Name] e make:mail-dunning [Name], validam identifiers, recusam traversal/colisão, habilitam as features necessárias, registram módulos e escapam HTML. O fiscal consome FiscalResponse, recusa proveniência contraditória e imprime OfflineMock como [PREVIEW — NOT AUTHORIZED]; o dunning expõe estágios D+1/D+3/D+7 sem inferir agenda ou estado. Um projeto materializado passa Clippy, executa os sete templates, recusa link perigoso e prova preservação em colisão. Autorização fiscal, due state, scheduling, entitlement e política continuam externos.

O hardening Mail fechou o generator que antes parecia pronto mas emitia imports ausentes e templates vulneráveis a markup injection. Os dois comandos que só existiam na documentação agora também são código materializado, com proveniência fiscal e dunning explícitos. O caminho enganoso que tentava falar com AWS sem SigV4 foi removido e o caminho nativo opt-in agora delega assinatura, transporte e credenciais ao SDK AWS oficial. Isso melhora a segurança local sem promover mocks, bearer proxy, fixture SigV4, HMAC ou heurísticas a conta live, deliverability, privacidade legal, autorização fiscal ou inbox delivery. O fechamento delimitado de MAIL-08 adiciona uma fronteira comum antes do transporte e corrige a árvore MIME SMTP sem renomear cópias como zero-copy. A validação base de metadata não se transforma sozinha em inspeção do arquivo nem em prova de aceitação por contas live.

O lote de teto local posterior adicionou três controles opt-in sem ampliar essas promessas: AttachmentInspectionGuard executa um inspector estático antes do transport; SuppressionGuard consulta estado manual/hard-bounce/spam complaint process-local ou SQLite compartilhado-local, com replay exato, quotas imutáveis, restart e concorrência de duas instâncias; e ObservedMailDriver emite somente provider/outcome/latência/contagem e flags de scheduling/tenant. Eventos do provider ainda precisam ser autenticados antes de entrar no store, o scanner local continua heurístico e operação distribuída/inbox delivery continuam externas. Essas evidências elevam rullst-mail ao teto local 95/A, não a certificação nem a uma garantia de entrega.

Inventário item a item — rullst-nexus histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
NEXUS-01#[derive(Nexus)] gera Create/Edit/Delete para qualquer structIntegral no escopo delimitadoA derive gera NexusModel para structs com campos nomeados, permite table/label/icon/PK/widgets e tenant text explícitos e possui prova de compilação pela fachada rullst. O host ainda registra o model e fornece schema SQLx, auth, membership e privilégios; tuple structs/enums não são aceitos.
NEXUS-02Data tables com paginação, busca e sorting server-sideIntegral no escopo delimitadobuild_table_query limita a 15 linhas, usa offset saturating, binds para busca e allowlist de colunas/direção. Para models tenant-scoped, list/search/edit/create/update/delete/batch incluem o TenantContext confiável e falham sem ele; o teste HTTP SQLite nega leitura/mutação cross-tenant e input que tenta escolher o tenant. Contagem total, cursor pagination, otimização de índice, identity/membership e models globais permanecem fora.
NEXUS-03Boolean vira toggle, Enum dropdown e texto textarea automaticamenteIntegral no contrato de metadata explícita delimitado; automação absoluta refutadaBoolean é inferido e renderizado como checkbox; Enum e Textarea são declarados na derive e renderizados como select/textarea. try_build() limita e valida modelos, fields, PK, labels, relações e opções; POST/PUT limitam pares e bytes, recusam campo desconhecido/protegido, duplicata não Boolean, enum fora da allowlist e valor semântico inválido antes do SQL bindado. Testes cobrem macro, HTML e negativos HTTP. Variantes de enum externo e intenção multiline não podem ser inferidas com segurança só pelo tipo do field, portanto permanecem metadata explícita em vez de “mágica”.
NEXUS-04Batch actions para Delete All/DeactivateIntegral no escopo delimitadoO handler aceita somente IDs selecionados, limita 1.000, parametriza valores, recusa verbos desconhecidos e executa delete/deactivate em SQLite no teste de integração. Deactivate só aparece para Boolean gravável is_active/active; quando audit é obrigatório, scope, mutação e registro minimizado pertencem à mesma transação. Não é update arbitrário nem audit trail append-only/tamper-evident.

O lote Nexus também fechou uma divergência de segurança: labels, nomes de tabela/campo e ícones registrados são escapados ou estritamente normalizados nas telas CRUD, sidebar, dashboard e fallback do assistente; falhas SQL não são devolvidas ao navegador. O tenant opt-in protege todas as rotas built-in e o audit obrigatório registra actor/tenant/table/action/key opcional/count/outcome/ correlation/time/version ou desfaz a mutação. Isso não transforma metadata em identity/membership, não autoriza models globais/custom routes e não torna a tabela do mesmo banco append-only, tamper-evident ou separada.

Inventário item a item — rullst-studio histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
STUDIO-01Data Browser permite view/filter/edit/delete sem SQLIntegral no escopo relacional local delimitadoO browser lê/busca/pagina e, somente após o middleware debug-loopback/same-origin instalar sua marker não forjável, atualiza valor primitivo não-PK ou exclui exatamente uma linha por PK completa. Tabela/coluna/PK vêm do schema allowlisted, valores são tipados/bindados, corpo/campos são limitados, tipos proprietários ficam read-only e delete exige DELETE <table>. SQLite, PostgreSQL, MySQL e MariaDB passam contratos executáveis. Tenant/RBAC da aplicação, audit trail, rollback e administração compartilhada/produção não são inferidos.
STUDIO-02Swagger/OpenAPI é auto-gerado e interativoParcialutoipa-swagger-ui fornece a interface interativa quando o host chama Studio::with_openapi. Studio não infere um documento completo a partir de rotas Axum arbitrárias.
STUDIO-03Logger intercepta requests, payloads, headers e tempo em tempo realParcial; captura total não recomendada por padrãoMiddleware e SSE expõem método, URI, status e latência; markup não confiável é escapado. Corpos e headers não são capturados porque podem conter auth, sessão, pagamento e PII. A aplicação ainda precisa posicionar o middleware no tráfego que deseja observar.
STUDIO-04Monitor mostra jobs pendentes, falhos e concluídosIntegral no contrato SQLite opt-in delimitadoUma Queue fornecida expõe até 50 registros reais, contagens pending/processing/failed/completed, retry/purge de falhas e purge de sucessos. SQLite apaga sucesso por padrão ou sqlite_with_completed_history retém explicitamente 1–100.000, muda status e poda na mesma transação; regressão atravessa driver, facade e HTTP Studio. Payload continua armazenado, logo acesso/retenção são política do host. Redis/custom inspection continua capability-specific e não fabrica snapshot.
STUDIO-05ER diagram é gerado automaticamente do schemaIntegral no escopo SQLx relacional delimitadoMetadata SQLite, PostgreSQL, MySQL e MariaDB é consultada com valores bindados; PK/FK/tipos alimentam Mermaid com identifiers normalizados, securityLevel: strict e estado indisponível explícito. Turso-primary e stores polyglot não fingem ser um schema SQLx relacional.
STUDIO-06Feature Flags Manager gerencia flags em tempo real via DbFeatureDriverIntegral no escopo in-process delimitadoA UI lê a mesma tabela rullst_feature_flags e alterna atomicamente a linha nomeada com valor bindado; depois de exatamente uma linha atualizada, avança um epoch constante que invalida imediatamente todas as instâncias DbFeatureDriver já aquecidas no processo. A regressão aquece por 60 s, alterna via HTTP e observa o novo valor sem esperar. Outros processos e writers diretos continuam no TTL sem pub/sub da aplicação; criar/editar rollout/variants não é parte da alegação delimitada.
STUDIO-07Environment & Config Viewer inspeciona configuração com segurançaIntegral no escopo delimitadoValores de ambiente usam redaction deny-by-default; somente uma allowlist pública aparece. A projeção do RullstConfig global mostra environment/port/driver e contagens/políticas não secretas, omitindo URLs, paths, cookies, tokens e credenciais.

Os dois itens que estavam abertos no roadmap histórico foram implementados depois desta classificação sem reescrever o snapshot: o profiler SQL usa somente labels redigidos de spans v1 autenticados e chama repetição de possível N+1; o inspetor Memory/Redis é metadata-only, mascara a chave por HMAC e não fornece conteúdo nem flush global. Esses limites são parte da capacidade v12 atual, não prova de diagnóstico perfeito ou administração remota segura.

O hardening Studio corrigiu ainda um falso positivo de teste: a antiga consulta SELECT * podia falhar ao mapear BOOLEAN do SQLite via SQLx Any e o erro era silenciosamente convertido em tabela vazia com HTTP 200. A projeção agora faz casts explícitos, propaga a falha e o teste exige os registros reais. A busca de metadata do ER deixou de interpolar nomes em SQL e a fronteira Mermaid deixou de aceitar identifiers brutos ou securityLevel: loose. A nova escrita local não reutiliza o router bruto como autoridade: sem a marker privada produzida pelo access middleware, o handler retorna 403 antes de consultar o banco.

Inventário item a item — rullst-security histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
SEC-01Honey instala traps e banimento DashMap com latência zeroParcial; absoluto não recomendadoHoneypotState usa rotas exatas, TTL/cardinalidade limitados e identidade do socket, com concorrência testada. Nenhum middleware pode prometer latência literalmente zero.
SEC-02Sanitizer cobre HTML/SVG e CSP nonce/anti-clickjackingIntegral no escopo delimitadoammonia preserva apenas o subconjunto HTML permitido e remove conteúdo/atributos inseguros; CspSecurityLayer gera nonce por request e os headers compartilham a mesma extensão. Isso não torna SVG arbitrário seguro.
SEC-03RBAC/ownership impede IDOR/BOLAIntegral como primitive explícitaRbacGuard, UserContext e authorize_owner_or_role cobrem role, owner e tenant em testes negativos. O host ainda deve instalar o guard antes de cada operação e filtrar queries pelo tenant.
SEC-04Audit log HMAC encadeado é tamper-proof e verificável offlineIntegral como cadeia tamper-evident delimitadaEncoding versionado/domain-separated, chave mínima, sequence/predecessor e HMAC são verificados. Durabilidade, exclusão, rollback do storage e âncora externa dependem do AuditLogger; “tamper-proof” absoluto foi removido.
SEC-05cargo rullst audit --ai encontra secrets/CVEs e produz recomendações AIParcialO comando executa heurísticas bounded e cargo audit quando disponível, falha diante de findings/check solicitado incompleto e oferece recomendações determinísticas. Exceções explícitas e estritamente validadas usam --audit-ignore; o resultado preserva os IDs como NO FINDINGS OUTSIDE EXCEPTIONS, nunca como ausência de findings. O nome legado --ai não chama um modelo nem substitui análise humana ou governança de exceções.
SEC-06Radar mostra vetores, reputação IP e incidentes AI liveParcialStudio/Nexus usam LiveSecurityEvent v1 e snapshots locais limitados, sem dados inventados. Reputação externa, feed versionado, persistência e investigação SOC não existem.
SEC-07Sentinel AI autônomo classifica ataques e emite Proof-of-WorkParcial; autonomia/AI não recomendadas sem evidênciaThreatClassifier classifica deterministicamente credential stuffing, API scraping e automação distribuída a partir de agregados confiáveis e thresholds transparentes. ProofOfWorkGate emite tokens HMAC aleatórios, subject-bound, expirantes, limitados e one-shot no processo, com corrida/replay/tamper testados. Não coleta tráfego, não usa IA, não atribui botnet, não bloqueia automaticamente e não fornece replay distribuído, acessibilidade ou garantia anti-DDoS.
SEC-08RASP bloqueia SQLi/XSS/traversal/SSRF/RCE/JNDI com latência zeroParcialURI, headers e corpos textuais/JSON bounded são inspecionados antes do handler e falham fechados quando um corpo declarado não pode ser examinado. Regras por substring não são parser completo e “zero latency” não é prometido.
SEC-09Vault impede heap dumps e oferece AES/ChaCha transparente no ORMParcialVaultSecret zeroiza seu buffer no drop e FieldEncryptor fornece AES-256-GCM versionado com AAD/keyring; o ORM possui encrypted fields nesse limite. Cópias/heap capture não podem ser impedidos e ChaCha20-Poly1305 não existe.
SEC-10Headers OWASP garantem nota A+Parcial por causa da garantia externaHSTS, CSP nonce, Permissions-Policy, COOP, COEP, CORP, XFO e nosniff são aplicados/testados. A nota depende de todas as respostas, conteúdo, TLS, proxy e configuração do deployment.
SEC-11Login tarpit 0–5 s e jail temporárioIntegral no escopo in-memory delimitadoLoginGuard limita identidades, expira estado, aplica progressão e jail; record_login_failure_and_wait executa o sleep para evitar que handlers apenas ignorem a duração. Estado distribuído continua externo.
SEC-12DLP de resposta garante zero vazamentoParcial; absoluto não recomendadoO middleware bounded mascara padrões documentados de PEM, AWS e URLs de banco em respostas textuais. Binários, encoding, padrões desconhecidos, streams maiores e outros sinks ficam fora.
SEC-13TOTP RFC 6238 com validator e QRIntegral no escopo delimitadoSecrets têm 160 bits gerados pelo RNG do SO, códigos são comparados em constant time e inputs fracos falham fechados; build_mfa_qr_svg gera QR SVG real e limitado a partir do URI de enrollment. Recovery/transação da aplicação é separado.
SEC-14Fingerprint liga sessão a JA3/JA4 e subnet automaticamenteParcialtry_generate_fingerprint exige chave de 32 bytes, IP válido, observações bounded e normaliza IPv4 /24/IPv6 /64. A biblioteca não coleta TLS/JA3/JA4 nem invalida a sessão do host automaticamente.
SEC-15Traps dinâmicos alimentam Threat RadarIntegral no escopo local delimitadoRegistro estrito recusa query/fragment/traversal/controles, limita 1.024 traps e o middleware emite evento local ao detectar path exato. Feed/SOC externo não é implícito.
SEC-16Schema Guard valida JSON/OpenAPI, pollution, overflow e bombsIntegral no escopo de body JSON explicitamente montadoO transporte exige media type JSON exato e recusa sintaxe inválida, duplicate keys recursivas, corpo acima de 2 MiB e profundidade acima de 32. JsonSchemaPolicy compila um schema 2020-12 ou um componente explícito de OpenAPI 3.1 com limites de bytes/nós/profundidade, somente refs locais, resolução externa desabilitada e regex linear; o middleware preserva o corpo válido e retorna 415/400/422 sem ecoar valores. Auth, ownership, regras de domínio e parâmetros query/header/form continuam contratos separados.
SEC-17Log redactor suprime secrets antes de todo tracingParcialBearer repetido, assignments query/JSON, PEM, AWS e DSNs são redigidos e testados. redact_secrets precisa ser chamado ou integrado ao formatter pelo host; depender da crate não instala filtro global.
SEC-18CSWSH valida Origin, CSRF ticket e cifra framesParcialA política normaliza e compara Origin/Host/porta ou allowlist explícita, recusando origens enganosas e missing origin por padrão. Ticket CSRF e criptografia application-level de frames não existem; TLS continua obrigatório.
SEC-19SRI calcula SHA-384 e injeta tags automaticamenteIntegral no escopo explícito delimitadoHelpers geram tags escapadas a partir de bytes ou arquivos locais de até 64 MiB. O host escolhe assets e inclui as tags; não há descoberta/rewrite global do pipeline.
SEC-20SIEM transmite para Datadog/Splunk/Elastic/Slack/SyslogParcialCEF possui escaping contra field/line injection e DurableSiemSpool preserva o formato local unsigned. O novo AuthenticatedSiemSpool encadeia sequência, key_id, predecessor e payload exato com HMAC-SHA256, mantém um ativo mais sete históricos em memória zeroizing e falha fechado para forgery, chave errada/ausente, reorder, remoção interna, quota, symlink e alteração externa. Continua single-process; rollback de uma cauda inteira exige checkpoint confiável separado, e não há transport, compaction, retry, ack, backpressure, dead-letter ou adapter dos providers nomeados.
SEC-21CLI IDOR/BOLA verifica ownership em rotas parametrizadasParcialO scanner exige classificação adjacente com motivo e evidência reconhecida de owner/role/admin, inclusive Axum multiline, e retorna exit não zero. É heurística source-level, não data-flow/AST completo nem prova de query ownership.
SEC-22Cargo Geiger garante 100% memory-safe/zero unsafeParcial; absoluto não recomendadoO auditor faz scan bounded da source e --geiger exige ferramenta presente e exit bem-sucedido. Dependências/FFI e branches não executados impedem garantia universal; unsafe justificado exige política explícita.
SEC-23Fuzzing contínuo prova zero panicParcial; absoluto não recomendadoHá targets cargo-fuzz e loops determinísticos para RASP/DLP/sanitizers. Evidência pertence ao corpus, tempo, build e SHA executados, não a todas as entradas/dependências.
SEC-24Compliance exporter avalia OWASP/SOC2/HIPAA/ISOParcial; certificação não recomendadaO report lista resultados, erros e NOT CHECKED, e declara controles não avaliados. Certificação e controles organizacionais/operacionais exigem escopo, evidência externa e assessor autorizado.
SEC-25Timing guard elimina user enumerationParcial; absoluto não recomendadoPadding mínimo, jitter e synthetic work reduzem diferenças grosseiras. Scheduler, rede, cache, CPU e análise estatística continuam fora; handlers devem proteger todos os caminhos equivalentes.
SEC-26Firewall LLM bloqueia jailbreak, leak e indirect injectionParcialInspeção recursive cobre prompt/messages/content, unicode invisível, delimiters e padrões documentados; JSON declarado inválido falha fechado e payload grande retorna 413. É heurística com possíveis falsos positivos/negativos.
SEC-27Exporta SBOM CycloneDX 1.5Integral no escopo Cargo delimitadoO CLI parseia Cargo.lock como TOML, emite UUID URN válido, bom-ref único, purl e checksum SHA-256 válido quando presente. Isso não é attestation, assinatura ou análise de vulnerabilidade.
SEC-28Scanner de rede prova nenhum leak em 0.0.0.0ParcialProbes loopback e heurísticas em source/.env/ss disponível reportam listeners unspecified e retornam falha. Containers, proxy, firewall, namespaces, UDP e reachability externa precisam de observação própria.
SEC-29Stress concorrente prova ausência de racesParcial; absoluto não recomendadoSuites multi-thread exercitam rate limit, honey, login e audit chain. Elas não enumeram todos os interleavings nem testam automaticamente Redis/failover distribuído.
SEC-30Git hooks garantem zero lint/unsafe/IDORParcialO instalador é idempotente, preserva/chains hooks e executa fmt, Clippy workspace/all-features/all-targets e o scanner fail-closed de unsafe/IDOR. Hooks podem ser ignorados e as análises são bounded; CI/review permanecem autoridade.
SEC-31Doctor verifica MSRV e ferramentas de segurançaIntegral como diagnóstico delimitadoO doctor agora parseia de fato rustc, exige ≥1.96.0, verifica fmt/Clippy/audit/geiger/deny/Kani e não chama autofix de sucesso sem confirmar status e disponibilidade. Ferramentas opcionais ausentes são reportadas, não instaladas silenciosamente.
SEC-32Toda criptografia/rede é 100% Pure Rustls sem OpenSSLParcial; absoluto não recomendadoPaths first-party HTTP/SQLx selecionam Rustls quando suportado. Uma garantia sobre todas as features e dependências transitivas exige inventário por lockfile/target; Rustls tampouco prova memory safety de todo o grafo.

O lote Security acrescentou hardening verificável sem inventar integrações: inputs de fingerprint/traps/MFA passaram a falhar fechados, o Schema Guard recusa JSON inválido/chaves duplicadas e aplica uma política 2020-12/OpenAPI 3.1 explicitamente montada sem resolução externa, o redator cobre valores repetidos e JSON, o firewall alcança prompts aninhados e o Login Guard oferece uma API que aplica o atraso. O QR de MFA e o SRI file-backed são artefatos reais; o SBOM e o doctor deixaram de aceitar identificadores/versões apenas aparentes. No gate focado passaram 129 testes unitários e 36 testes de integração, robustez e concorrência em rullst-security, além de 148 testes de biblioteca no CLI e Clippy estrito nos dois pacotes. Isso continua sendo evidência local do worktree; Redis distribuído, providers, certificação e deployment exigem suas próprias provas.

Inventário item a item — rullst-orm histórico

IDAlegação histórica deduplicadaClassificação atualEvidência e limite
ORM-01Orm::transaction envolve operações com commit/rollback automáticoIntegral no escopo executor-aware delimitadopool.rs, save_with_tx e CURRENT_TX cobrem operações geradas/execute_query!; regressões verificam que uma linha do caminho de erro realmente desaparece. SQL bruto que usa diretamente Orm::pool() não adere magicamente à transação e deve usar o handle/executor fornecido.
ORM-02Collections oferecem map, pluck e key_byIntegral no escopo delimitadocollection.rs implementa e testa esses helpers e operações adicionais sobre Vec<T>. É uma extensão em memória, não um cursor de banco.
ORM-03Macros verificam colunas SQL em compilaçãoIntegral no caminho tipado delimitadoCampos geram enum e métodos por nome; filtros de String, i32, f64 e bool agora exigem o tipo persistido na assinatura, de modo que valor incompatível não compila. Builders por string, conversões customizadas, SQL dinâmico/raw e schema live continuam runtime-checked/caller-owned e não são descritos como equivalentes a query!.
ORM-04Relações polimórficas morphTo, morphMany e morphOneIntegral no contrato tipado delimitadoA derive gera lazy/constrained/batched eager loading para morph_many, morph_one e alvos inversos explícitos morph_to. A inversa valida em macro expansion o ID persistido e o discriminador String, retorna None para outro tipo e suporta vários alvos declarados; não existe registry universal, tipo desconhecido implícito ou relação polyglot.
ORM-05Factories/seeders possuem API fluente de dados fakeIntegral no escopo delimitadoCada model recebe factory make/create com count; Seeder e Orm::seed executam registros fornecidos pela aplicação. Rullst não inventa faker/domain data automaticamente.
ORM-06Many-to-many possui suporte de pivotIntegral no escopo delimitadobelongs_to_many gera lazy/constrained load e eager loading em duas consultas, com identifiers validados. Escrita/metadata extra do pivot continuam explícitas.
ORM-07Paginação retorna dados e metadataIntegral no escopo delimitadopaginate calcula total/last/current page, normaliza página zero e agora recusa per_page = 0; matrizes relacionais exercitam a consulta. É paginação por offset.
ORM-08#[orm(json)] faz casting JSON tipadoIntegral no escopo delimitadoJson<T> fornece encode/decode e integração SQLx para os backends suportados, com testes de round-trip. Evolução/versionamento do schema JSON pertence à aplicação.
ORM-09Eager loading aceita closures restritivasIntegral no escopo delimitadoMétodos with_<relation>_constrained aplicam uma closure do builder antes da consulta em lote. O escopo segue as relações geradas, não expressões relacionais arbitrárias.
ORM-10CLI gera, executa, mostra status e reverte migrationsIntegral no escopo delimitadocargo-rullst, run_artisan_with_args e o perfil Turso cobrem generate/migrate/status/rollback. O runner SQLx agora registra cada migration logo após seu sucesso, evitando reaplicação quando uma posterior falha; DDL transacional varia por backend.
ORM-11Observers/lifecycle events podem ouvir create/save/delete externamenteIntegral no escopo process-local delimitadoA derive cria registry por model e callbacks async de saving/creating/updating/deleting e pós-operação. O novo callback committed recebe snapshot próprio apenas após commit de save/delete gerado direto ou Orm::transaction; os callbacks tradicionais continuam lifecycle síncrono à mutação. Não é bus durável ou distribuído.
ORM-12Subqueries e joins avançados aceitam closuresIntegral no escopo delimitadoCTE/subquery e JoinClause existem; joins agora propagam erros da closure e recusam tabela/coluna/operador fora da allowlist. Fragmentos com sufixo raw continuam escape hatch explícito.
ORM-13db:seed popula dados via seeders/factoriesIntegral no escopo delimitadoO CLI encaminha ao binário da aplicação e Orm::seed executa seeders registrados em ordem, propagando erros. Registro, idempotência e dataset pertencem à aplicação.
ORM-14Query logging permite inspecionar SQLIntegral no escopo delimitadoToggle global imprime SQL gerado e contagem de parâmetros, sem valores de bindings. Subscriber estruturado, sampling e export não estão implícitos.
ORM-15#[orm(hidden)] omite automaticamente campos de toda serializaçãoParcialO to_json gerado exclui campos hidden e testes cobrem o contrato. #[derive(Serialize)] separado continua vendo o struct original e precisa de sua própria política Serde.
ORM-16Edge/read replicas dividem SELECT local e write primary transparentemente, inclusive Turso com 1 msParcial; latência absoluta não recomendadainit_with_replicas publica estado atomicamente, reads gerados usam round-robin e writes usam primary. Não há health/failover/consistência configurável nem sincronização Turso transparente; 1 ms depende da topologia.
ORM-17Chunking/cursors processam milhões sem carregar tudoIntegral no escopo keyset delimitado.chunk_by_id(size, ...) e chunk_by_id_with_tx percorrem o id: i32 ascendente com id > cursor, recusam zero e propagam erro do callback. A regressão apaga cada linha já processada e ainda visita todos os IDs sem salto. .chunk(...) preserva compatibilidade por offset; não se promete cursor do servidor, snapshot cross-shard nem término sob inserção concorrente ilimitada.
ORM-18Queries expõem futures::StreamIntegral no escopo delimitadostream/stream_with_tx usam fetch do SQLx e aplicam decriptação/hook por linha. O consumidor ainda define backpressure, timeout total e cancelamento.
ORM-19.remember(seconds) fornece cache Redis integradoParcialA feature Redis usa chave SHA-256 versionada com namespace validado de aplicação, tenant opaco, tabela, SQL e bindings tipados; TTL zero é recusado e transações explícitas/task-scoped sempre ignoram cache. Save/delete gerado invalida após commit as chaves do tenant ativo/tabela com scan limitado; rollback as preserva. Configuração ausente falha fechada, enquanto falha de comando ou JSON corrompido usa o banco autoritativo. Gate live pinado cobre hit, TTL, recovery, bypass e invalidação. Raw/bulk writes, Redis cluster/failover e coerência distribuída continuam fora.
ORM-20Hooks reativos/webhooks/cache só disparam depois de commit confirmadoParcial; limites process-local e outbox explícito implementadosafter_commit, observer committed, invalidação/pub-sub Redis e Scout usam o commit de save/delete gerado direto ou Orm::transaction; rollback descarta callbacks, todos são tentados e PostCommit distingue falha posterior à persistência. O novo Outbox opt-in grava evento idempotente com a mutação e oferece claim com lease/token, retry e dead-letter em SQLite/PostgreSQL/MySQL/MariaDB. Ele não converte hooks automaticamente, não fornece dispatcher/webhook, autorização tenant, cleanup ou exactly-once; consumidores continuam idempotentes. Transação SQLx crua precisa usar enqueue_with_tx.
ORM-21Todos os findings críticos/médios de auditoria antiga foram resolvidosParcial; absoluto não verificávelGates atuais e esta campanha corrigem findings reproduzíveis, mas não há artefato imutável completo da auditoria Jules/Antigravity que sustente “todos” para o código atual.
ORM-22Criterion prova overhead desprezível contra Diesel/SeaORMIntegral como harness; conclusão histórica refutada/não recomendadaorm_comparison.rs fixa Diesel/SeaORM, usa drivers SQLite tipados, uma conexão por ORM, arquivos separados e schema/índice/100 linhas/política SQLite/operações equivalentes para find, filtro, count, list-ten e insert/delete; CI publica todos no mesmo runner/commit. O primeiro smoke local mostrou Diesel à frente nos cinco casos e Rullst competitivo com SeaORM nas leituras, portanto a alegação “desprezível” é contradita em vez de promovida. Isso não mede rede, concorrência, memória, cauda ou aplicações completas.
ORM-23OpenTelemetry cobre queries, transactions e pool checkoutIntegral no contrato de telemetria delimitadoQueries geradas/raw e streams emitem spans rullst.orm.query com modelo/tabela/operação estáticos, sem SQL, bindings, valores, DSN ou erros; transações registram begin e outcomes delimitados; todo pool criado por Orm::init*, inclusive réplicas, emite timing de checkout. O layer OpenTelemetry opt-in de Core exporta esses sinais quando inicializado. Subscriber, sampling, segurança/retenção do collector, pools SQLx criados pela aplicação e logs SQLx configurados separadamente continuam pertencendo ao host.
ORM-24Orm::raw(...).map_to<T>() preserva type safetyParcialO mapper exige FromRow<T> e valores passam por .bind, mas texto/colunas/tipos do SQL são validados somente em runtime. É escape hatch caller-owned.
ORM-25Multi-tenancy injeta tenant e exige unscoped() para acesso globalIntegral no escopo relacional delimitadoModelos tenant_column agora falham fechados sem with_tenant, validam campo/tipo em macro expansion, escopam queries e protegem save/partial/delete/restore/force-delete contra instância cross-tenant. unscoped() é explícito; autenticação/autorização para usá-lo pertence ao host.
ORM-26Auto migration aditiva lê structs e garante segurançaParcialmake:migration:auto compara AST e SQLite, gerando tabelas/colunas. Tipos são simplificados, Turso/Postgres/MySQL não têm esse diff e garantia universal de segurança não existe.
ORM-27Auto migration destrutiva sincroniza tudo sob --allow-destructive/safe defaultParcialColunas/tabelas removidas aparecem apenas como SQL comentado no diff SQLite. Não há flag de execução, plano tipado completo, backup ou prova de rollback.
ORM-28Prevent-lazy-loading elimina completamente N+1Parcial; absoluto não recomendadoMétodos de relação gerados retornam erro quando o toggle está ativo; eager loading em lote cobre relações suportadas. Código SQL/raw/repository da aplicação ainda pode gerar N+1.
ORM-29Dirty checking automático atualiza só campos alterados sem overheadParcialupdate_partial() é builder typed explícito, atualiza apenas setters escolhidos e agora não contorna policy/tenant. Ele não detecta automaticamente mutações anteriores nem possui prova zero-overhead.
ORM-30PersonalData + SecretString AES impedem leakage e entregam GDPR/LGPDParcial; compliance automático não recomendadoPersonalData descreve fields, SecretString redige Debug e encrypted model fields usam AES-256-GCM versionado/AAD/keyring. SecretString sozinho não cifra; acesso, retenção, consentimento e demais controles são externos.
ORM-31Audit revisions registram quem/o quê e permitem rollbackIntegral no escopo relacional delimitado#[orm(auditable)] exige AuditContext tipado, deriva o tenant ativo, registra correlação e persiste create/update/delete com payload recursivamente redigido no mesmo savepoint da mutação. Updates v2 bounded expõem restore compensatório com referência/reason, validação exata de model/ID/tenant, lock de linha PostgreSQL/MySQL e recusa de estado stale, segredo, legado, create/delete, malformação ou excesso. Migração v1→v2 e callbacks capturados após falha têm regressões. Bulk por linha, backup/DR, autoridade do principal fornecido pelo host e export externo durável continuam fora.
ORM-32Scout sincroniza Meili/Algolia/Elastic automaticamenteParcial; adapters delimitados implementadosscout-http fornece update/delete/search para Meilisearch, Elasticsearch e Algolia com URLs/recursos limitados, mock determinístico e erros de busca visíveis. Projeções geradas rodam após commit e propagam PostCommit. Meilisearch passa lifecycle real pinado; Elastic/Algolia passam fixtures de protocolo, não contas hospedadas. Sync gerado continua process-local; durabilidade exige Outbox explícito e worker idempotente.
ORM-33RefreshDatabase envolve teste em transação e rollbackIntegral no escopo executor-aware delimitado#[rullst_orm::test] inicializa ORM, abre/scopa transaction e faz rollback após o corpo. SQL que ignora o executor e usa conexão própria continua responsabilidade do teste.
ORM-34Model policies ligam autorização declarativa ao modelParcialCreate/update/delete/restore/force-delete chamam a policy e partial update deixou de contorná-la. Reads, principal, ownership e tenant authorization não são inferidos pela crate.
ORM-35ORM Admin é drop-in e gerencia dadosParcialdashboard_html é apenas uma shell estática. CRUD administrativo autenticado/fail-closed é responsabilidade da crate Nexus, não dessa função.
ORM-36API Resources/Transformers geram JSON limpoIntegral no escopo explícito delimitadoApiResource, JsonResource e ResourceCollection aplicam transforms definidos pela aplicação e possuem testes de coleções/nesting. Datas e relações não são inferidas magicamente.
ORM-37Relações SQL viram travessia distribuída automática via recursive CTEParcialBuilders oferecem CTE/recursive CTE manual. Não existe compilador de friends.of.friends, distribuição cross-node ou mapeamento automático da relação.
ORM-38Qdrant e Redis native data structures são first-classIntegral no contrato especializado delimitadoqdrant oferece coleção dense-cosine, upsert/delete e nearest-neighbor bounded com mock, fixture autenticada e lifecycle live pinado. redis oferece Hash, Set e Sorted Set namespaced, bounded, TLS-required fora do loopback, mock determinístico e lifecycle live com isolamento. Named/sparse/multivectors, filtros Qdrant arbitrários, Redis Lists/Streams, cluster/failover e autorização tenant continuam fora.
ORM-39CLI gera Mermaid ER diagram dos modelsIntegral no escopo estático delimitadogenerate:diagram parseia models e relações e emite diagram.md; não consulta automaticamente constraints live nem schemas polyglot.
ORM-40Soft delete em cascata é recursivo/automáticoParcialRelações has_one/has_many marcadas geram cascade. delete() agora abre transação quando necessário e reutiliza transação explícita/task-scoped; um trigger SQLite força falha no filho e prova rollback do pai. Descendentes recursivos, ciclos/profundidade arbitrária e efeitos externos estritamente post-commit ainda não são tratados.
ORM-41Rust enum mapeia a ENUM nativa de PostgreSQL/MySQL ou constraint SQLiteIntegral no contrato de perfil delimitadoderive(Enum) valida uma lista fechada de até 64 labels portáveis e gera metadata, string/parse, Serde, RullstValue e codecs SQLx coerentes. native_enum cria e compara exatamente um tipo nomeado no PostgreSQL com strict-postgres, emite ENUM inline no MySQL/MariaDB e TEXT CHECK no SQLite; matrizes live cobrem encode/decode, valor inválido e drift. PostgreSQL por SQLx Any falha antes do DDL porque o driver não decodifica custom types. Evolução/reordenação de variantes, ordem de deployment, remoção de dependências/tipo e rollback continuam migrations explícitas.
ORM-42Database-first introspection gera models de bancos legadosIntegral no escopo delimitadoCLI consulta metadata de SQLite/PostgreSQL/MySQL (MariaDB pelo protocolo), valida identifiers/collisions antes de escrever e gera structs. Relações, índices, defaults e tipos proprietários não têm paridade completa.
ORM-43pgvector/RAG tem where_similar/ordenação nativosIntegral no contrato pgvector delimitadopgvector + strict-postgres expõe Vector tipado e helpers L2/cosseno/inner-product com vetor/distância bindados; a ordem dos bindings independe da ordem de chamadas do builder. A matriz live pinada instala a extensão, insere valores tipados e executa filtros/ordenação. Orchestration RAG, tenant/application authorization, lifecycle produtivo de índice e tuning ANN continuam explícitos.
ORM-44ORM Sail escreve e inicia ambiente Docker zero-setupParcialsail:install escreve Compose para Postgres/Redis/Meilisearch/pgAdmin e testa o artefato. Não inicia Docker, não inclui o app e ainda exige configuração/pré-requisitos locais.
ORM-45Turso recebe suporte nativo completoIntegral no contrato Turso-primary delimitadoTransporte direto pelo protocolo oficial Hrana HTTP v3, SQL parametrizado, batch transacional atômico, migrations checksummed e derive typed CRUD/filter/order/paginate passam em fallback SQL real e servidor libSQL. Relações/hooks/auto-diff/seeds, replica sync e paridade com todo blueprint SQLx continuam explicitamente fora.

O inventário deduplicado está agora 190/190 (100%) classificado, mas o resultado honesto não é “190 funcionalidades prontas”. Neste lote ORM há 28 contratos/evidências integrais delimitados e 17 fundações parciais: 62,2% estrito ou 81,1% ponderado atribuindo metade do peso aos parciais. O benchmark comparativo conta como evidência entregue, não como confirmação da antiga conclusão de performance, que seus primeiros resultados contradizem. O hardening associado fechou o bypass tenant/policy em mutações, operadores de join injetáveis, inputs inválidos de paginação/chunk/vector e o tracking de migration anterior a uma falha posterior. O keyset chunking também impede offset drift quando o callback remove linhas já processadas. Os gates focados e o cache .remember deixou de atravessar a fronteira transacional, ganhou isolamento application/tenant sem expor o tenant bruto e invalidação por tabela após commit, preservada em rollback. O contrato after_commit também separa falha do efeito posterior à persistência. O Outbox relacional opt-in agora acrescenta durabilidade at-least-once verificável sem fingir que callbacks arbitrários podem ser serializados automaticamente ou entregues exactly-once. Os gates focados e o trifecta final continuam sendo registrados como evidência do worktree, nunca como certificação de uma aplicação ou deployment.

Compatibility, MSRV, deprecation, and support policy

This policy applies to the 16 publishable Rullst packages. They are released as one synchronized release train even when a user depends on only one crate. The current supported-version table in SECURITY.md remains authoritative for releases that are actually available; a version in the workspace is not a supported release merely because its manifest exists.

Semantic Versioning contract

Rullst follows Cargo Semantic Versioning for stable releases:

  • Patch (x.y.Z) releases contain compatible bug, security, documentation, and dependency fixes. They do not intentionally remove public APIs, public Cargo features, CLI commands, or accepted configuration fields.
  • Minor (x.Y.z) releases may add compatible APIs, opt-in features, diagnostics, and deprecations. Changes to defaults or generated projects must include migration notes and compatibility tests.
  • Major (X.y.z) releases may contain breaking changes. Each known break must be listed in the changelog and migration guide.
  • Prereleases such as 12.0.0-rc.1 are public evaluation artifacts. Their APIs may still change in a later RC, and users must opt in explicitly.

The compatibility surface includes documented public Rust APIs, public Cargo feature names, CLI command and flag identifiers, supported configuration keys, serialized public data contracts, and generator output relied on by the packaged-distribution tests. Undocumented internals, exact dashboard HTML/CSS, test fixtures, deterministic mocks, and third-party provider behavior are not stable interfaces.

Minimum Supported Rust Version

The v12 release line declares Rust 1.96.0 in every publishable manifest.

  • Patch releases do not raise the MSRV.
  • A minor release may raise it only with an explicit changelog entry, updated manifests and documentation, and a green MSRV CI job for the release commit.
  • A major release may select a new baseline, which must be announced in its migration guide.

The MSRV promise covers the supported feature boundaries exercised by CI. A nightly-only analysis tool or an experimental target does not change the crate MSRV and must be labelled separately.

Deprecation and removal

A stable public API scheduled for removal is marked with #[deprecated] and kept for at least one released minor version before removal in the next major release. Documentation must name the replacement when one exists.

An API that is unsound, enables a security bypass, or cannot be made safe may be disabled or removed sooner. Such an exception requires a security advisory or changelog entry describing impact, affected versions, and the supported migration path. Compatibility never overrides safety or fail-closed behavior.

Removing a public Cargo feature is a major change. Adding an opt-in feature is normally minor-compatible. Changing default features is treated as an operationally significant minor change and requires package and generated-app regression tests.

Supported release window

Rullst does not currently promise an LTS or multi-minor backport program. Routine fixes target the latest patch of the latest supported stable minor. The exact versions receiving security triage are listed in SECURITY.md; older versions are end-of-life unless that table explicitly says otherwise.

Release candidates receive fixes through a subsequent RC number rather than by overwriting a published artifact. Security reports follow the coordinated disclosure and response targets in SECURITY.md and the advisory-exception policy.

Release evidence

Before publishing a stable or prerelease version, the release commit must prove the applicable compatibility contract with:

  1. synchronized package versions and internal requirements;
  2. the MSRV and multi-OS CI jobs;
  3. full tests, strict Clippy, formatting, docs, and feature-boundary checks;
  4. packaged crates and generated applications tested without monorepo paths;
  5. a changelog and migration notes for every intentional behavior change.

Passing these gates is evidence for the exact commit and declared matrix, not a guarantee about untested platforms, external providers, or downstream application code.

Cargo feature matrix

Important

Dependency examples use 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use path dependencies from this source checkout during development.

This page is the public feature contract for the 16 packages in the Rullst release train. The package manifests remain the machine-readable source of truth. The matrix explains the behavior those names select in v12 and makes the default build visible before an application adopts optional integrations.

Cargo features are additive across a dependency graph. An application can disable a package’s defaults at the dependency edge, but it cannot disable a feature enabled by another dependency. Inspect the final selection with:

cargo tree -e features

The release gates compile every package with no default features, every public umbrella feature in isolation, representative domain-package boundaries, and the complete workspace with all features. The isolated umbrella list is checked automatically against rullst/Cargo.toml, so a newly added public feature cannot silently escape the matrix. See check-feature-boundaries.sh for the exact individual checks.

Umbrella crate: rullst

The default rullst dependency enables orm and queue-sqlite. Applications that only need the HTTP runtime can opt out:

[dependencies]
rullst = { version = "12.0.0-rc.1", default-features = false }
FeatureDefaultEnables
ormyesrullst-orm and Core’s ORM integration
orm-mongodbnoorm plus the MongoDB document adapter
orm-duckdbnoorm plus the in-process DuckDB analytics adapter
orm-tursonoorm plus typed Turso-primary CRUD/query, parameterized remote libSQL SQL over Hrana HTTP v3, transactions, reversible checked migrations, and a persistent offline fallback
orm-surrealdbnoorm plus SurrealDB HTTP document and bounded graph adapters
orm-scoutnoorm plus bounded Meilisearch, Elasticsearch and Algolia Scout HTTP adapters
orm-pgvectornoorm plus typed pgvector SQLx values; use with strict-postgres for the supported live query contract
orm-qdrantnoorm plus bounded dense-vector Qdrant HTTP operations and offline fallback
orm-redisnoorm plus namespaced Redis Hash, Set and Sorted Set operations
orm-polyglotnoConvenience feature enabling MongoDB, DuckDB, Turso, SurrealDB and Qdrant adapters
queue-sqliteyesCore’s durable SQLite queue backend
nexusnoThe generated Nexus administration interface
studionoStudio plus Core’s Studio integration marker
authnoAuthentication, sessions, passkeys, and RBAC helpers from rullst-auth
auth-jwtnoauth plus the strict application-issued JWT policy
auth-sqlitenoauth-jwt plus bounded shared SQLite JWT revocation and passkey device lifecycle state
mailnorullst-mail with HTTP/offline transports and no SMTP dependency
mail-sqlitenomail plus bounded shared-local SQLite recipient suppression and provider-event replay evidence
mail-smtpnomail plus the optional SMTP transport
mail-aws-sesnomail plus native SES v2 delivery signed by the official AWS SDK
messagingnoNative bounded broker-neutral messaging contracts and the deterministic process-local broker
messaging-sqlitenomessaging plus fixed-schema durable local SQLite publication, lease, retry/DLQ, ACK and idempotency state
messaging-orm-outboxnomessaging and orm plus the static relational outbox-to-broker relay; the publish/ACK crash window remains at-least-once
mailernoCompatibility alias for mail-smtp; prefer mail-smtp in new manifests
queue-redisnoRedis dependency and Core’s Redis queue backend
cache-redisnoRedis dependency and Core’s Redis cache backend
redisnoConvenience alias enabling queue-redis, cache-redis and orm-redis
offline-syncnoNative bounded offline queue, explicit conflict state machine, account-bound encrypted snapshots, and static-dispatch push/pull orchestration; platform storage and concrete transport remain application responsibilities
oauthnoOAuth2/OIDC providers from rullst-connect
oauth-sqlitenooauth plus bounded encrypted shared-local token-generation state with exact SQLite compare-and-swap
ainoProvider-agnostic AI clients and local safeguards from rullst-ai
ai-sql-memorynoai plus tenant-aware durable chat memory for SQLite, PostgreSQL, MySQL, and MariaDB
capitalnoPayment, payout, analytics, DPS builder, and offline fiscal APIs from rullst-capital
capital-actixnocapital plus the Actix Web adapter for the canonical signed-webhook verifier
capital-quota-sqlnocapital and orm plus atomic shared resource quotas for SQLite, PostgreSQL, MySQL, and MariaDB
capital-webhook-sqlnocapital and orm plus bounded durable webhook replay/event claims for SQLite, PostgreSQL, MySQL, and MariaDB
capital-nfsenocapital plus checksum-pinned official XSD validation, PKCS#12 XMLDSig, signed-environment protocol binding, authenticated local command journal, and rustls mTLS preparation
capital-pdfnocapital plus bounded validated native invoice PDF rendering
capital-mailnocapital-pdf plus Mail’s payment-bound HTML/PDF attachment delivery bridge
securitynoRASP/WAF and application-security primitives from rullst-security
security-redisnosecurity plus the atomic Redis rate limiter
iotnoIoT models, frame helpers, and signed OTA verification from rullst-iot
telemetrynoOpenTelemetry dependencies and Core’s OTLP integration
strict-postgresnoorm with the concrete PostgreSQL pool/backend selected
strict-mysqlnoorm with the concrete MySQL pool/backend selected when PostgreSQL is not also selected
strict-sqlitenoorm with the concrete SQLite pool/backend selected when PostgreSQL and MySQL are not also selected

The three strict-* backend features are supported as single selections. Feature unification can activate more than one; the current deterministic precedence is PostgreSQL, then MySQL, then SQLite. Do not depend on that precedence as backend negotiation. Select one strict backend in an application, or select none to use SQLx Any.

Shared-local SQLite composition profile

A bounded single-host application may compose the following umbrella features over one file-backed SQLite URL:

[dependencies]
rullst = { version = "12.0.0-rc.1", default-features = false, features = [
  "auth-sqlite",
  "capital-quota-sql",
  "mail-sqlite",
  "messaging-sqlite",
  "oauth-sqlite",
  "queue-sqlite",
] }

The stores use distinct fixed table namespaces. Initialize/check them sequentially, keep application readiness false until every required component is healthy, and reuse exactly the same URL, quotas, namespaces, and keys after restart. Close every handle before file-level backup or restore. The dedicated facade_recovery test proves restart, idempotency, encrypted-secret plaintext absence, queue recovery, aggregate readiness, and isolated fail-closed corruption for this exact profile.

Sharing the file is not a cross-domain transaction or a multi-host design. The host still owns permissions, encryption-key custody, a consistent whole-file backup procedure, recovery drills, contention policy, and domain authorization. Prefer separate databases when failure isolation or write throughput is more important than simple local operation.

Runtime and data crates

rullst-core

Default features: none.

FeatureEnables
ormOptional rullst-orm and SQLx support, including Artisan and database-backed feature flags
queue-sqliteSQLx-backed durable SQLite queues without enabling the full ORM facade
queue-redisRedis-backed queues
cache-redisRedis-backed cache storage
redisConvenience alias for both Redis queue and cache backends
offline-syncNative bounded offline state, AES-256-GCM snapshots, and timeout/budget/cursor-checked transport orchestration; excludes platform storage and a concrete authenticated transport
studioIntegration marker used by the umbrella Studio boundary; it adds no dependency by itself
telemetryOpenTelemetry tracing and OTLP export dependencies
strict-postgresorm plus the ORM PostgreSQL backend selection
strict-mysqlorm plus the ORM MySQL backend selection
strict-sqliteorm plus the ORM SQLite backend selection

Core’s process-local Radar and span collector do not require telemetry. That feature is specifically for OpenTelemetry/OTLP integration. The rullst.client v1 codec and bounded #[server_function] transport are available without a feature flag; only the explicit generated route exists on native targets, while the same annotated function becomes its Wasm caller. Identity, authorization and tenant policy remain application layers.

rullst-orm

Default features: none. With no strict-* feature, public pool and database aliases use SQLx Any.

FeatureEnables
redisRedis query cache plus bounded namespaced Hash, Set and Sorted Set datastore operations
mongodbOfficial MongoDB driver plus typed document CRUD, identifier inventory, encrypted recovery participation and offline fallback
duckdbBundled DuckDB client plus parameterized, bounded analytics queries
tursoDirect official Hrana HTTP v3 transport, typed primary CRUD/query facade, parameterized SQL, atomic batches, reversible checksummed migrations, and a persistent SQLite-compatible offline fallback
surrealdbSurrealDB HTTP document CRUD, identifier inventory, encrypted recovery participation and bounded read-only ISO GQL; no embedded SDK
scout-httpBounded Meilisearch, Elasticsearch and Algolia adapters with deterministic offline fallbacks; Meilisearch also has a live container contract
pgvectorTyped pgvector SQLx values and parameterized L2/cosine/inner-product helpers; the live contract also selects strict-postgres
qdrantBounded dense-vector collection/upsert/delete/cosine query operations over HTTP with offline fallback
polyglotConvenience feature enabling mongodb, duckdb, turso, surrealdb, and qdrant
strict-postgresConcrete PostgreSQL pool, database, query-result, and query paths
strict-mysqlConcrete MySQL paths when PostgreSQL is not also selected
strict-sqliteConcrete SQLite paths when PostgreSQL and MySQL are not also selected

The strict backend selection rules and precedence are the same as the umbrella crate. SQLx drivers remain implementation dependencies; strict-* selects concrete public types and query paths rather than acting as a driver download switch.

The Polyglot features expose capability-specific APIs under rullst_orm::polyglot; they do not participate in a shared cross-backend transaction. The base deterministic document store and the MongoDB/SurrealDB adapters implement DocumentInventory for an application-operated bounded snapshot/restore contract; it does not supply online isolation or managed backup. Turso can additionally be selected explicitly by #[orm(backend = "turso")] and the blank/API scaffold. See the Polyglot Persistence guide.

rullst-orm-macros

Default features: none. #[derive(Orm)] uses a fail-closed structured parser: unknown/duplicate options, missing persisted targets, conflicting relations, unsafe identifiers, and SQLx mappings that generated persistence cannot honor are compile errors. The exact derive grammar and its raw soft-delete-expression boundary are defined in the packaged crate README and the SST.

FeatureEnables
strict-postgresCompatibility marker matching the ORM backend vocabulary; no macro expansion changes in v12
strict-mysqlCompatibility marker matching the ORM backend vocabulary; no macro expansion changes in v12
strict-sqliteCompatibility marker matching the ORM backend vocabulary; no macro expansion changes in v12

rullst-connect

Default features: none. Provider clients and framework-independent OAuth/OIDC types remain available without a web-framework adapter.

FeatureEnables
axumAxum callback extractors and the local mock IdP router
actixActix Web callback extractors
leptosFramework-independent callback extractor module for Leptos integration; no Leptos runtime dependency
rullstConvenience integration boundary that enables axum
retryRetry-aware HTTP client behavior using reqwest-middleware and reqwest-retry
reqwest-middlewareThe optional middleware dependency alone; prefer retry for retry behavior
axum-sessionAxum plus a ten-minute, one-active-challenge tower-sessions state/PKCE/OIDC-nonce transaction and callback extractor
sqliteFile-backed shared-local encrypted token snapshots with persisted quota, restart recovery and exact generation compare-and-swap; remote refresh leases, key custody and multi-host operation remain application concerns
mockDeterministic offline provider modules outside test builds

rullst-messaging

Default features: none. The deterministic process-local broker, versioned envelope, idempotency, consumer groups, leases, retry, dead-letter, and purge contracts are available without optional dependencies. Remote broker adapters are not implemented and therefore are not represented by placeholder features.

FeatureEnables
sqliteFixed-schema durable local broker with serialized SQLite writes and immutable plaintext or explicit AES-256-GCM content profiles; restart/corruption/rotation/tamper/two-instance evidence is local, while metadata visibility, key custody and remote replication/failover remain explicit boundaries
orm-outboxStatic bridge from the relational rullst-orm outbox to one configured broker topic, with exact replay after the publish-before-ACK crash window; worker operations and remote atomicity remain application boundaries

rullst-iot

Default feature: std.

FeatureEnables
stdStandard-library support in serialization and Ed25519 dependencies; disabling it makes the crate no_std + alloc
experimental-simulatorsDeterministic MQTT formatting, HSM, and PQC fixtures; not live transports, hardware-backed keys, or production PQC

rullst-capital

Default feature: axum.

FeatureEnables
axumAxum middleware for the canonical bounded signed-webhook verifier
actixActix Web middleware for the same verifier; it does not enable Axum when selected directly
quota-sqlDurable idempotent shared quota accounting over SQLite, PostgreSQL, MySQL, and MariaDB; schema setup/migrations and authoritative membership/tier state remain application-owned
webhook-sqlBounded durable provider-scoped payload/event claims over SQLite, PostgreSQL, MySQL, and MariaDB, including a caller-owned transaction path; cross-system effects and reconciliation remain application-owned
nfseChecksum-pinned official XSD validation, PKCS#12 RSA-SHA256 XMLDSig, signed-tpAmb binding, deterministic GZip/Base64 issuance JSON, bounded signed-authorization/rejection parsing, a HMAC-chained single-writer local command journal, and rustls mTLS preparation; it does not enable live SEFIN transmission, provide a distributed outbox/retry engine, or establish certificate trust/homologation
invoice-pdfBounded paginated A4 invoice PDF with embedded WinAnsi or a validated caller-supplied TTF/OTF; payment/mail orchestration is separate

rullst-mail

Default features: none. HTTP mail providers remain available without SMTP.

FeatureEnables
mail-smtpLettre-based SMTP transport
aws-sesOfficial AWS SES v2 SDK, regional SigV4, temporary/rotating credential providers and native attachments/CID; AWS account readiness and inbox delivery remain external
capital-invoiceCapital’s native invoice PDF plus the final-payment-bound delivery bridge; durable outbox claiming remains application-owned
sqliteFile-backed shared-local suppression state with exact provider-event replay binding and immutable quotas; webhook authentication, encryption and multi-host replication remain application-owned

rullst-auth

Default features: none.

FeatureEnables
oauthOptional rullst-connect OAuth2/OIDC integration and re-exports
jwtApplication-issued JWT claims, key rotation, and revocation-store policy
sqlitejwt plus bounded file-backed shared JWT revocation and passkey device lifecycle state

The umbrella crate exposes these as auth-jwt and auth-sqlite; both enable auth, while auth-sqlite also enables auth-jwt.

rullst-security

Default features: none.

FeatureEnables
redis-rate-limitAtomic namespaced Redis fixed-window limiter plus its explicit offline mock mode; CI/release run the independent-client contract against a digest-pinned Redis service

The umbrella crate exposes this as security-redis, which also enables security.

Dashboard crates

rullst-nexus and rullst-studio both have no default features and expose the same database selection boundary:

CrateFeatureEnables
rullst-nexusstrict-postgresPostgreSQL selection in Core and ORM
rullst-nexusstrict-mysqlMySQL selection in Core and ORM
rullst-nexusstrict-sqliteSQLite selection in Core and ORM
rullst-studiostrict-postgresPostgreSQL selection in Core and ORM
rullst-studiostrict-mysqlMySQL selection in Core and ORM
rullst-studiostrict-sqliteSQLite selection in Core and ORM

Use the same single-selection rule described for rullst-orm.

Packages without optional features

These packages have no public optional Cargo features in v12:

PackageAlways-available scope
rullst-macrosCore procedural macros
rullst-aiProvider clients, prompt inspection, and PII masking
cargo-rullstCLI commands, generators, auditing, and deployment helpers

No optional feature does not mean that a provider is contacted automatically. External integrations still require explicit runtime configuration and use the documented deterministic offline behavior for empty or mock_* credentials.

Selection recipes

Minimal HTTP runtime:

rullst = { version = "12.0.0-rc.1", default-features = false }

SQLite application using the release default:

rullst = "12.0.0-rc.1"

PostgreSQL application with explicit domain integrations:

rullst = {
    version = "12.0.0-rc.1",
    default-features = false,
    features = ["strict-postgres", "auth", "security", "telemetry"]
}

Embedded IoT model without the standard library:

rullst-iot = { version = "12.0.0-rc.1", default-features = false }

Experimental IoT fixtures are deliberately separate:

rullst-iot = {
    version = "12.0.0-rc.1",
    default-features = false,
    features = ["experimental-simulators"]
}

Migrating an application to Rullst v12

Rullst v12 is a coordinated release train of 16 packages. Upgrade all direct rullst-* dependencies together; mixing v12 facade/runtime crates with older domain crates is outside the supported compatibility contract.

Choose the guide matching the application’s source baseline:

Only v5 has a repository release tag among those three baselines. The repository contains a v6 source snapshot, while “v11” principally identifies ecosystem dependencies such as rullst-connect rather than a tagged v11 umbrella release. The guides state these evidence limits instead of inventing release history.

Safe upgrade procedure

  1. Commit or stash the application and record its current Cargo.lock.
  2. Back up every database and prove that the backup can be restored.
  3. Run the old application’s tests and save any known failures.
  4. Install the exact v12 CLI version only after that RC or stable version is published. Do not use an unversioned install in a reproducible migration.
  5. Run cargo rullst upgrade --dry-run from the application root and resolve every BLOCKER; use --dry-run --json when CI or other tooling consumes the versioned plan.
  6. Run cargo rullst upgrade to execute the backed-up transaction.
  7. Review Cargo.toml, Cargo.lock, every compiler-provided edit, and the Markdown/JSON reports under target/rullst-upgrades/.
  8. Apply the baseline-specific manual changes below.
  9. Run migrations against a disposable copy of production-shaped data.
  10. Execute the application’s tests, authorization negatives, and deployment smoke tests before merging.

The v12 upgrade command has deliberately bounded behavior:

  • it discovers exact Cargo workspace members and updates standard, inline, workspace, target-specific and renamed versioned Rullst dependencies while preserving TOML comments and relative order;
  • it leaves unversioned path/git dependencies untouched and reports them;
  • it never rewrites valid Axum, SQLx, or Tokio imports;
  • it selects source checks from a versioned migration-rule catalog and can emit the versioned rullst.upgrade-plan.v1 JSON envelope;
  • it snapshots manifests, the root lockfile and Rust sources before applying compiler-provided cargo fix, then runs cargo check for the workspace’s selected features;
  • it restores the snapshot when a gate fails unless --keep-on-failure was explicitly selected; an interrupted run can be recovered with cargo rullst upgrade --restore <backup-directory>;
  • it returns failure when any gate fails and never reports “100% stable” or production readiness.

It does not install a new CLI globally, change application secrets, run database migrations, prove runtime behavior, or replace the full test suite.

See the complete assisted upgrade tutorial for the v5 workflow, recovery examples, JSON contract and future-major policy.

Version placeholder

The snippets in these guides use 12.0.0-rc.1, the planned first public RC. Use it only after publication and replace it with the exact v12 version being evaluated. A prerelease must be requested explicitly by Cargo.

Mandatory v12 review

Every baseline must review these contracts:

  • Cargo feature defaults and aliases;
  • compatibility, MSRV, and support policy;
  • explicit Nexus access policy and debug-only loopback convenience;
  • explicit Nexus tenant metadata for tenant-owned admin models, with a trusted TenantContext, plus the optional required audit schema/policy when mutation evidence is needed;
  • debug-only, loopback-bound Studio deployment;
  • ownership checks on parameterized data routes;
  • exact CSRF webhook exemptions, WAF/body limits, and trusted proxy identity;
  • deterministic offline provider credentials versus live provider validation;
  • Capital subscription calls: Billable::extend_trial(15) is a relative 15-day operation in v12; use set_trial_end(unix_timestamp) for an absolute provider timestamp and persist a command clock with extend_trial_days_at when a worker may retry;
  • the AI provider capability matrix.

Do not deploy v12 solely because cargo check succeeds. Compilation does not validate data conversion, authorization, provider credentials, reverse-proxy trust, rollback, or restore behavior.

Migration guide: v5 to v12

Baseline: the repository tag v5.0.0 and the compatible v5 line. This is a multi-major migration. Use a branch and follow the common safe upgrade procedure before applying these changes. The assisted upgrade tutorial shows the complete CLI transaction and recovery flow.

1. Replace the dependency graph

The v5 facade had no default features, kept several modules inside rullst, depended on rullst-orm = 6.1.1, and used rullst-connect = 11. V12 releases all framework packages in one version train and enables orm plus queue-sqlite by default.

To preserve the old opt-in behavior, start explicitly:

[dependencies]
rullst = {
    version = "12.0.0-rc.1",
    default-features = false,
    features = ["orm", "queue-sqlite"]
}

Add only the domain features the application actually uses. Consult the feature matrix; do not copy --all-features into a production manifest. Remove independently pinned old Rullst package versions or move every direct package to the same v12 version.

Important feature changes include:

  • auth, ai, capital, nexus, studio, security, and iot now select real optional crates rather than empty facade markers;
  • mail selects HTTP/offline transports without SMTP; mailer remains a compatibility alias for mail-smtp when Lettre SMTP is required;
  • redis selects both queue and cache Redis boundaries;
  • strict-postgres, strict-mysql, and strict-sqlite select concrete ORM database types. Select at most one in an application.

2. Move from attribute-style routing

The v5 README demonstrated attribute-style #[routes] registration. V12’s supported registration surface is the explicit routes! macro. The deprecated #[route] compatibility marker does not register a route.

use rullst::{Server, response::Html, routes};

async fn home() -> Html<&'static str> {
    Html("Hello from v12")
}

#[rullst::runtime::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = routes![get("/" => home)];
    Server::new(app).run(3000).await?;
    Ok(())
}

Direct Axum escape hatches remain supported through rullst::web::axum or a normal direct dependency. The upgrade command does not rewrite those imports; its versioned scanner reports the known v5 routing/server markers for manual, semantic conversion.

3. Review ORM and migrations

  • Replace the old independently pinned ORM with v12.
  • Decide between SQLx Any and one concrete strict-* backend.
  • Run schema migrations on a disposable restored database first.
  • Recheck raw SQL parameterization, tenant predicates, ownership guards, query limits, pool timeouts, and rollback behavior.
  • Never assume that a successful compile proves an old migration is reversible.

4. Rebuild administrative and security boundaries

Nexus no longer mounts as an implicitly open admin router. New code should use Nexus::try_build() with either validated production authentication or LocalNexusAccess::loopback_only() in a debug build. Basic Auth requires a verified TLS boundary in production.

Tenant-owned Nexus models should add an explicit text tenant column to their derive metadata and install TenantContext only after authenticated membership resolution. Models without this metadata remain global administrator models. If with_required_audit() is enabled, run create_nexus_audit_table() as a deployment migration first; a missing table intentionally rolls mutations back.

Studio must remain a separate debug-only loopback service. Rebuild the server middleware order using the v12 production baseline, then add application authorization and the extended rullst-security layers explicitly where used. Re-test CSRF, CORS, webhook signatures, request limits, IDOR, and cross-tenant denials.

5. Validate provider migrations

Empty or mock_* credentials intentionally use deterministic offline behavior. They are not proof that mail, OAuth, billing, AI, or storage providers work live. Validate each configured provider in a non-production account and keep unsupported fiscal, transport, and hardware capabilities fail-closed.

Completion gate

Run at minimum:

cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features

Also test the application’s actual feature selection without --all-features, database restore/migration/rollback, Nexus and Studio network exposure, and a production-profile smoke build.

Migration guide: v6 to v12

Baseline: repository source commit 5229132f, which bumped the facade to 6.0.0. There is no repository v6 tag, so first verify the exact crate versions in the application’s Cargo.lock; do not assume this snapshot matches every artifact an application may have consumed.

Follow the common safe upgrade procedure.

1. Normalize the mixed-version ecosystem

The v6 snapshot combined rullst = 6.0.0, rullst-orm = 6.1.1, rullst-connect = 11.0.0, path-based internal crates, and unversioned internal path requirements. V12 uses one synchronized version for all 16 publishable packages.

For the old no-default behavior:

[dependencies]
rullst = {
    version = "12.0.0-rc.1",
    default-features = false,
    features = ["orm", "queue-sqlite"]
}

The explicit features above opt into v12’s local database behavior. Omit them for a database-free HTTP service. If the application relies on v12 defaults, rullst = "12.0.0-rc.1" enables both automatically.

Remove obsolete direct lettre wiring from the facade migration and use mail-smtp. Review the new security, iot, redis, and strict-* boundaries in the feature matrix. Path-only dependencies are not changed by cargo rullst upgrade.

2. Preserve valid ecosystem escape hatches

V6 applications often imported Axum, SQLx, and Tokio directly. Those imports remain valid application choices. V12 exposes convenience paths, but migration does not require global substitutions such as axum:: to rullst::server::. Review imports using compiler errors and API intent, not blind text replacement.

Prefer explicit routes! registration for the central router and propagate startup errors from Server::run. Deprecated route attributes must not be treated as functional registration.

3. Revalidate modular crate boundaries

V6 had already started splitting Core, Auth, Mail, AI, Nexus, Capital, and Studio. V12 makes those package and feature relationships explicit and adds the independently versioned ORM macros, Connect, Security, and IoT packages to the same release train.

  • Update direct imports only when the corresponding feature is enabled.
  • Remove code that depended on empty facade features compiling without their domain crate.
  • Use the umbrella mail feature for HTTP/offline transports. Add mail-smtp only when the SMTP dependency is required.
  • For OpenTelemetry export, enable telemetry; process-local Radar data does not require it.

4. Apply v12 security changes

  • Build Nexus with an explicit access policy and try_build().
  • Keep Studio debug-only and loopback-bound.
  • Derive tenant identity from authenticated membership, never directly from a selector header, subdomain, or URL parameter.
  • Recheck exact CSRF exemptions, trusted proxy/TLS metadata, webhook replay protection, body limits, and parameterized-route ownership.
  • Treat local security telemetry events as unsigned unless a configured audit verifier proves HMAC integrity.

5. Revalidate data and providers

Choose one strict database backend or SQLx Any, run migrations on restored data, and exercise real SQLite/PostgreSQL/MySQL behavior used by the application. Then validate live OAuth, mail, billing, and AI credentials separately from their deterministic offline modes.

Finish with the full commands in the common guide, the application’s exact feature build, a production-profile smoke test, and a documented rollback rehearsal.

Migration guide: v11-era dependencies to v12

There is no repository tag for a rullst umbrella v11 release. The v5/v6 source line did, however, consume ecosystem packages such as rullst-connect = 11. This guide therefore targets applications whose manifests or lockfiles contain v11 Rullst ecosystem crates, and any untagged development snapshot described as “v11”. Record the exact starting commit and dependency graph before proceeding.

Follow the common safe upgrade procedure.

1. Inventory before editing

cargo tree -e features
cargo metadata --format-version 1

Save the output and list every direct or transitive rullst-* package. In v12, all 16 published packages must use the same release version. Do not update only rullst-connect or only the umbrella crate.

Example explicit selection:

[dependencies]
rullst = {
    version = "12.0.0-rc.1",
    default-features = false,
    features = ["orm", "queue-sqlite", "auth", "oauth", "security"]
}

If the project uses renamed dependencies or local paths, update those entries manually; the upgrade command intentionally changes only standard versioned Rullst dependency keys.

2. Connect and authentication

V12 rullst-connect keeps provider-neutral OAuth/OIDC types available without a web adapter. Select the adapter explicitly:

  • axum for Axum extractors and the local mock IdP router;
  • actix for Actix extractors;
  • axum-session for Axum plus tower-sessions integration;
  • retry only when the Connect retry client is intended;
  • mock only for deterministic offline provider modules.

Empty credentials choose explicit offline behavior. Custom-provider configuration errors and live callback/state validation remain fallible. Re-test PKCE, state, redirect URL allowlists, session binding, logout, refresh, and unknown-key JWKS refresh behavior.

rullst-auth enables Connect integration through its oauth feature, while the umbrella rullst facade exposes Connect through its own oauth feature. Review which layer the application imports instead of enabling both reflexively.

3. Adopt the v12 runtime contracts

  • Use explicit routes! registration; compatibility route attributes do not perform runtime registration.
  • Propagate startup, pool, migration, provider, and server errors.
  • Review the changed default features (orm and queue-sqlite).
  • Select at most one strict-* database backend.
  • Build Nexus with an explicit access policy and keep Studio on debug loopback.
  • Mark tenant-owned Nexus models with an explicit text tenant column and supply a membership-derived TenantContext; install the audit table before enabling with_required_audit().
  • Re-run IDOR, tenant-isolation, CSRF/CORS, webhook, and trusted-proxy negative tests.

4. Review AI and tool assumptions

V12 has a machine-readable provider capability matrix. Built-in live transports have configurable request deadlines. The separate StreamingAiClient enforces bounded output and explicit cancellation, and an exact OpenAI-compatible configuration may opt into strict incremental SSE. Provider-native tool calling, automatic provider retries, ordinary non-streaming cancellation and streaming for other provider protocols are not uniform transport capabilities. ToolRegistry is guarded local dispatch infrastructure, not an authorized autonomous-agent boundary.

Completion gate

Compile both the application’s exact feature set and the intended release profile. Run the full local trifecta, provider integration tests in non-production accounts, database restore/migration/rollback, and network-exposure checks. Keep the previous artifact and database restore procedure available until the v12 deployment has passed its observation window.

AI provider capability matrix

This matrix describes the transport paths implemented by rullst-ai v12. It is not a claim that every model sold by a provider accepts every request. Model availability, account entitlements, regions, quotas, and upstream API behavior remain provider concerns.

Applications can inspect the same contract in code through AiProvider::capabilities() or AiClient::capabilities(). Built-in provider tests assert every row so unsupported paths remain explicit instead of silently falling back to another operation.

Provider transportTextChatEmbeddingsVisionJSONJSON SchemaStreamingProvider toolsRullst timeoutAutomatic retryExplicit cancellation
OpenAIyesyesyesyesnative modeyesnonoyesnono
Anthropicyesyesnoyesprompt onlynononoyesnono
Geminiyesyesyesyesnative modeyesnonoyesnono
DeepSeekyesyesnononative modedefault model onlynonoyesnono
Ollamayesyesyesyesnative modeyesnonoyesnono
OpenAI-compatibleyesyesdeclareddeclareddeclared native modedeclareddeclared SSEnoyesnodeclared for SSE

yes means Rullst constructs and parses that provider request. A configured model can still reject vision, embeddings, or schema output. In particular:

  • DeepSeek JSON Schema is enabled only for the default deepseek-v4-flash transport contract. Selecting another model makes the capability false and returns UnsupportedCapability, including in deterministic offline mode.
  • Ollama vision, embeddings, JSON, and schema support depend on the installed local models. The transport can send those request shapes; it cannot prove that an arbitrary model implements them.
  • Anthropic JSON uses an instruction requesting one JSON value. It is labelled prompt only because the current transport does not request native JSON mode or schema enforcement.
  • Empty and mock_* credentials select deterministic offline behavior. They do not contact the configured endpoint and do not promote capabilities that the transport marks unsupported.
  • The OpenAI-compatible adapter defaults to text/chat only. Its exact endpoint/model configuration must explicitly declare embeddings, vision, native JSON mode, JSON Schema, and SSE streaming. This reports which request shapes Rullst will send; it does not discover or certify model behavior.

Vision input sources

The Vision column describes provider request-shape support, not unrestricted image acquisition. AiClient::prompt_with_image accepts application-admitted bytes. prompt_with_image_file additionally requires an exact canonical LocalImagePolicy root and byte budget. prompt_with_image_url accepts only an explicit EgressFetcher, so HTTPS host allowlisting, DNS pinning, peer and redirect validation, proxy bypass, timeout and streaming limits are mandatory. Both helpers verify bounded JPEG/PNG/WebP/GIF signatures; a supplied remote media type must match the bytes. Capability and prompt checks happen before file or network I/O. Local-directory trust, tenant/owner authorization, image decoding safety and model behavior remain application/provider boundaries.

OpenAI-compatible local and cloud endpoints

OpenAiCompatibleProvider::try_local accepts an unauthenticated OpenAI-shaped base URL only when its host is a literal loopback IP such as 127.0.0.1 or ::1. It may use HTTP for local development and never probes localhost implicitly; requiring an IP literal avoids trusting host-name resolution. try_local_with_bearer supports an explicitly authenticated loopback server. try_cloud requires HTTPS and Bearer authentication; empty and mock_* keys select the offline fixture. All constructors reject URL credentials, query strings, and fragments, disable redirects and environment proxies, cap images at 10 MiB and JSON responses at 2 MiB, and retain the ordinary 30-second configurable request deadline.

This adapter covers /chat/completions, optional /embeddings, OpenAI-shaped image content, the declared response-format modes, and opt-in strict text/event-stream chat deltas. It does not claim Azure query/header conventions, arbitrary authentication schemes, provider-native tools, retries, automatic model discovery, or compatibility with an unrelated HTTP protocol. Implement AiProvider for those explicit semantics. Local runtimes such as llama.cpp server, LocalAI, LM Studio, and vLLM are possible consumers only when their installed configuration exposes these exact shapes; Rullst does not certify a product name or infer capabilities from it.

Operational boundaries

Streaming

StreamingAiClient<P> applies provider-independent output limits through static dispatch. For an exact OpenAI-compatible configuration that declares with_streaming(), Rullst parses incremental UTF-8 SSE deltas, requires the terminal [DONE] marker, rejects an incorrect media type, malformed/truncated events and all configured byte/chunk overflows. The maximums are 4,096 chunks, 64 KiB per chunk and 2 MiB of raw response and delivered text. DeepSeek and Ollama ordinary payloads still select stream: false; other provider-specific streaming protocols remain unimplemented rather than being treated as OpenAI-compatible.

Timeouts and cancellation

Every built-in live transport applies a 30-second request deadline by default. Each provider exposes with_request_timeout(Duration) to select a stricter or longer deadline, and a loopback regression proves timeout classification on the OpenAI-compatible transport. This bounds the local request future; it is not proof that an upstream provider stopped work or billing. The adapters still do not expose cancellation for ordinary AiProvider calls. AiCancellation provides an explicit cloneable signal for StreamingAiClient; the compatible transport races it against both the initial request and every streamed body read. Other provider protocols still use deadline/drop semantics.

Retries

The adapters make one transport attempt. There is no automatic retry, backoff, idempotency classification, or retry budget in rullst-ai. An application-level retry must classify operations carefully and must not assume that an interrupted provider request was never processed.

These provider-call rules are distinct from AuditDeliveryClient. The latter supports one to five bounded attempts only for transport/deadline failures, HTTP 429 and HTTP 5xx, while preserving a caller-supplied event ID. Its receiver must deduplicate that ID because a timed-out request may already have been accepted.

Authenticated audit export

AuditDeliveryClient is an opt-in transport for application-minimized audit events, not an AI provider adapter. It signs the exact JSON body with HMAC-SHA256 plus a key ID and timestamp, caps an event at 16 KiB and an acknowledgement at 8 KiB, disables redirects and ambient proxies, and requires the acknowledgement to bind the original event ID. Cloud endpoints require HTTPS; literal-loopback HTTP(S) is reserved for development fixtures. Empty or mock_* keys never use the network.

The receiver owns signature/freshness verification, event-ID deduplication, authorization, persistence, retention, key distribution/rotation and SIEM operations. The client does not automatically attach itself to RAG/tools or provider calls and does not inspect an arbitrary serialized event for secrets.

Adaptive evaluation

AdaptiveAiEvaluator<P> is also independent of the provider capability table. It wraps the configured text path with mandatory input guards, at most 32 strategy-directed turns, per-turn prompt/response/deadline limits and explicit cancellation. Strategies classify each bounded response or low-cardinality guardrail/provider/deadline outcome as passed, failed, inconclusive or another prompt. Reports are versioned JSON and retain no raw prompt, response or provider error.

The caller supplies the exact suite and provider/model/configuration subject labels. Rullst neither discovers nor attests that identity. Repository tests prove deterministic orchestration and redaction, not a live provider’s safety; operators must execute and review their versioned corpora against every exact model they deploy.

Tools

ToolRegistry is a separate guarded local execution boundary. Dispatch requires an exact policy allowlist, principal authorization, closed JSON validation, payload limits, a call budget and an audit sink. Destructive and financial calls additionally consume a one-use approval bound to the exact payload. Applications may select either the bounded process-local in-memory sink or DurableToolAuditTrail, whose local versioned file validates restart integrity and fails closed on quota, corruption, symlink targets and competing-writer growth. The durable sink is single-process and does not provide authenticity, rotation, retention, backup, external delivery or principal/approver authentication.

The registry is not connected to any built-in provider transport. Consequently the provider tools column remains no, and local guarded execution must not be advertised as provider-native function calling or an autonomous safe agent.

Portable custom-provider default

A custom AiProvider receives a compatibility default of text, chat, embeddings, and prompt-only JSON because those first three methods are required by the trait and JSON has a guarded prompt fallback. Custom implementations that deliberately reject one of those methods, or implement additional native paths, must override capabilities().

FallbackProvider reports the union of its configured providers. The union means that at least one provider claims a path; it does not guarantee which provider will satisfy a particular model-specific request.

Guarded local AI tools

rullst-ai includes a local tool registry, not provider-native function calling or an autonomous agent runtime. Model-produced tool names and arguments are untrusted input. The only execution entry point requires all of these controls:

  • an exact registry-level allowlist (ToolExecutionPolicy);
  • a principal-specific authorization set (ToolExecutionContext);
  • a closed, bounded JSON object validated from ToolParam declarations;
  • non-zero serialized input/output limits and a per-context call budget;
  • a mandatory ToolAuditSink that fails execution closed when unavailable; and
  • a one-use, exact-payload approval for Destructive and Financial tools.

The application authenticates the principal and approver. Rullst does not infer authorization from model output, a prompt, a role string, or tool registration.

Minimal read-only dispatch

#![allow(unused)]
fn main() {
use rullst_ai::ai::{
    AiTool, InMemoryToolAuditTrail, ToolExecutionContext, ToolExecutionPolicy,
    ToolParam, ToolRegistry, ToolRisk,
};
use serde_json::{Value, json};

struct AccountStatus;

impl AiTool for AccountStatus {
    fn name(&self) -> &str { "account_status" }
    fn description(&self) -> &str { "Read an account status" }
    fn parameters(&self) -> Vec<ToolParam> {
        vec![ToolParam {
            name: "account_id".into(),
            param_type: "string".into(),
            description: "Authorized account identifier".into(),
            required: true,
        }]
    }
    fn risk(&self) -> ToolRisk { ToolRisk::ReadOnly }
    fn execute(
        &self,
        payload: Value,
    ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
        Ok(json!({ "account_id": payload["account_id"], "status": "active" }))
    }
}

let mut registry = ToolRegistry::new();
registry.register(AccountStatus)?;

let policy = ToolExecutionPolicy::new(["account_status"])?
    .with_payload_limits(4 * 1024, 16 * 1024)?;
let mut context = ToolExecutionContext::new(
    "authenticated-user-17",
    ["account_status"],
    3,
)?;
let audit = InMemoryToolAuditTrail::new(128)?;

let output = registry.execute(
    "account_status",
    json!({ "account_id": "acct-17" }),
    &mut context,
    &policy,
    &audit,
)?;
Ok::<(), Box<dyn std::error::Error>>(())
}

additionalProperties: false is emitted in the exported schema and enforced at dispatch. Supported parameter types are the bounded JSON primitives string, number, integer, boolean, object, array, and null. Applications must perform domain validation inside the tool as well: a JSON string is not proof that an account identifier belongs to the principal.

Destructive and financial approval

High-risk approval is bound to the exact serialized payload. Changing an amount, destination, identifier, or any other field produces ToolExecutionError::ApprovalPayloadMismatch.

The fragment below continues with the registry, context, policy, audit sink and high-risk tool initialized by the application as described above, so it is contextual rather than a standalone program:

use rullst_ai::ai::HumanApproval;
use serde_json::json;

let payload = json!({ "invoice_id": "inv-42", "amount_cents": 1500 });
let approval = HumanApproval::for_payload(
    "issue_refund",
    &payload,
    "authenticated-reviewer-9",
    "support ticket FIN-42",
)?;
context.approve(approval);

let result = registry.execute(
    "issue_refund",
    payload,
    &mut context,
    &policy,
    &audit,
)?;
Ok::<(), Box<dyn std::error::Error>>(())

The approval is consumed once. Its approver and bounded reason are recorded in authorized/success/failure audit events, while the payload itself is omitted to avoid duplicating secrets or personal data in the audit trail.

Durable local evidence

InMemoryToolAuditTrail is bounded and process-local; it is intended for local development and tests. A single-process service can instead open the built-in bounded local trail:

#![allow(unused)]
fn main() {
use rullst_ai::ai::DurableToolAuditTrail;

let audit = DurableToolAuditTrail::try_open("storage/audit/ai-tools.log")?;
Ok::<(), Box<dyn std::error::Error>>(())
}

Each append is synchronized and followed by sync_data. The distinct versioned tool stream has 16 MiB and 4,096-record default ceilings, validates every frame and event on restart, and rejects corruption, unsafe file types, external length changes and quota exhaustion. try_open_with_max_bytes may set a smaller byte ceiling.

This is a single-process local writer, not an external audit service. Its SHA-256 frames detect accidental or same-length record corruption but are not a signature or HMAC. The host owns trusted directory permissions, exclusive writer operation, rotation, retention, backup, incident export and deletion policy. Multi-instance applications should implement ToolAuditSink over an appropriate durable destination.

Production boundary

The registry deliberately does not provide network fetchers, shell execution, database ownership policy, distributed budgets, provider tool-call parsing, or human identity verification. Tools providing those capabilities must enforce their own domain authorization and egress policies. The provider capability matrix therefore continues to report provider tools as unsupported.

v12 security claims and evidence ledger

This page is the canonical boundary for security claims made by the Rullst v12 release notes. Each row names the narrow behavior implemented in the repository, the tests that exercise it, and a known limit. A security statement that is not represented here is not a v12 release guarantee.

The evidence links point to source-controlled tests. A passing local run proves only that checkout and environment; the release candidate must attach the same checks to its immutable tag SHA. None of these rows is a certification, external audit, proof of complete OWASP coverage, or substitute for an application’s own threat model.

Narrow v12 claimImplementationVerificationKnown boundary
The optional Connect Axum session flow binds one authorization callback to ten-minute state + PKCE and, for OIDC, nonce held in tower-sessions; it removes and immediately saves the challenge before validation and does not print its URL, code, nonce or verifier through managed Debug implementations.Managed session flow and constant-time callback state comparisonRound-trip, sequential replay, expiry, mismatch, missing-state, replacement and redaction negatives, promoted to TM-AUTH-04 in the release minimumOne challenge per browser session invalidates an earlier concurrent tab. The generic session-store contract is not a distributed compare-and-delete across requests that already loaded the same record. The host still owns durable/shared session storage, Secure/HttpOnly/SameSite cookie and TLS/proxy policy, redirect registration, post-login rotation, idempotent account linking/recovery and live-provider conformance.
The explicitly mounted Connect Mock IdP supplies a bounded signed OIDC loopback fixture: exact configured HTTP-loopback issuer/client/callback, expiring one-shot code, optional S256 PKCE and nonce, EdDSA ID token/JWKS and issued-bearer userinfo.Concrete router/state, validated configuration and fixture signerReal OidcProvider loopback discovery/exchange/signature/replay test and local negative matrix, plus router integrationThe signing seed and credentials are public deterministic fixtures. The state is bounded and process-local. This is not safe for public exposure and does not implement interactive login/consent, refresh/device/federation lifecycle, durable state, key rotation or OIDC conformance.
Nexus requires an explicit access policy and rejects an unverified peer; registered CRUD metadata and submitted semantic fields also fail closed before dynamic SQL. An opt-in registered text tenant column scopes every built-in read/mutation/batch path to a trusted Core TenantContext, and required mutation audit couples one minimized record to the data transaction.NexusAuthPolicy, registry validation, bounded form validation, scoped mutations, scoped batch and audit schema/APIAccess-policy unit tests, metadata negatives, semantic validation negatives, HTTP widget/parameter-pollution regression, and materialized cross-tenant/audit atomicity regression, promoted as TM-NEXUS-02/TM-NEXUS-06 through portable release-minimum testsLoopback convenience is debug-only and Basic Auth requires verified TLS. Unscoped models remain global. The application owns identity/membership/domain policy, within-tenant ownership, custom-route authorization, database schema/privileges and audit export. The built-in audit table is same-database mutable committed-event evidence, not denied-attempt, append-only, tamper-evident, retention, backup, replication or external-review evidence; generated create keys may be absent.
Studio’s built-in local capability accepts only a verified loopback peer and local Host in debug builds, and unsafe methods require a same-origin Origin.LocalStudioAccessInline loopback, remote-peer, missing-peer, Host and Origin negative tests, plus router integration testsThis bounds the supported local browser surface against direct remote access, DNS rebinding and cross-origin mutation. Shared or release deployments still need application-owned authentication, administrator authorization, TLS and network policy; Studio does not supply them.
Newly generated CORS middleware fails closed without a valid explicit origin allowlist.CORS scaffold templateGenerated-middleware HTTP testsPreviously generated applications are copied code and are not rewritten by a CLI upgrade. Browser/proxy behavior must still be tested in the deployed application.
Newly generated fiscal-receipt mail keeps an OfflineMock visibly unauthorized, rejects contradictory fiscal markers, and both fiscal/dunning action links require credential-free HTTP(S) URLs.Extended mail scaffold templates, validate_action_url, and CLI dispatchMaterialized seven-template Clippy/runtime/provenance/link/collision contract and action-URL unit negativesGenerated source cannot prove an application-supplied response truthful, authorize tax documents, calculate due state, schedule delivery, pause access, or define billing policy. Previously generated source is not rewritten.
The opt-in Mail inspection guard completes every configured static inspection before transport and fails closed on scanner unavailability. Its strict local policy rejects executable magic, spoofed recognized types, active PDF/SVG, recognized secrets and unsafe links.Inspector contract and guardSafe-shape and active/type-confusion negatives, promoted to TM-MAIL-01 in the release minimumThe local policy is a bounded heuristic, not antivirus, sandbox execution, recursive archive inspection or CDR. Hosts that require authoritative scanning must provide and operate a production AttachmentInspector.
The opt-in Mail suppression guard checks a recipient before transport and fails closed when suppression state is unavailable. The SQLite store binds provider/event/payload replay exactly, escalates manual/hard-bounce/spam-complaint state monotonically and enforces immutable quotas across restart and competing local instances.Suppression domain and guard and SQLite storeProcess-local contracts and restart, two-instance quota, conflict, pruning, target and corruption negatives, promoted to TM-MAIL-02 in the release minimumSuppressionEvent does not authenticate a webhook. Only already verified provider events may be recorded. Replay retention must cover provider redelivery; file protection/encryption/backups, multi-host replication and provider conformance remain external.
Mail terminal observations exclude recipient, subject, body, filenames and provider response content. The default sink is bounded and exposes eviction count, while observer failure cannot turn an accepted delivery into a retryable error.Observation contract and wrapperOutcome, minimization, capacity, tenant and label regressions, promoted to TM-MAIL-03 in the release minimumThe default sink is process-local. External tracing/metrics delivery, retention, correlation and alerting remain deployment work; a custom sink must not block or panic.
Newly generated billing code selects SQLx or Turso-primary persistence, derives checkout/portal identity from an authenticated extension, requires an explicit production plan allowlist before persistence, accepts webhook events only through the mandatory verifier layer, refuses existing outputs and rejects cross-owner subscription reuse before binding the conflicting customer.Billing generator and generated controllerMaterialized SQLite/Turso Clippy, migration, persistence, ownership and collision contract, promoted under TM-DEPLOY-06Generated code is application code. The host must configure correct provider plan IDs, mount auth and exact webhook middleware, add durable provider-event idempotency/reconciliation, validate a provider sandbox and review previously generated source separately.
Core CSRF middleware uses a double-submit check and only exact configured webhook exemptions.CSRF middlewareCSRF unit tests and application integration testsSafe deployment still depends on HTTPS, cookie configuration, application authentication, and a correct exact-path exemption list. Request-body inspection is bounded.
Core WAF inspects and preserves supported bounded request bodies and rejects bodies outside its one-megabyte inspection envelope.WAF middlewareWAF body and query testsDetection is pattern-based, has false positives and negatives, and is not complete OWASP coverage. Streaming or other uninspected representations require application controls.
Parameterized routes recognized by the CLI audit require an adjacent public/owner/role/admin classification; owner paths require the recognized ownership guard.Bounded route source audit and RbacGuardAudit negatives, scaffold contracts, and HTTP owner/cross-owner testStatic analysis recognizes bounded syntax and guard names; it cannot prove the correctness of an application’s identity source, object lookup, or business authorization.
Encrypted session tokens are versioned, authenticated with AES-256-GCM, expire, and reject malformed or weak-key inputs.Session implementationSession negative and round-trip tests and integration testsApplication-key custody, rotation, session/device revocation, and distributed logout are application or roadmap responsibilities.
The optional application JWT policy binds versioned access claims to issuer, audience, subject, times, token ID and session version; it bounds TTL/scopes and supports strong kid key rotation. The SQLite profile serializes bounded JTI/session-version revocation shared by local processes and verifies it asynchronously.JWT policy and SQLite revocation storeRotation, revocation, audience, configuration and production fail-closed tests, plus restart, exact quota, two-instance and corruption evidenceThe bundled in-memory store remains process-local and is rejected by production mode. SQLite is one shared local file, not multi-host replication, secret management, backup/availability or a refresh-token workflow. Third-party OIDC verification belongs to Connect.
MFA recovery helpers generate subject-bound 80-bit codes and persist only salted HMAC-SHA256 verifiers; verification is constant-time and a successful in-memory consume removes one verifier.Recovery-code implementationSubject binding, single-use, plaintext-storage, format, pepper and tamper testsThe application must render plaintext once, protect the pepper, persist compare-and-delete transactionally, rate-limit attempts and audit enrollment/recovery. This is not a complete account-recovery workflow.
TOTP enrollment uses 160 bits from the OS RNG, rejects weak/invalid inputs, compares exactly six ASCII digits in constant time and can render a bounded SVG QR.TOTP and QR implementationSecret, code, drift, invalid-input and real-SVG testsThe application must encrypt the TOTP secret, protect the enrollment ceremony, confirm a code before activation, rate-limit verification and own account recovery.
The optional Redis rate limiter hashes client-derived keys and atomically shares a bounded fixed-window counter across independent clients. Empty or mock_* configuration is explicitly process-local and rejected by the production boundary.Redis rate limiterOffline fail-closed regression, live independent-client contract, and digest-pinned services in CI / releaseA single Redis service does not prove cluster/failover behavior, Academy HTTP identity/origin composition, or an operational abuse policy.
Core can bind its storage, cache and in-process realtime/presence facades to a tenant selected by authenticated membership. TenantStorage applies an immutable tenants/<tenant>/ object root after strict path validation; TenantCache validates logical keys, applies an immutable backend namespace and exposes no global flush; TenantRealtime/TenantPresence validate logical names, cap message payloads at 64 KiB and namespace rooms.TenantStorage, TenantCache, realtime wrappers and TenantContextExact same-logical-name cross-tenant non-interference tests for storage, cache, broadcast and presence rooms in realtime (TM-TENANT-04)These prove local wrapper contracts, not room-level authorization, distributed realtime/presence liveness, remote bucket policy, search/metrics/export isolation or automatic use by Academy/application routes. Redis cluster/failover remains separate evidence.
The LMS starter persists schools, active/default memberships, course scopes, cohorts and temporal entitlements. Authentication treats X-School-ID only as a selector against persisted membership, binds the validated tenant to UserContext, and loads roles for that school. Learning, publication/rollback, assignment grading/correction, score correction/leaderboard, completion/certificate mutation, role lifecycle and scheduled publication enforce the selected school. Outbox, derived automation and notification state preserve school_id; the leaderboard cache is tenant-scoped, payload-validated and invalidated after authoritative mutations.Tenancy models/migration/service, authentication and learning composition, leaderboard/cache service, and tenant-aware RBACMaterialized arbitrary/ambiguous selection, entitlement, cache/invalidation, same-user notification/preference separation, foreign-rule isolation, cross-school HTTP enrollment/leaderboard and foreign-admin mutation/scheduler/role non-interference, plus exact tenant guard testsThis is bounded SQLite database/HTTP/local-cache evidence, not complete multi-tenancy. Other caches, files, search, metrics, exports, Nexus, invite/provisioning, distributed cache/failover and PostgreSQL/MySQL non-interference remain open.
The Academy scaffold provides a minimized, school-scoped privacy lifecycle foundation: age band instead of birth date, versioned bounded retention, purpose-specific guardian consent/revocation and idempotent export/delete request records. Minor authorization fails closed without matching active consent. A bounded admin/owner sweep schedules replay-safe delete requests; fulfillment adds exact leased claims, abandoned-claim recovery, delayed retry/dead-letter, a hard ten-attempt claim ceiling and actor/SHA-256-bound completion. A supervised static-dispatch executor applies an adapter timeout shorter than the lease, explicit shutdown and local metrics; its deterministic mock never exports, deletes or anonymizes data.Privacy migration and service templates, retention scheduler, request worker protocol, and supervised executor boundaryMaterialized consent, cross-school, stale-token, lease-recovery, retry/dead-letter, hard-limit, completion-digest, supervised-success and adapter-failure regression (TM-ACADEMY-10)No product adapter that performs cross-table export/deletion/anonymization, legal-guardian verification, PII-safe application telemetry proof or legal compliance claim is provided.
Core’s storage-agnostic upload admission contract enforces a hard in-memory size ceiling and kind allowlist, validates canonical tenant/name and exact declared MIME/extension against recognized signatures, denies recognized active text, emits randomized tenant-prefixed quarantine keys and a SHA-256 digest, and fails closed unless a scanner adapter supplies a valid clean verdict.Upload admission/quarantine contractExact unit negatives for active SVG text, traversal, tenant injection and MIME/extension spoofing, plus digest-bound clean/infected/unavailable mock scans in the same moduleThis is not an HTTP multipart implementation, deep parser, S3/R2 mover, transcoder or production malware scanner. PDF/media checks are bounded signatures, the bundled scanner is mock-only, and archives/SVG/HTML remain denied or outside the allowlist.
The LMS outbox commits score, first lesson completion, enrollment, achievement, publication, editorial rollback, assignment submission/grading/correction, course completion and certificate revocation events with domain state, claims an available event under a bounded lease, binds acknowledgement/failure to an exact token, schedules bounded retry, recovers expired claims, counts attempts and dead-letters at a bound. Its supervised database worker validates each supported envelope, loads current rules, rederives/applies the sole achievement action and ACKs/fails the exact claim; configuration, shutdown, safe drop and local counters are explicit.Generated score/outbox templates, assignment services, progress/completion services, claim service, worker, closed passive envelopes, and executorMaterialized SQLite supervised worker/FIFO delivery, assignment/rollback/completion/revocation delivery, progress, score, backoff, expired-claim/stale-token, ACK-loss redelivery, transactional achievement and poison dead-letter regressionNo external transport or exported durable telemetry is built in. Only score_recorded → award_achievement currently drives an automation mutation; tenant policy and approval envelopes are required before adding sensitive actions.
The LMS starter grades single-choice quizzes from server-side question/option state, binds replay to attempt/quiz/learner/ruleset, enforces the configured attempt count, and commits immutable answers, a ScoreEvent, the authoritative leaderboard update, score_recorded and quiz_graded in one transaction. Timed quizzes require a persisted server-time start/expiry session and server-random question/option presentation; replay cannot extend/change either, starting consumes an attempt and grading rejects an ID set changed under the same ruleset. Authenticated start/submit routes derive subject/quiz from session/path and never accept client points.Generated grading, timing ceremony, HTTP boundary and schema/modelsMaterialized SQLite correct/incorrect grade, start/order replay/expiry, cross-user, unknown-option, attempt-limit, score-event/outbox counts and leaderboard regressionQuiz grade override, browser UI and multi-database contention proof remain open; distributed clock and concurrent-start behavior remain deployment/test boundaries.
The complete LMS starter’s owner-only activity routes accept only an idempotency key plus one selected option, a complete bounded pair-ID permutation or bounded typed text. Static-dispatch single-choice/matching/typed evaluators and opaque ValidatedActivityResult keep identity, answers, points, policy, evidence and time server-owned. Typed replay uses a policy-bound SHA-256 digest rather than raw text. A transaction-locked recheck binds exact evaluator configuration and atomically stores the exact-replay attempt, appends ScoreEvent v2, updates the leaderboard and emits strict score_recorded v2. Attempt identity is learner/activity-scoped and event identity is server-derived.Activity evaluation/HTTP templates, matching evaluator/route, typed evaluator/route, persisted bridge, replay gate, score service and schemaMaterialized SQLite HTTP owner/cross-user, actor/evidence/policy mismatch, malformed pair/text input, normalized/conflicting replay, raw-text absence, durable attempt, score/outbox/leaderboard and downstream automation regressionTyped comparison does not provide Unicode normalization/accent/fuzzy semantics and its digest may remain personal data. Listening/game evaluators, quiz-path unification and PostgreSQL/MySQL contention proof remain open.
Enabled Academy activities update a versioned deterministic review schedule in the same transaction as a newly applied authoritative score. Policy/state validation and algorithm drift fail closed; exact replay returns before the schedule transition. The bounded owner-only due endpoint derives learner/time server-side and rechecks school membership, course scope and active enrollment.Review policy/state/service and HTTP templates, score transaction, routing and schemaPure deterministic pass/lapse transition test and materialized SQLite three-activity persistence, replay, future queue and cross-user boundaryrullst-box-v1 is an inspectable bounded heuristic, not FSRS/SM-2 compatibility, educational efficacy, AI personalization or generated pedagogy. Algorithm migration/experimentation, complete privacy integration, visual UX and PostgreSQL/MySQL contention remain open.
Text assignments persist versioned server policy, rubric criteria, owner-bound submissions, human grades and criterion feedback. Submission requires the authenticated learner’s school-scoped enrollment, server deadline/attempt limit and exact idempotency request. Grading/correction derive the lesson scope from persisted assignment state, require the selected school and then apply evaluator/admin/rubric policy. Admin correction is append-only and records before/after, reason, actor, server time, canonical request and outbox.Assignment models, submission service, grading service, correction service, schema, and HTTP boundaryMaterialized deadline, cross-user, replay, impossible-score and correction regression, plus foreign-school grading denialThere is no attachment pipeline, visual authoring/review UI, tenant-scoped outbox or PostgreSQL/MySQL contention proof. Human evaluator identity is only as strong as the host authentication/role lifecycle.
Protected LMS lesson, progress and assessment paths require the authenticated school, an active membership, a course scope, open-or-active entitlement policy, active enrollment and exactly one valid versioned availability rule; server time enforces release/expiry and same-course prerequisites.Generated learning guard, school service, and availability schema/modelMaterialized prerequisite/release/expiry regression and cross-school HTTP/database denial plus entitlement regressionEntitlement issuance is not connected to billing, and signed media/download URLs, cross-subsystem tenant isolation and multi-database contention remain open.
Academy course completion is derived only after school-scoped membership/entitlement checks and from the immutable version pinned at enrollment plus persisted lesson progress under a closed rule. The transaction writes immutable evidence, one opaque certificate and course_completed; public verification omits learner identity, while revocation requires an admin in the certificate’s school, server time, reason and exact idempotency.Completion service, models/schema, and HTTP boundaryMaterialized completion/revocation regression and foreign-school revocation denialCertificates are database-verifiable opaque records, not externally signed credentials. Portable signatures, full relational snapshots, tenant-scoped outbox, PostgreSQL/MySQL contention and an external revocation registry remain open.
A newly applied Academy achievement emits school-bound achievement_awarded in the same transaction; the exact claimed event creates one localized in-app notification in that school, applies school/user opt-out/locale state, and authenticated owner/admin APIs control bounded school-filtered listing, preference updates and idempotent read transition without accepting subject identity from input. Its closed versioned template renders Portuguese, Spanish or English with deterministic English fallback and rejects unknown keys/payloads. A new unsuppressed row and the same rendered projection are sent after commit through TenantRealtime to an authenticated tenant/user subscription.Transactional achievement event, notification contract/schema/realtime projection, closed translation templates, HTTP boundary, and worker dispatchMaterialized database delivery, three-locale/fallback rendering, unknown-key denial, realtime receipt, owner/cross-user listing/read/preference/replay and same-user cross-school notification/preference isolationThe database is authoritative; realtime is in-process and best-effort. Catalogs for other event kinds, Mail/push, distributed realtime/replay, tenant-scoped Nexus UI and PostgreSQL/MySQL contention remain open.
Course versions are immutable snapshots with explicit draft/review/scheduled/published transitions, an admin reviewer distinct from the author, atomic archival/publication plus outbox, and an enrollment-time pin that does not move when a newer version publishes. Authenticated handlers derive the author/reviewer and current time from server state rather than payload fields. A supervised bounded scheduler acquires/renews/releases an exact shared lease, activates due versions with an admin context, preserves the independent reviewer, records the activation actor and fails closed on lease loss. Admin-only editorial rollback rejects the source author, requires reason/idempotency, creates a new published revision from historical content and atomically persists replaced/result version evidence plus outbox without moving old pins.Publication model/service/migration, rollback service/model/migration, HTTP boundaries, scheduler loop and transactional enrollment pinMaterialized service/pin regression, rollback authorization/replay/conflict/audit and old/new pin regression, and handler plus supervised scheduler contention/activation/replay continuationThe starter does not yet serve every module/lesson/assessment field from the snapshot, expose visual Nexus authoring, export scheduler telemetry to Studio, or prove PostgreSQL/MySQL contention.
Education roles are durable, allowlisted and reconstructed from active assignments for the authenticated school; each grant stores school_id, actor, reason and validity window, support must expire, and only a distinct school owner can grant or revoke owner/admin. Exact grant/revocation replay is a no-op only for the same request; cross-school assignment lookup returns not-found.Role model/service/migration, HTTP boundary and authentication compositionMaterialized expiry/grant/revoke/replay negatives and foreign-school revocation denialThere is no explicit step-up ceremony, external audit sink or AI tool integration. Provisioning/bootstrap and full Nexus isolation remain application-owned.
Academy scheduler instances sharing a database coordinate on a named lease with exact holder/token renewal and release; another token can take over only at expiry. Scheduled publication mounts that lease around every bounded cycle, renews before each mutation and provides an awaited shutdown, safe drop and local counters.Generated scheduler lease model/service/migration and supervised publication schedulerMaterialized competing acquire, wrong-token renewal, pre/post-expiry takeover and stale-release regression, plus continuous scheduler standby, activation and empty replayDelay/failure metrics are not exported to Studio or a durable backend. PostgreSQL/MySQL contention and distributed clock-topology behavior remain unproved.
Automatic OAuth refresh validates and redacts token state, binds every replacement to the original provider user, serializes concurrent process-local refresh and swaps credentials only after a complete response with a bounded lifetime. A storage-neutral AES-256-GCM snapshot preserves the validated generation and authenticates the version, key ID, provider and trusted local-account binding before restoration. The optional SQLite store adds a persisted quota, restart recovery and exact generation CAS for shared-local writers.Refresh coordinator and values, encrypted snapshot and SQLite storeFresh, concurrent single-refresh, rotation, identity-confusion, malformed-response and rollback tests, snapshot unit negatives, public storage/ownership contract and the TM-CONNECT-24 two-instance/restart/corruption matrixThe host must authorize the account, provide high-entropy key custody/rotation, lease the remote provider call, reconcile a losing rotation, protect and back up the directory, configure retry/backoff/revocation and decide when reauthentication is mandatory. SQLite CAS is local durability, not a secret manager, remote refresh lock or multi-host replication contract.
The current passkey foundation binds ceremonies to relying-party/origin data, consumes bounded challenges once, verifies flags/signatures, and enforces monotonic counters. The SQLite profile persists bounded device registration/inventory/rename/revocation and advances counters with CAS shared by local processes.Passkey service, ceremony store and SQLite device lifecycleInvariant tests, negative-path tests, and real ES256 lifecycle, restart, replay, quota and two-instance CAS testsChallenge state remains process-local, so multi-instance ceremonies require sticky routing or a host shared layer. It has not passed an external WebAuthn conformance suite or independent audit; SQLite file trust/encryption/backup/replication and device ownership remain deployment/application duties.
Capital exposes Axum and opt-in Actix middleware over one canonical verifier. Both bound payloads at two megabytes, verify the selected provider before dispatch, restore the exact signed body, insert the normalized event, check protocol freshness where defined, and reject replay within the configured store.Canonical verifier and Axum adapter and Actix adapterStripe/LemonSqueezy canonical, freshness, reconstruction, capacity, expiry, Actix preservation/rejection and replay testsThe default replay store is process-local and payload-keyed. Multi-instance deployments require durable shared provider-event idempotency before side effects, provider-specific operational validation, and an append-only audit trail.
A Billable direct charge requires bounded integer minor units, currency, provider-owned customer and tokenized payment-method IDs, validated model e-mail and an application idempotency key. Stripe forwards the key, confirms off-session and accepts only amount/currency-bound succeeded or processing; deterministic offline receipts are explicitly non-success Mock.Charge contract, Billable helpers, and Stripe adapterValidation/redaction/provider-negative integration, Stripe response-confusion negatives, and facade derive regressionThe application must authorize provider IDs, establish mandate/SCA, persist unique keys/order state, reconcile signed events and drive entitlements. No other adapter or live sandbox is implied.
Metered usage uses provider-specific identities and actions rather than guessing from one uniform subscription ID. Stripe forwards and binds customer/event/value/timestamp/identifier; Lemon Squeezy sends and binds the subscription-item relationship, quantity and action while preserving a separate application event key.Usage domain, Stripe adapter, and Lemon Squeezy adapterStripe exact HTTP/body-limit/mismatch/mock tests, Lemon exact JSON:API/mismatch/mock tests, and domain validation/redaction testsThe fixtures do not establish live-account acceptance. Stripe uniqueness is rolling, while Lemon’s request has no equivalent provider event key; the application must durably claim events, configure the matching aggregation, retry/reconcile and drive quota/entitlement state.
Coupon IDs and relative trial changes are bounded before provider dispatch. Stripe’s discount response must bind the subscription and expanded coupon; Stripe and Lemon Squeezy trial responses must bind subscription and exact expiration. Unreviewed live operations fail explicitly.Subscription values, Stripe protocols, Lemon Squeezy trial protocol, and static handleExact local HTTP fixtures assert form/JSON:API headers, body and response binding; cross-provider fail-closed integration covers unreviewed live paths.The host must authorize ownership, persist one command clock for stable retries, serialize conflicting updates, understand provider billing-cycle effects and reconcile signed webhooks. Protocol fixtures are not live-account acceptance.
Shared Team/Workspace quotas use a trusted billing subject, an application-stable event key and an atomic conditional counter. An over-limit or exact replay never executes QuotaGate’s callback; a conflicting replay fails closed. The SQL adapter can reserve in the same transaction as the domain insert.Quota domain and gate, SQL store, and Billable limit derivationLocal validation, isolation, replay, compensation and concurrency plus live PostgreSQL, MySQL, and MariaDB contention contractsAuthentication must establish membership and the active tenant; webhook reconciliation must persist the authoritative tier. Schema migrations, abandoned standalone reservation policy and Turso/NoSQL adapters remain host work. Writes outside the explicit gate are not intercepted.
Opt-in paid-invoice delivery rejects non-final/mock payment evidence and exact recipient, minor-unit amount or currency mismatch before producing bounded HTML/PDF; the Mail bridge runs pre-flight and retains a stable non-secret delivery key.PaidInvoice binding, PDF renderer, and Mail bridgePayment-status/substitution and key-stability negatives, font/pagination/PDF parse tests, and prepared plus explicit-driver delivery testsA custom adapter is trusted to authenticate its own provider response. The application must construct the authoritative order, reconcile webhooks, atomically claim the key in a durable outbox and own retry/provider attachment policy. No automatic webhook hook, provider acceptance or exactly-once delivery is implied.
Vault field envelopes use authenticated AES-256-GCM, random nonces, version/key identifiers, AAD binding, and explicit rotation keys.Vault implementationRound-trip, tamper, wrong-key, malformed-envelope, and rotation testsThe caller owns key custody and rotation. No real KMS/HSM adapter or hardware-backed key claim is made.
#[orm(encrypted)] transparently encrypts String and Option<String> values on generated writes, decrypts generated model reads, authenticates table/column context, and reads prior keys from an explicit keyring.ORM privacy implementation and derive integrationEnd-to-end SQLite insert/read/update/rotation/tamper/query-boundary test and envelope unit testsRandomized fields cannot support generated filtering, ordering, grouping, or explicit selection. Raw SQL is not transformed, blind indexes are application-owned, full key retirement is not automated, and key custody remains external.
Portable document recovery encrypts and authenticates a versioned MongoDB/SurrealDB/deterministic-store snapshot with AES-256-GCM, fresh nonce, explicit rotation key and length-delimited application/collection binding. Export compares two bounded inventories; restore accepts only an empty or exact matching subset, never replaces/deletes and verifies the final collection.Recovery types and redacted key/envelope, bounded codec, and restore algorithmTamper/key/scope, conflict/extra-row, replay, capacity, ordering and concurrent-source negatives, plus the live MongoDB→SurrealDB→MongoDB matrixApplications must quiesce writers, provision destination schema and own high-entropy key custody, rotation, snapshot permissions, durability, retention and erasure. Two equal scans are not formal online snapshot isolation; partial successful inserts remain for exact retry, and no managed backup, point-in-time recovery, replication or cross-store transaction is claimed.
OTA accepts firmware for commit only after Ed25519 verification binds target, version, rollback counter, length, and SHA-256 digest. The store-backed path additionally requires exact monotonic compare-and-set before local mutation.OTA manifest verifier and counter-store contractOTA integration negatives and state tests, restart/retry/conflict store tests, and bounded fuzz targetRullst does not download, flash, boot, or implement/certify a physical durable counter. Platform bootloader, storage integrity, wear and power-loss integration remain external.
Core’s canonical browser baseline installs the exact application config outside a stable secure-header/CSP nonce → exact-origin CORS → WAF → CSRF → optional PII order; Server uses the same composition. Core and the optional extended secure-header layer reuse one per-request CSP nonce. Generated LMS/SaaS authentication plus LMS catalog/course/player style elements consume that nonce, and the audited LMS shell has no remote page dependency or inline style attribute.Core baseline, generated authentication controller, LMS catalog, and extended layerIntegrated Core baseline tests, generated catalog contracts, materialized escaping/nonce regression, and extended nonce testsIn-process/source evidence does not guarantee a scanner grade or real-browser behavior. TLS, proxy policy, every application-owned HTML surface and session/auth/tenant layers still require deployment testing.
Response DLP masks recognized secrets only in supported, bounded textual payloads without corrupting invalid UTF-8 or incomplete values.DLP middlewareUnit/integration tests and bounded fuzz targetIt does not prove zero leakage and does not rewrite arbitrary binary, compressed, encrypted, or streaming bodies. Unknown secret formats can pass through.
RASP rejects the implemented SQL injection, path traversal, JNDI, SSRF, RCE, header, and decoded JSON patterns within its inspection boundary.RASP inspectorMiddleware and pattern tests, integration tests, and bounded fuzz targetIt is a bounded detector, not a parser for every protocol or proof against every bypass; false positives and false negatives remain possible.
Login Jail applies bounded progressive delay and temporary process-local jailing, with cleanup of expired state; its async API awaits the selected delay.Login guardApplied-delay, jail, expiry, capacity, and concurrency tests and contention testsState is not distributed. Correct client identity depends on trusted peer/proxy handling, and deployments still need upstream abuse controls.
The optional Redis limiter consumes one namespaced fixed-window budget atomically, hashes the caller-supplied client key and returns TTL-derived retry metadata.Redis limiterConfiguration, key-redaction, explicit-mock and shared-clone testsOffline mode is process-local and require_distributed() rejects it. Real Redis cross-instance, eviction/failover and trusted-proxy deployment tests remain required; the caller still owns the identity key policy.
Audit-chain entries use HMAC verification and unambiguous length-prefixing; ordinary process-local telemetry is not presented as HMAC verified.Audit chain, telemetry store, authenticated journal, and Nexus renderingTamper/concurrency tests, telemetry provenance tests, journal forgery/rotation tests, and Nexus badge testThe compatible DurableSiemSpool deliberately stores unsigned events. The separate opt-in journal sets the flag only after verifying its complete local HMAC chain; it does not authenticate the truth or identity behind a semantic event. Key management, trusted tail checkpoints, external storage/delivery and independent verification remain open.
Local security telemetry uses an exact, versioned v1 JSON envelope with normalized identifiers, IPs and timestamps, bounded UTF-8 details, escaped CEF extension values and opt-in synchronous local journals.LiveSecurityEvent, machine-readable schema, CEF serializer, compatible spool, and authenticated journalSchema/normalization tests, unsigned spool tests, authenticated restart/rotation/forgery/ordering tests, public API restart test, and CEF injection testBoth formats are bounded and single-process. The authenticated form supports explicit key rotation but not trusted whole-tail checkpoints, directory policy, compaction, delivery, acknowledgement, retention, correlation, retry, dead-letter handling, or an external SIEM.
Declared JSON bodies fail closed on invalid syntax, recursive duplicate keys, excessive size/depth and ambiguous content types; an explicitly mounted policy additionally enforces one bounded JSON Schema 2020-12 document or OpenAPI 3.1 component without external retrieval; recognized log secrets and local SRI assets have explicit bounded helpers.Schema guard, compiled policy, log redactor, and SRI helpersInline malformed/duplicate/nested JSON, schema shape/external-reference/OpenAPI component negatives, repeated secret and file-backed SRI tests in those modules, plus DLP/RASP integrationThis validates JSON bodies only after explicit per-route mounting. It does not infer arbitrary route schemas, authorize a subject, enforce domain rules, install a global tracing filter, discover every secret, or rewrite every asset automatically.
Security CLI evidence fails when an explicitly requested Geiger/SBOM/network check is incomplete or reports a finding; SBOM UUID/component fields and actual MSRV parsing are regression-tested.Audit orchestration, SBOM/network evidence, doctor, and managed hookUnit tests in the linked modules and strict package ClippySource heuristics are not complete static analysis; the SBOM is not signed attestation; listener observations are not external reachability; tool presence and local output are not certification.
AI prompt checks and PII masking run before built-in provider dispatch and report only a heuristic result.GuardrailReport, provider dispatch, compatible adapter, and versioned corpusVersioned cross-provider eval runner, cross-provider guardrail pipeline, compatible public/loopback contracts, and provider capability testsThe corpus is a deterministic regression suite, not a safety benchmark. Compatible capabilities are caller declarations, not discovery. Passing heuristics or JSON schema does not make output trustworthy, stop adaptive injection, authorize tools, prevent egress, or prove an upstream request was cancelled.
Every built-in live AI transport applies a configurable local request deadline, defaulting to 30 seconds.OpenAI, OpenAI-compatible, Anthropic, Gemini, DeepSeek, and Ollama adaptersCapability contract and loopback timeout/shape regressionsA local timeout only drops the request future. It does not prove upstream cancellation, prevent billing, provide automatic retries, limit concurrency, or implement circuit breaking.
The strict AI egress policy denies every host until an exact allowlist is configured and rejects insecure/credentialed URLs, local/private/metadata/reserved addresses, mixed DNS answers, disallowed redirects and oversized bodies. Its opt-in fetcher resolves under deadline, pins all validated answers into a proxy-free client, verifies the connected peer, disables automatic redirects and enforces the byte budget while streaming.EgressPolicy, host normalization, and EgressFetcherIPv4, IPv6, allowlist, DNS, redirect, configuration and resource-budget negatives, plus private/mixed DNS rejection before transport and streaming overflow testsThe fetcher is not mounted around arbitrary application/provider traffic. Tenant-aware destination authorization, response content/schema validation, data minimization and a deterministic successful live-origin redirect/stream contract remain caller/integration work.
Local AI tool dispatch requires an exact allowlist, principal authorization, closed bounded JSON, call budget and audit sink; destructive/financial approvals are one-use and exact-payload-bound. Bounded local RAG/tool trails synchronously persist distinct versioned streams and fail closed on quota, corruption, unsafe targets and competing-writer growth.Guarded tool registry, policy, tool audit, RAG audit, and shared durable formatAuthorization, schema, budget, approval binding, failure, and audit tests, plus restart, tamper, quota, concurrency and real-pipeline durable evidenceProvider-native calling, principal/approver authentication, domain ownership and network egress policy remain application responsibilities. The local trail is single-process; SHA-256 detects corruption but does not authenticate events, and the host owns permissions, rotation, retention, backup and external delivery.
The Academy production preset normalizes all twelve declared boundaries and fails unless each has one explicit evidenced PASS.ProductionPreset::academy and the Academy CLI diagnosticPreset negatives, diagnostic normalization tests, process-level CLI tests, and CLI inventory contractEvidence is caller-declared and the diagnostic always reports certification: false; it does not inspect a deployment, prove the declaration true, or replace independent review.
Live Alipay RSA2 verification and NFS-e homologation/production fail closed instead of returning simulated success. NFS-e local preparation separately enforces pinned XSDs, PKCS#12 XMLDSig, deterministic issuance JSON, bounded signed-authorization and structured-rejection parsing and bounded mTLS construction.Alipay adapter, NFS-e signer, protocol codec, schema boundary, and NFS-e clientAlipay unsupported-path tests plus fiscal builder, signature verification, protocol binding/tamper/decompression negatives, official-artifact opt-in validation, and environment testsOffline fixtures and local schema/cryptographic/protocol validity are not legal compliance, certificate trust, tax authorization, settlement, live response evidence, independent review, or provider homologation.

Release use

Before an RC or stable release, reviewers should:

  1. run the linked tests through the workspace trifecta on the candidate SHA;
  2. attach multi-OS CI and packaged-distribution evidence to that same SHA;
  3. confirm the changelog uses the narrow claim wording above;
  4. keep every remaining boundary visible in the release notes; and
  5. leave the release at NO-GO if a claimed control lacks matching evidence.

Threat assumptions and abuse cases live in the v12 threat models. The broader implementation/roadmap boundary lives in the capability ledger.

Security event schema v1

rullst-security::LiveSecurityEvent is the bounded event contract rendered by Studio/Nexus and accepted by the process-local security store. Its current schema version is exported as SECURITY_EVENT_SCHEMA_VERSION = 1. The package also embeds and exports the JSON Schema 2020-12 document as LIVE_SECURITY_EVENT_V1_JSON_SCHEMA; its packaged source is security-event-v1.schema.json.

This is an application telemetry envelope, not a remote SIEM transport. The optional local spools provide bounded persistence and one explicit authenticated-journal mode, but the schema itself does not provide delivery, retention, correlation, acknowledgement, retry, dead-letter handling, source identity, or regulatory evidence.

JSON fields

Fieldv1 contract
schema_versionInteger 1. Legacy JSON without the field deserializes as v1; locally stored events are normalized to v1.
event_typeNon-empty uppercase ASCII letters, digits, and underscores; maximum 64 bytes. Invalid values become SECURITY_EVENT.
detailsUnstructured human-readable UTF-8 text; maximum 2 KiB and truncated only at a valid character boundary. It must not be parsed as authorization data.
client_ipCanonical IPv4/IPv6 string or unknown. Forwarded headers are not implicitly trusted.
timestamp_strAbsolute RFC 3339 timestamp. Invalid local timestamps are replaced at ingestion.
verified_hmactrue only when a connected verifier validated an HMAC for that exact event. It does not prove the event’s semantic claim or source identity. push_local_event always forces it to false.

Example:

{
  "schema_version": 1,
  "event_type": "RBAC_DENIAL",
  "details": "Authenticated principal denied access to the resource",
  "client_ip": "192.0.2.4",
  "timestamp_str": "2026-08-27T15:30:00.000Z",
  "verified_hmac": false
}

Producer rules

New local producers should use LiveSecurityEvent::local(...) and SecurityStore::push_local_event(...). This path:

  1. assigns schema v1 and an RFC 3339 timestamp;
  2. validates/bounds the type and detail text;
  3. canonicalizes the IP address;
  4. removes any caller-provided local HMAC claim; and
  5. stores the event in the bounded 50-entry process-local buffer.

The buffer is deliberately a dashboard snapshot. Loss on restart, eviction at capacity, and absence of a consumer are expected properties, not successful external delivery.

Compatibility rule

Within the stable v12 line:

  • fields cannot be removed, renamed, or change meaning;
  • a new required field or incompatible type requires a new schema version;
  • additive optional fields require tolerant consumers and a changelog entry;
  • event-type additions are compatible, so consumers need an unknown fallback;
  • consumers must not infer trust from verified_hmac alone; and
  • all compatibility claims apply to JSON, not field order in serialized text.

The version-one contract has source-controlled serialization, legacy-input, normalization, size, UTF-8, and CEF-injection tests. DurableSiemSpool can persist normalized unsigned v1 values in a local single-process file with exact byte/record quotas, versioned length/digest frames and restart validation. AuthenticatedSiemSpool is a distinct opt-in file format that authenticates sequence, named rotation key, predecessor and exact payload before JSON decode; verified reads set verified_hmac=true only after the complete chain passes. Release evidence still belongs to the exact RC tag SHA.

CEF boundary

format_cef_event is a serializer only. It escapes backslashes, equals signs, and CR/LF in extension values so event details cannot inject fields or records. Calling dispatch_siem_alert records a local SIEM-candidate event; it does not send or acknowledge an external alert.

Durable local spool boundary

DurableSiemSpool serializes one local event per bounded frame, calls sync_data before returning a receipt, validates every frame on reopen and rejects truncation, digest mismatch, invalid event JSON and external length changes. The SHA-256 digest detects corruption; it is not an authentication tag. The spool deliberately supplies no directory creation, multi-process lock, rotation, retention, backup, retry, dead-letter handling or remote adapter. Operators must provide a trusted, permissioned directory and one writer process per file.

Authenticated local journal boundary

AuthenticatedSiemSpool uses domain-separated HMAC-SHA256 frames and a SiemKeyRing containing one active write key plus at most seven historical verification keys. Key material is held in zeroizing storage and omitted from Debug. Reopen fails closed for an absent/wrong key, forged payload, non-canonical sequence, broken predecessor chain, removed/reordered interior frame, symlink target, quota violation or external file-length change.

The chain does not identify whether the local producer’s semantic assertion was true. Removal of a whole valid tail also needs a checkpoint retained in a separately trusted system. The journal does not rotate/compact itself or supply multi-process locking, remote delivery, retry, acknowledgement or dead-letter handling.

A future operational sink should consume the versioned JSON envelope and define redaction, backpressure, authentication, durable spool/retry/dead-letter, delivery acknowledgement, retention, and multi-tenant access separately.

Comparativo técnico do Rullst e prioridades competitivas

Fotografia em 27 de agosto de 2026. Este documento compara capacidades documentadas e verificáveis, não popularidade percebida nem slogans. A fonte normativa do Rullst continua sendo a especificação; o capability ledger registra limites e o programa v12 contém os gates de release.

Resposta curta

O Rullst já tem uma combinação incomum no ecossistema Rust: servidor Axum/Tokio, ORM, autenticação, segurança em profundidade, IA multi-provider, pagamentos, email, filas/realtime, Admin CMS, control room e uma pequena fundação no_std para IoT sob uma mesma arquitetura tipada. Em critérios específicos, essa integração já é mais ampla que o núcleo oficial de vários frameworks comparados.

Isso ainda não permite afirmar que o Rullst é o melhor framework do mundo ou que é superior a todos eles de forma geral. Django, Rails, Spring Boot, Laravel, ASP.NET Core e outros têm anos de produção, comunidades, documentação, extensões, suporte e casos reais que o Rullst ainda precisa conquistar. O objetivo honesto é transformar diferenciais técnicos em uma plataforma estável, auditada, mensurável e agradável de usar.

Como ler o comparativo

Os veredictos usados abaixo têm significado restrito:

  • Diferencial comprovado: existe código, teste e limite documentado no Rullst;
  • Vantagem de escopo: o Rullst entrega mais componentes oficiais para esse caso, mas isso não prova maior qualidade em todos eles;
  • Paridade: ambos resolvem o problema por caminhos diferentes;
  • Rullst atrás: a alternativa tem hoje uma solução oficial mais madura ou uma experiência que o Rullst ainda não oferece;
  • Não comparável diretamente: os projetos ocupam camadas diferentes.

As comparações consideram o núcleo ou suíte oficial de cada projeto. Um plugin comunitário pode preencher várias lacunas, por isso este documento não afirma que algo “não existe no ecossistema” sem evidência. Também não faz afirmações de desempenho: velocidade só deve ser comparada com código, dataset, hardware, percentis e metodologia reproduzíveis.

Onde o Rullst já possui diferenciais reais

CritérioEvidência atual no RullstVeredicto e limite
Suíte Rust coesaDezesseis pacotes publicáveis cobrem runtime, ORM, Auth, Security, AI, Capital, Mail, Connect, Messaging, IoT, Nexus, Studio, macros e CLI.Vantagem de escopo diante de bibliotecas HTTP e microframeworks. Mais superfície também aumenta a obrigação de manutenção e testes.
Segurança como contrato de composiçãoProductionPreset expõe a ordem canônica; CSRF, headers, WAF/RASP, DLP, Login Jail, ownership/RBAC e telemetria possuem implementações delimitadas.Diferencial comprovado frente a um simples conjunto de middlewares. Ainda falta consolidar completamente a fronteira entre Core e Security e obter auditoria externa.
Ferramentas privilegiadas fail-closedNexus exige política de autenticação; Studio requer uma capability local explícita, recusa release builds e valida loopback por request.Diferencial comprovado de segurança por padrão. Não equivale a provar que todo handler está livre de vulnerabilidades.
Auditoria de acesso geradoO scanner AST do CLI exige classificação adjacente para rotas parametrizadas e cobre acessos público, proprietário, role e admin.Diferencial comprovado de engenharia preventiva. É uma barreira adicional, não um substituto para autorização em runtime e pentest.
IA first-party com limitesOpenAI, Gemini, Anthropic, DeepSeek, Ollama e endpoints OpenAI-compatible compartilham guardrails, mascaramento de PII, capacidades tipadas e fixtures offline determinísticas.Vantagem de escopo sobre as suítes oficiais comparadas. Capacidades variam por modelo; faltam avaliações públicas, políticas completas de tool calling e prova contra ataques adaptativos.
Desenvolvimento offline determinísticoCredenciais vazias ou mock_* selecionam caminhos sem rede em AI, Connect, Mail e Capital, com erros tipados para capacidades ausentes.Diferencial comprovado de testabilidade. Mocks não validam contratos reais dos provedores.
Projetos gerados auditáveisSeis blueprints, matriz estrutural das 18 formas públicas da v12, projetos representativos e um gate que instala o CLI empacotado e compila offline cada blueprint verificam a saída distribuída.Diferencial comprovado na base atual. A matriz inteira ainda não é compilada e a evidência precisa ser repetida no SHA final da RC.
Web, administração e Edge no mesmo projetoA suíte combina backend web e uma fundação no_std com telemetria e verificação de manifesto OTA Ed25519.Vantagem de escopo, não OTA completo: download, flashing, bootloader, MQTT, HSM e PQC reais continuam fora do contrato implementado.
Evidência de release multi-crateOrdem topológica validada, preflight, empacotamento, SBOM e recuperação parcial estão documentados e parcialmente automatizados.Diferencial de governança em construção. Só vira prova de release quando os gates rodarem verdes no SHA limpo da tag.

Frameworks Rust

Axum

O Axum é uma biblioteca de routing e request handling modular integrada ao ecossistema Tokio, Hyper e Tower. O próprio Rullst usa essa base, portanto a relação é mais de plataforma sobre fundação que de concorrência direta.

O que Axum faz melhorVantagem atual do RullstVeredicto
API HTTP pequena, composição Tower direta, baixo acoplamento e escape hatch natural.Convenções, ORM, auth, stack de segurança, jobs, email, AI, geradores, Nexus e Studio já integrados.Não comparável diretamente. O Rullst deve preservar a interoperabilidade Axum, não tentar esconder ou substituir sua fundação.

Actix Web

O Actix Web se define como um framework web poderoso e pragmático; sua documentação cobre composição de middleware e serviços de produção.

O que Actix faz melhorVantagem atual do RullstVeredicto
Núcleo web consolidado, foco claro e histórico maior de uso real.Suíte oficial mais ampla, security/AI/admin/CLI integrados e convenções de aplicação.Vantagem de escopo, não prova de maior desempenho ou maturidade.

Loco

O Loco é o concorrente Rust mais próximo: uma proposta “Rails on Rust” com modelos, controllers, jobs, mailers, autenticação e CLI. Seus geradores e o modelo uniforme de background jobs são referências importantes.

O que Loco faz melhor hojeVantagem atual do RullstVeredicto
História de produto mais concentrada, documentação de fluxo coesa, scaffolds CRUD/HTMX e workers com backends bem apresentados.Segurança dedicada, IA multi-provider com guardrails, Capital, Nexus/Studio, auditoria IDOR e fundação IoT fazem parte da suíte oficial.Concorrente direto. O Rullst possui maior amplitude; Loco é uma referência de foco e acabamento de DX.

Topcoat, do ecossistema Tokio

O Topcoat está no repositório oficial tokio-rs e propõe um framework full-stack modular: expressões reativas escritas em Rust são traduzidas para JavaScript, sem bundle WASM ou build separado de cliente, junto a componentes server-rendered assíncronos. O próprio README o classifica como early-stage e experimental, com breaking changes esperadas; o workspace consultado declara a versão 0.5.0 em seu Cargo.toml.

O que Topcoat faz melhor hojeVantagem atual do RullstVeredicto
Direção mais inovadora para UI reativa server-first, componentes e envio seletivo de comportamento ao cliente.Backend muito mais amplo: ORM, auth/security, AI, Capital, Mail, filas, Admin, Studio e geradores auditados.Projetos complementares e ainda em evolução. Topcoat lidera a experimentação de UI; Rullst lidera o escopo de plataforma backend. Não integrar na RC enquanto sua API for experimental.

Poem e Salvo

Poem e Salvo oferecem superfícies HTTP extensas. O Poem inclui middleware para CSRF, sessões, OpenTelemetry e outros casos; Salvo documenta HTTP/3, OpenAPI, rate limiting, SSE, WebSocket e WebTransport.

O que eles fazem melhor hojeVantagem atual do RullstVeredicto
Poem tem um catálogo HTTP/middleware concentrado; Salvo tem cobertura oficial mais ampla de protocolos e OpenAPI.Rullst integra domínio de aplicação, segurança defensiva, IA, administração e scaffolding em vez de se limitar ao HTTP.Vantagem de escopo do Rullst, mas Rullst atrás em OpenAPI completo e em protocolos como HTTP/3/WebTransport.

Leptos

O Leptos é um framework full-stack de UI reativa em Rust, com SSR/CSR, hydration, server functions e integrações Axum/Actix.

O que Leptos faz melhor hojeVantagem atual do RullstVeredicto
Componentes reativos, hydration e uma experiência full-stack centrada na UI.Modelo server-first simples com HTML/HTMX e uma suíte backend muito mais ampla.Não comparável diretamente. Interoperabilidade é mais valiosa que duplicar um runtime reativo completo.

Dioxus

O Dioxus 0.7 permite compartilhar Rust em aplicações web, desktop e mobile. A suíte full-stack oferece SSR, hydration, typed routing, hot reload e server functions compatíveis com Axum; o mobile é um alvo first-class baseado em WebView, com renderização WGPU ainda experimental.

O que Dioxus faz melhor hojeVantagem atual do RullstVeredicto
Componentes reativos e uma toolchain coerente para web, desktop, Android e iOS a partir do mesmo projeto.Backend com ORM, segurança, identidade, AI, billing, mail, jobs, Nexus e Studio sob políticas comuns.Dioxus está à frente na camada de aplicação cross-platform; Rullst está à frente no backend especializado. Uma integração oficial é mais realista e valiosa que recriar UI/mobile dentro do Core.

Frameworks de outras linguagens

Django

O Django 6.0 combina ORM, autenticação, migrations, formulários e um admin model-centric. Sua documentação de segurança cobre XSS, CSRF, SQL injection, clickjacking, CSP e segurança de deployment, mas também deixa explícito, por exemplo, que throttling de autenticação não é oferecido pelo núcleo.

Onde Django é referênciaVantagem específica do RullstVeredicto
Maturidade, documentação, ORM/admin, i18n, ecossistema e experiência de produção.Tipagem e ownership compilados, runtime defense first-party, ferramentas privilegiadas fail-closed, IA com guardrails e binário Rust.Rullst tem diferenciais de arquitetura, mas está atrás em maturidade e não pode alegar segurança global superior.

Ruby on Rails

O Rails 8.1 continua sendo uma referência de convenção e produtividade: Active Record, generators, Action Mailer, Hotwire, deploy e Active Job/Solid Queue formam uma experiência coerente. O próprio guia de segurança ressalta que nenhum framework torna uma aplicação segura por si só.

Onde Rails é referênciaVantagem específica do RullstVeredicto
Convenções lapidadas, velocidade para construir CRUD, ecossistema, material educacional e operação conhecida.Segurança de memória e concorrência do Rust, contratos explícitos, security/AI first-party e análise estática dos artefatos gerados.Rullst pode superar em garantias compiladas e controles específicos; Rails ainda é a meta de coesão e produtividade.

Spring Boot

O Spring Boot possui uma suíte enterprise extensa, starters, integração com o ecossistema Java e Actuator. Sua auto-configuration é deliberadamente orientada por dependências e condições do classpath.

Onde Spring Boot é referênciaVantagem específica do RullstVeredicto
Integrações enterprise, DI, mensageria, observabilidade, suporte comercial, tooling e grande base instalada.Contratos menores e mais explícitos, static dispatch nas rotas comuns, ownership Rust e menos comportamento decidido por scanning/configuração em runtime.Rullst tem vantagem de explícito e compacto, mas está muito atrás na plataforma enterprise. Spring AOT também reduz parte da diferença; não se deve caricaturá-lo como “apenas reflection”.

Laravel

O Laravel 13 oferece ORM, container, auth, policies, migrations, mail, notifications, events e queues, além de uma experiência de CLI e starter kits muito refinada.

Onde Laravel é referênciaVantagem específica do RullstVeredicto
Ergonomia, Artisan, Eloquent, filas, ecossistema SaaS, documentação e onboarding.Garantias compiladas do Rust, stack de defesa dedicada, AI/provider mocks first-party e auditoria de acesso dos blueprints.Rullst tem diferenciais de segurança e tipagem, mas Laravel é uma referência essencial de DX e ecossistema.

Gin

O Gin é um framework HTTP Go intencionalmente pequeno, com routing, middleware, binding/validation, rendering e recovery.

Onde Gin é referênciaVantagem específica do RullstVeredicto
Simplicidade, API concentrada e implantação Go conhecida.Plataforma full-stack oficial com ORM, segurança, IA, admin, jobs, pagamentos, email e geradores.Vantagem de escopo do Rullst, não vitória direta: usuários de Gin podem preferir justamente montar cada componente.

FastAPI

O FastAPI transforma type hints e modelos em validação, JSON Schema, OpenAPI e documentação Swagger/ReDoc, com um sistema de dependências bastante ergonômico.

Onde FastAPI é referênciaVantagem específica do RullstVeredicto
Contrato de API/documentação automática, validação declarativa, DI e onboarding para APIs.Binário e tipos Rust, stack full-stack mais ampla, controles de segurança operacionais e AI integrado.Rullst atrás em OpenAPI/SDK e ergonomia de contrato HTTP; essa é uma prioridade competitiva, não algo a esconder.

ASP.NET Core

O ASP.NET Core 10 reúne DI, configuração, logging, métricas, Minimal APIs, MVC, Blazor, SignalR, gRPC, auth e data protection em uma plataforma madura.

Onde ASP.NET Core é referênciaVantagem específica do RullstVeredicto
Tooling, diagnóstico, compatibilidade enterprise, protocolos, identidade e suporte de longo prazo.Modelo de ownership do Rust, APIs mais explícitas e possibilidade de uma distribuição nativa menor e altamente especializada.ASP.NET Core está à frente como plataforma geral. O Rullst deve competir por foco, segurança verificável e DX Rust, não por uma lista maior de features.

O que já pode ser afirmado publicamente

Afirmações sustentáveis:

  • “Rullst é uma suíte full-stack Rust construída sobre Axum, Tokio e Tower.”
  • “A suíte reúne primitivas first-party de segurança e IA com limites documentados.”
  • “Nexus e Studio adotam acesso fail-closed; os blueprints passam por auditoria estrutural de autorização.”
  • “Integrações externas possuem caminhos offline determinísticos para testes e desenvolvimento local.”
  • “NFS-e ao vivo, OTA completo, HSM/PQC reais e SIEM operacional completo são roadmap, não capacidades de produção atuais.”

Afirmações que ainda não devem ser usadas:

  • “o framework mais seguro”, sem auditoria independente e critério comparável;
  • “o framework Rust mais rápido”, sem benchmark público reproduzível;
  • “substitui Django, Rails, Spring ou Laravel em qualquer projeto”;
  • “SOC/SIEM autônomo completo”, enquanto entrega durável, ingestão externa, retenção, correlação, casos e operação não estiverem implementados;
  • “production-ready” como um único selo para toda a suíte.

Recomendações para a v12 RC

A RC não deve tentar alcançar toda a ambição do framework. Ela deve provar que o que existe é instalável, seguro por padrão, reproduzível e descrito com honestidade. O programa v12 também contém itens da estável e do período pós-RC; portanto, não é necessário marcar 100% daquele documento para publicar uma RC.

Bloqueadores reais

  1. Congelar features e escolher o SHA exato da candidata.
  2. Fazer o bump atômico para 12.0.0-rc.1, empacotar os 16 crates e testar consumidores usando somente os pacotes empacotados/crates.io.
  3. Rodar formato, Clippy e testes all-features no SHA; obter CI Linux, macOS e Windows verde no mesmo commit.
  4. Compilar projetos materializados representativos de todos os seis blueprints em CI e provar em release que Studio não é exposto e Nexus falha fechado.
  5. Cumprir o gate único de cobertura escolhido: bibliotecas do framework, patch coverage, Auth e Security em pelo menos 90%, sem caminho crítico em 0% e casos negativos do threat model cobertos.
  6. Publicar matriz de features, MSRV, políticas de SemVer/depreciação/suporte, guias de migração e changelog compatível com a implementação real.
  7. Executar auditoria de dependências/licenças, validar SBOM/proveniência e fazer uma revisão manual focada em Auth, Nexus, Studio, webhooks e configuração de produção.
  8. Registrar decisão GO/NO-GO, responsáveis, SHA e evidências antes do primeiro upload irreversível.

Melhorias competitivas permitidas antes da RC

Somente mudanças pequenas e redutoras de risco:

  • corrigir diagnósticos e mensagens de configuração dos projetos gerados;
  • completar testes negativos já previstos no threat model;
  • garantir estados “indisponível” honestos em Studio/Nexus;
  • congelar e documentar o schema de SecurityEvent, sem prometer SIEM durável;
  • corrigir exemplos, links, feature flags e instalação offline/reproduzível.

Topcoat, novos protocolos, conectores enterprise, uma nova UI reativa, NFS-e ao vivo, hardware IoT e grandes reformulações de Auth não pertencem à RC. Cada uma aumenta a superfície justamente quando a prioridade deve ser estabilizar.

Prioridades aditivas para v13

A próxima linha de funcionalidades será a v13. Ela deve privilegiar primeiro melhorias aditivas e compatíveis, antes de usar a nova major para contratos que realmente precisem mudar:

  1. OpenAPI e SDK: gerar contrato tipado a partir das rotas/extractors, validar breaking changes e produzir clientes testados. FastAPI, Salvo e o ecossistema Spring mostram por que isso é decisivo.
  2. Matriz gerada completa: transformar as 270 verificações estruturais em compilação incremental/particionada, com smoke E2E por blueprint.
  3. Auth operacional: concluir política JWT de aplicação, rotação/revogação, sessão por dispositivo e TOTP sem inventar criptografia própria.
  4. Rate limit e idempotência distribuídos: contratos Redis/SQL com testes de concorrência, falha e múltiplas instâncias.
  5. Security Event Sink: API aditiva, redaction, backpressure, retry, dead-letter/spool e primeiro transporte padrão (preferencialmente OTLP ou HTTP assinado). Adaptadores CEF/syslog podem ser crates opcionais.
  6. AI evals e tool safety: matriz por provider, datasets versionados, autorização por ferramenta, limites de saída e proteção SSRF/egress.
  7. Storage remoto: adapters opcionais S3/R2 com testes de compatibilidade, multipart, retries e isolamento tenant-aware.
  8. DX mensurável: medir tempo até primeiro CRUD seguro, qualidade dos erros, rebuild incremental e número de passos até deploy local.

Mudanças arquiteturais para v13

A v13 pode receber mudanças arquiteturais que não cabem numa minor:

  1. Consolidar o contrato de segurança entre Core e rullst-security, mantendo uma única ordem e tipos compartilhados sem ciclo de dependências.
  2. Definir uma arquitetura de extensões first-party e comunitárias com compatibilidade, ownership, manutenção e conformance suites.
  3. Evoluir a fundação delimitada de rullst-messaging com outbox/relay e adapters Kafka, RabbitMQ, NATS/JetStream e Redis Streams, em vez de misturar mensageria com OAuth em Connect.
  4. Evoluir SOC/SIEM como produto verificável: ingestão multi-source, regras, correlação, retenção, casos, evidências, RBAC, auditoria e intervenção humana. Conectores de fornecedores devem permanecer opcionais.
  5. Concluir WebAuthn por biblioteca auditada e testes de conformidade; avaliar auditoria externa formal da superfície Auth/Security.
  6. Criar programas separados para NFS-e homologada e IoT/OTA real, com mantenedores, hardware/ambientes, fault injection e critérios jurídicos ou operacionais próprios.
  7. Investigar interoperabilidade com Topcoat quando sua API estabilizar. O Rullst deve escolher conscientemente entre integração e uma camada reativa própria, em vez de copiar uma experiência experimental durante a v12.
  8. Publicar benchmarks independentes e reproduzíveis de workloads completos: CRUD/ORM, auth, templates, filas, WebSocket e middleware de segurança.

Critérios para se tornar o principal framework Rust

“Melhor” precisa ser convertido em resultados observáveis:

DimensãoEvidência necessária
CorreçãoZero P0 conhecido, CI multi-OS consistente, testes negativos e recuperação de falhas.
SegurançaThreat models versionados, disclosure responsável, dependências governadas, auditoria externa e correções com SLA.
EstabilidadeSemVer previsível, MSRV, política de suporte, migrações e janela real de RC.
DXProjeto inicial e CRUD seguro rápidos, erros acionáveis, documentação verificável e escape hatch Axum/SQLx.
DesempenhoBenchmarks públicos com metodologia, percentis, consumo de memória e regressão em CI.
EcossistemaExtensões mantidas, conformance tests, exemplos reais e integração com serviços usados em produção.
AdoçãoAplicações independentes, feedback de mantenedores externos, contribuidores e casos públicos de operação.
OperaçãoTelemetria interoperável, runbooks, backup/restore, upgrades e incident response testados.

Critérios para se tornar o principal framework de todos

Competir também com Django, Rails, Spring Boot, Laravel, FastAPI e ASP.NET Core exige algo mais difícil que acumular features: o Rullst precisa oferecer um golden path completo e seguro, sem impedir que especialistas substituam cada camada. As ideias abaixo são realistas quando entregues por etapas; várias já existem nos roadmaps, mas algumas ainda estavam marcadas de forma mais otimista que a implementação.

Implementação sugeridaOrigem preservadaEntrega realista e critério de sucessoJanela
Contrato de API tipado e SDKsM5, M29 e M34 do roadmapUm schema canônico gera OpenAPI e clientes TypeScript/Dart/Swift; golden tests provam serialization e CI detecta breaking changes.v13
Perfil oficial Rullst + DioxusM14, M16 e visão OmniTemplate opcional para web/desktop/mobile, auth compartilhada, client gerado e um app Android E2E. Rullst permanece dono do backend; Dioxus, da UI.v13, primeiro experimental
Auth de referência completaM9 e roadmaps Auth/SecurityJWT com issuer/audience/rotação/revogação, sessões por dispositivo, TOTP/recovery e WebAuthn por biblioteca auditada/conformance suite.v13
Multi-tenancy e entitlements segurosM11 e M33Tenant derivado de identidade autenticada, filtros SQL verificáveis, gates server-side, auditoria e negativos cross-tenant em todos os blueprints SaaS.v13
Idempotência e limites distribuídosM10 e roadmap CapitalA quota de recursos do Capital já possui reserva SQL atômica/idempotente por tenant em quatro protocolos. Ainda faltam stores compartilhados uniformes, expiração/reconciliação e cobertura multi-instância para login, APIs e webhooks.Capital delimitado na v12; demais na v13
Observabilidade que explica o problemaM19, M35 e roadmap StudioOTLP interoperável, trace waterfall, profiling SQL/N+1, jobs/cache e estado indisponível honesto; nenhum dado inventado.v13
SOC/SIEM operacional, não cenográficoM12 e roadmap SecurityEventos versionados, redaction, spool/retry/dead-letter, ingestão e correlação, casos, retenção, RBAC e conectores opcionais testados.v13 em etapas
AI segura e avaliávelRoadmap AI e M19/M36/M37Evals versionados, capability matrix por provider, tool authorization, egress/SSRF guard, budgets, aprovação humana e rollback de mudanças.v13
Mensageria por contratoM15 e roadmap MessagingA crate separada já possui envelope, limites, idempotência, grupos, leases, retry/DLQ, broker determinístico e conformance suite; adapters Kafka/RabbitMQ/NATS/Redis Streams entram somente quando seus semantics forem comprovados.Fundação v12; remotos v13
Storage e media isoladosM17S3/R2 com assinatura oficial, multipart/retry, limites de path/pixels, mocks determinísticos e fuzzing de codecs em crate opcional.v13+
Extensões sustentáveisM13 e arquitetura de packagesManifesto, capability permissions, compatibilidade SemVer, ownership e testes; sandbox Wasm apenas após limites reais de CPU/memória/I/O.v13
Deploy e upgrades recuperáveisM26 e M27Health/readiness, migrations coordenadas, secrets, canary/rollback e runbook testados; “one click” descreve automação guiada, não disponibilidade garantida.v13
Performance demonstradaM2 e benchmarks do ORMRepositório público de workloads, hardware fixado, throughput, p50/p95/p99, memória e regressões; comparar aplicações completas, não uma função isolada.contínuo
Confiança externaPrograma v12 e governançaRC pública, auditoria independente, política de suporte, mantenedores externos, apps reais e divulgação coordenada de vulnerabilidades.começa na v12 RC

Três regras impedem essa ambição de destruir o projeto:

  1. O Core continua pequeno; capacidades grandes entram em crates/perfis opcionais com conformance suites.
  2. Nenhuma integração vira “implementada” apenas por compilar ou possuir um adapter nominal: precisa de teste de contrato e falha segura.
  3. NFS-e, hardware IoT, HSM/PQC e um SOC hospedado são programas próprios, com mantenedores e infraestrutura, mesmo quando usam Rullst como plataforma.

O caminho mais forte para o Rullst não é vencer uma competição de quantidade de features. É combinar a produtividade que Rails/Laravel/Loco ensinaram, a previsibilidade operacional de Spring/ASP.NET Core, a ergonomia de contrato do FastAPI, a experiência cross-platform do Dioxus, a composição de Axum/Tower e as garantias de Rust — sem anunciar como pronto o que ainda é visão.

Fontes externas consultadas

Somente documentação ou repositórios oficiais foram usados nesta fotografia:

Essas fontes e versões mudam. A comparação deve ser revalidada antes de virar material de lançamento e toda alegação de desempenho deve viver em um benchmark versionado, não nesta página.

Preservação da documentação anterior ao gpt.md

Snapshot histórico identificado: commit 96222fbd31bec3d20bc50db68c41bb85ca595779, de 24 de agosto de 2026. O gpt.md foi criado no commit seguinte, ecf3ecb. Nenhum texto anterior depende da memória de uma conversa para ser recuperado.

Este documento existe para garantir que a correção técnica não apague a visão original do Rullst. Ele também evita um problema igualmente grave: recolocar exemplos inseguros, URLs obsoletas ou capacidades inexistentes dentro de guias que um usuário pode copiar como instrução atual.

Política de preservação

  1. O snapshot acima é a cópia exata e imutável de toda a documentação anterior.
  2. O roadmap principal preserva as ambições como itens ativos e acrescenta Implementado, Parcial, Não implementado ou Não prometer, sempre com uma recomendação.
  3. Os roadmaps por crate preservam o detalhamento original. O quadro de auditoria no roadmap principal é a interpretação atual quando um checkbox histórico é mais amplo que a implementação.
  4. A especificação e os exemplos continuam operacionais: eles descrevem somente APIs e limites atuais. Tutoriais estão explicitamente fora desta reconstrução histórica e devem existir apenas na forma atual, copiável e segura.
  5. O changelog mantém a alegação antiga e coloca uma nota adjacente de escopo auditado na v12 em vez de apagá-la silenciosamente.

Ambições recuperadas e interpretação atual

Esta tabela cobre as famílias de capacidades extraordinárias presentes no snapshot. Os identificadores M* apontam para a classificação detalhada no roadmap.

Ambição original preservadaEstado atual e opiniãoDestino canônico
CLI completa, generators, make:resource, docs hub (mdBook) e SDK TypeScript[~] Parcial — vale concluir com matriz de projetos gerados e um schema de API canônico; inferência AST isolada não basta.M1, M4, M5 e M34
Recompilação sub-100 ms com mold/lld/Cranelift[~] Parcial — vale otimizar e publicar benchmarks por máquina; não vale garantir um tempo universal.M2
Zero lock-in, eject para Axum/Tokio e módulos totalmente opcionais[~] Parcial — escape hatches são valiosos; migração sem custo e opcionalidade universal não são garantias honestas.M3 e M32
Active Record, Repository, Turso/libSQL e réplicas SQLite transparentes[~] Parcial — os padrões SQLx existem e o perfil Turso-primary blank/API possui derive, CRUD/query, migrations/generators e contratos local/live. Paridade com relações/hooks/demais blueprints e replicação transparente continuam ausentes.M6 e M38
Edge/Wasm distribuído e upgrades autônomos[~] Parcial — o runtime portátil vale evoluir; atualização autônoma só com artefato assinado, aprovação e rollback.M7
Modelagem por intenção e índices de produção auto-otimizados[ ] Não implementado — vale como recomendação explicável e aprovada; DDL autônomo em produção não vale o risco.M8
Auth local, OAuth/OIDC, TOTP, passkeys e WebAuthn completo[~] Parcial — prioridade alta; exige conformance WebAuthn, recovery, revogação e política de sessão/JWT.M9
Nexus instantâneo, Omni/Tauri, billing e entitlements declarativos[~] Parcial — Nexus e billing têm fundações reais; Omni e gates completos precisam de contratos independentes.M11, M21 e M33
RASP/WAF, Vault, honeypots, HMAC audit, headers A+, Login Jail, DLP, fingerprinting, IDOR scanner e SOC[~] Parcial — os controles delimitados valem hardening contínuo; A+, zero leakage, zero latency, cobertura OWASP total e certificação não devem ser prometidos.M12 e capability ledger
Kani em 100% dos paths, fuzzing como imunidade a DoS e sanitizers como prova de ausência de races[!] Não prometer — as ferramentas são excelentes para alvos e execuções declarados; nenhuma prova segurança universal.Programa v12, seções 4, 7 e 9
SBOM como conformidade SOC 2/ISO/FedRAMP e “100% Rustls” como prova de segurança[~] Parcial — inventário e política de transporte valem manter; conformidade exige controles operacionais/auditoria, e Rustls não elimina todo risco.M12 e programa v12
PQC, KMS/HSM, eBPF, contenção no kernel, sandbox Wasm e heap guard pages[ ] Não implementado — vale somente por ameaça/protocolo concretos e com primitives auditadas; não criar criptografia própria.M13 e roadmap Security
HTMX zero-bundle, adapters Leptos/Dioxus, cinco engines frontend[~] Parcial — HTMX/SSR é real; adapters e engines precisam de compatibilidade e E2E, e “zero bundle” depende da opção escolhida.M14
Queues/cache/scheduler com Redis, RabbitMQ, Kafka, Streams, NATS e clouds[~] Parcial — Memory/SQLite/Redis têm fundações; adapters ausentes valem apenas com contrato compartilhado e testes reais.M15
Wasm islands e #[client_component] totalmente reativos[~] Parcial — vale concluir protocolo, serialização, hydration, empacotamento e browser E2E.M16
Realtime, S3/R2, media resizing e package registry[~] Parcial — realtime/local storage têm bases; object storage e registry são trabalho futuro modular.M17
LiveView completo e sem lógica cliente[~] Parcial — o loop WebSocket existe; auth, reconnect, backpressure, diffs e E2E ainda são necessários.M18
Radar “kernel-level”, Prometheus e traces distribuídos[~] Parcial — telemetria local/export existe; não é eBPF/kernel nem waterfall OTel distribuída completa.M19 e M35
Event streaming zero-copy e ledger imutável[ ] Não implementado — interessante após definir persistência, consistência, recuperação e verificação; HMAC chain local não é ledger distribuído.M20
Agentic DevOps, self-healing runtime e autofix autônomo[~] Parcial — recomendações e patches revisáveis são úteis; mutação autônoma exige preview, escopo, aprovação, testes e rollback.M22, M23 e M37
IoT completo: MQTT, Modbus/BLE reais, mesh, OTA, HSM/PQC e hardware verificado[~] Parcial — frames no_std e gate Ed25519 são reais; transporte, flash, boot, counters, hardware e certificação são programas separados.M24 e M25
Deploy realmente one-click/zero-downtime em PaaS/VPS[~] Parcial — scaffolding é útil; DNS, credenciais, migration, health e rollback continuam responsabilidades operacionais.M26
Kubernetes pronto para produção[x] Implementado no escopo de scaffold — manifests e probes existem; revisão e operação continuam com o usuário.M27
DI com custo zero[x] Fundação implementada — API typed existe; custo zero é hipótese de benchmark, não garantia.M28
OpenAPI/Scalar e SDKs automáticos completos[~] Parcial — UI e generators existem; fidelidade exige schemas tipados e testes de serialização.M29 e M34
gRPC/Tonic first-class[~] Parcial — generator inicial existe; falta crate suportada e matriz de conformidade do projeto gerado.M30
Aerospace, veículos autônomos, robótica e defesa[ ] Não implementado — visão extraordinária, mas não deve entrar no Core web; só vale como projeto safety-critical independente, com hardware, standards e governance.M31
AI SQL copilot e AI Admin capaz de mutar dados[ ] Não implementado no escopo autônomo — vale como read-only/preview com allowlists, parâmetros, limites, autorização e auditoria.M36
NFS-e nacional direta com XMLDSig, mTLS e custo zero[~] Live não implementado — a preparação local delimitada DPS/XSD/XMLDSig/mTLS existe, mas envelope/resposta oficial, idempotência, A1 real em produção restrita, revisão e homologação continuam externos; live falha fechado e “custo zero” não é promessa controlável.Capital roadmap e Maybe SaaS
Alipay RSA2, métodos uniformes e taxas fixas dos gateways[~] Parcial — adapters/mocks existem, mas método, preço e cobertura variam; Alipay live permanece desabilitado até interoperabilidade oficial.Capital roadmap e capability ledger
AI Firewall invulnerável, offline AI autônoma e classificação sem vazamento[~] Parcial — filtros, mocks e Ollama existem; heurísticas têm falsos positivos/negativos e tools sensíveis precisam de autorização externa ao modelo.AI/Security roadmaps e programa v12
Mail Radar, CSS inlining, AI dunning, inbound mail e deliverability universal[~] Parcial — transports, pipeline e fixtures são úteis; as expansões valem com contract suite e operação observável.Mail roadmap
Studio N+1 profiler, Cache/Redis browser e telemetry sempre “live”[~] Parcial — ferramentas reais mostram dados/unavailable; N+1 e cache browser permanecem backlog.Studio roadmap e capability ledger

Onde está cada documento original

Os links abaixo abrem o texto exato anterior ao gpt.md. Eles são referência histórica; para uso atual, prefira a documentação do branch principal.

Governança e visão

Livro e especificações

Crates e aplicação de referência

Regra para futuras correções

Ao descobrir uma alegação incorreta:

  • não apagar a ambição do roadmap;
  • não manter a alegação como fato numa spec ou tutorial copiável;
  • acrescentar o status, o limite, a recomendação e a evidência;
  • ligar a versão histórica quando a redação original tiver valor de registro;
  • atualizar o capability ledger e o programa de release quando o risco for transversal.

Assim o Rullst pode conservar sua imaginação sem pedir que usuários confundam uma visão excelente com uma garantia de produção.

Escopo excluído por decisão do mantenedor: tutoriais antigos não precisam ser republicados nem anotados. O histórico Git continua existindo, mas somente tutoriais atuais devem aparecer no livro.

Maybe SaaS: Incubating Products Built with Rullst

Status: strategy proposal, not an implementation claim. None of the products named in this document exists merely because it is described here. The capability ledger remains the evidence source for what the framework implements today.

Some of Rullst’s most ambitious ideas need more than another module in a web framework. They require a continuously operated service, official homologation, named hardware, vendor interoperability, incident response, or a dedicated security program. Those ideas can become separate products built with Rullst, while the framework remains a reusable and honest foundation.

This is not automatically an open-core strategy, and it does not require every advanced capability to become paid software. It is an incubation boundary: use the smallest delivery model that can provide credible evidence and operations.

The four possible delivery models

ModelUse it whenWhat stays in the Rullst framework repository
Framework crateThe capability is a reusable local library and does not require a vendor-operated control plane.Public traits, types, adapters, deterministic mocks, documentation, and contract tests.
Open-source reference applicationUsers need a deployable example or can reasonably self-host the whole capability.The stable client contract and an example; the application can have its own repository and release cycle.
Managed SaaS or private control planeThe value depends on durable shared state, continuously updated provider rules, tenant operations, monitoring, or 24/7 availability.A provider-neutral client/SDK, an explicit remote adapter, offline mocks, and a self-hosted escape hatch where practical.
Conformance or hardware programCorrectness depends on official test environments, physical devices, audited cryptography, interoperability matrices, or certification.Interfaces and test vectors. Passing named external suites is required before a production claim.

A capability can use more than one model. For example, an IoT platform can have an open device SDK, a managed control plane, and a physical hardware conformance lab.

Application built with Rullst
        |
        | typed client contract
        v
Framework adapter + deterministic offline mock
        |
        | explicit opt-in; never silent fallback
        v
Separately deployed product or customer-managed service
        |
        +-- durable state and tenant isolation
        +-- provider, fiscal, or device interoperability
        +-- monitoring, audit, support, and incident response
        +-- independent conformance evidence where required

The framework must remain useful without the managed product. Remote services must not become hidden requirements for routing, ORM, authentication, or local development. Live configuration must also fail closed instead of silently falling back to a mock.

Candidate incubation programs

The names below are working descriptions, not announced product names.

1. Fiscal Cloud for NFS-e

Best initial form: a dedicated fiscal program, followed by a managed SaaS and a self-hostable/private deployment if the operating model proves viable.

The current Rullst capability now includes a bounded local DPS 1.01 builder, checksum-pinned official XSD validation, PKCS#12 XMLDSig and mTLS client preparation, while live transmission remains disabled. A live product would be responsible for substantially more:

  • official schemas, municipality/national variations, rejection codes, and protocol updates;
  • PKCS#12 or delegated certificate custody, rotation, access control, and audit;
  • official request/response envelopes, retries, reconciliation, cancellation, substitution, and immutable evidence around the local crypto/schema core;
  • durable idempotency and a complete issuance state machine;
  • official homologation environments, operational monitoring, and specialized support.

rullst-capital should retain the typed fiscal contract, request/response models, offline preview, and an explicit remote adapter. The live fiscal engine should have its own lifecycle because protocol and legal maintenance must not be coupled to releases of the framework suite. It must not advertise legal or tax compliance without qualified review and current official evidence.

Why it is attractive: it solves a difficult Brazilian SaaS problem and would exercise queues, cryptography, observability, billing, multi-tenancy, and failure recovery in a real Rullst application.

Why it is risky: certification, certificate custody, protocol drift, and financially consequential failures make this much more than an HTTP adapter.

2. IoT Device and OTA Control Plane

Best initial form: open device SDK plus a hardware conformance program; managed SaaS only after selecting and testing named device families.

Rullst currently provides no_std telemetry/frame foundations and an Ed25519-signed OTA manifest verification gate. A credible platform would add:

  • device registry, provisioning, fleet inventory, and tenant isolation;
  • MQTT/CoAP/LoRaWAN interoperability against named brokers and devices;
  • durable anti-rollback counters, staged rollouts, download resumption, boot slots, health confirmation, and rollback orchestration;
  • signed firmware provenance, software bills of materials, revocation, and incident response;
  • physical test rigs covering power loss, partial writes, clock faults, poor networks, and recovery paths.

The device-side verifier should remain small, auditable, and independent of the SaaS. A customer must not brick a fleet merely because the control plane is temporarily unavailable.

3. Key Management, HSM, and Post-Quantum Interoperability

Best initial form: adapter crates and a conformance program, not a new cryptographic SaaS first.

HSM and post-quantum work is worthwhile only for named protocols, devices, and threat models. The safe path is to integrate audited libraries and standards, then validate them against PKCS#11 services, cloud KMS/HSM providers, secure elements, and published test vectors. Rullst must not invent cryptographic primitives or use a generic “quantum-safe” badge.

A managed orchestration service could eventually handle key policy, rotation, attestation inventory, and audit workflows. Actual private-key operations should remain inside the selected HSM/KMS boundary whenever possible. On-premise and bring-your-own-key modes are likely requirements, not optional extras.

4. Enterprise Identity Gateway

Best initial form: a separate identity service or private control plane, with a dedicated crate boundary if reusable protocols are added.

rullst-connect is currently focused on OAuth2/OIDC and social identity. Enterprise SAML, SCIM provisioning, organization/domain discovery, directory synchronization, delegated administration, policy evaluation, and tenant audit are a coherent product of their own. They should not be represented as a few extra provider flags in the OAuth crate.

The framework can expose authentication/session integration and typed identity events. The gateway can operate federation metadata, provisioning jobs, enterprise connectors, and tenant-specific policy. A private deployment option is important for organizations that cannot send identity metadata to a shared SaaS.

5. Security Operations Control Plane

Best initial form: a self-hostable reference service before any managed SaaS claim.

Rullst Security, Radar, Studio, and Nexus already provide useful local security and telemetry foundations. A separate control plane could aggregate signed events from multiple applications, manage distributed rate-limit policy, deliver alerts to SIEM destinations, retain audit evidence, and coordinate incident response.

This product would need explicit data-retention controls, regional placement, tenant isolation, end-to-end authentication, redaction, bounded ingestion, and an unavailable state that never weakens an application’s local defenses. Claims such as autonomous blocking, zero leakage, or complete OWASP coverage would still require narrow definitions and independent evidence.

6. Messaging and Remote Storage

Best initial form: provider-neutral crates and conformance suites before a managed service.

Kafka, RabbitMQ, Redis Streams, S3, and R2 are integrations with mature services. Rullst now has a coherent bounded rullst-messaging contract for idempotency, groups, leases, retry and dead letters, with a deterministic process-local broker, canonical envelope/trace contracts and opt-in durable local SQLite state with explicit encrypted content. Remote adapters and the remote-storage boundary still need provider-specific conformance, backpressure, multipart, path/key and deterministic mock evidence. Building another broker or object store as a SaaS would add little value until Rullst applications reveal a concrete unmet need.

What should remain inside the framework

Even when a separate product exists, Rullst should own:

  • stable, provider-neutral traits and serializable contract types;
  • feature-gated client adapters with bounded timeouts and typed errors;
  • deterministic offline mocks selected only by explicit mock configuration;
  • local development and self-hosted paths where they are practical;
  • contract tests that every official or third-party provider must pass;
  • telemetry hooks that expose availability without fabricating success;
  • migration and escape-hatch documentation that prevents vendor lock-in.

The separate product should own production tenancy, billing, durable global state, operational dashboards, on-call response, external-provider drift, certification evidence, and service-specific data governance.

Promotion gates

An incubated idea must not be promoted from roadmap to implemented because a demo or one happy-path adapter exists. Before a public production claim, require the applicable gates:

  1. a named owner and a documented support/release policy;
  2. a threat model, abuse cases, tenant-isolation tests, and data classification;
  3. a versioned API contract, deterministic mock, and self-host/exit strategy;
  4. end-to-end tests against named providers, devices, or official environments;
  5. migrations, backup/restore, disaster recovery, observability, and SLOs;
  6. bounded retries, idempotency, reconciliation, and failure-injection tests;
  7. security review and, for cryptographic/fiscal work, independent specialist validation;
  8. a private beta with real operators before general-availability language;
  9. evidence linked from the capability ledger and the relevant roadmap.

Passing framework unit tests is necessary, but it cannot substitute for these external and operational gates.

Suggested order of investment

  1. Shared foundations: distributed idempotency/rate limiting, canonical Security contracts, complete scaffold validation, and reproducible releases.
  2. One reference product: build and operate a narrowly scoped Rullst service to validate deployment, multi-tenancy, telemetry, upgrades, and support.
  3. Fiscal discovery or enterprise identity: choose one based on access to qualified domain partners and real design customers; do not start both as production programs simultaneously.
  4. IoT control plane: proceed only with named hardware, a physical lab, and a partner willing to test real update failures.
  5. HSM/PQC: integrate audited standards for concrete use cases after the threat model exists; keep speculative branding out of production claims.

The first reference product does not need to be the most spectacular one. Its purpose is to prove that applications built with Rullst can be upgraded, observed, secured, and operated for long periods. That operational evidence is more valuable to the framework than another unchecked feature list.

Strategic conclusion

Yes, a separate SaaS built entirely with Rullst can be an excellent direction. It creates a demanding real customer of the framework and can finance deeper engineering. The separation is successful only if it protects both sides:

  • the framework stays open, portable, provider-neutral, and truthful;
  • the product can evolve at the cadence required by its domain;
  • neither side claims external certification or production readiness without evidence;
  • every managed convenience has an explicit contract and a credible exit path.

The goal is not to move unfinished features behind a hosted API. It is to give the ideas that require operations, certification, or hardware the independent engineering program they need to become real.

Hardening status — rastreabilidade da avaliação gpt.md

Estado pontual atualizado em 2026-09-04, com a candidata limpa 27e81152 como último lote integral auditado. Este documento mapeia as recomendações da avaliação técnica; não é certificado, pentest, homologação de provedor nem declaração geral de production-readiness. A auditoria de release posterior substitui este retrato como fonte do estado atual; listas de lacunas e recibos abaixo devem ser lidos como histórico datado, não como checklist da candidata pós-auditoria.

Como ler este relatório

As classificações abaixo têm significado estrito:

  • Corrigido: o defeito concreto descrito em gpt.md tem implementação e regressão local correspondente. Isso não amplia o escopo da capacidade.
  • Mitigado / fail-closed: o comportamento inseguro ou enganoso não retorna sucesso, mas a integração real continua indisponível ou depende de uma infraestrutura externa ainda não implementada.
  • Parcial: parte material do item foi entregue, mas o critério completo ainda não é demonstrável.
  • Pendente: não foi encontrada mitigação suficiente no estado inspecionado.

A SST continua sendo o contrato normativo. O capability ledger registra o que é implementado, experimental, deliberadamente não prometido ou visão futura; o ROADMAP canônico, incluído na versão do livro, prioriza o trabalho restante. Este relatório não substitui nem promove itens do ledger/ROADMAP: ele apenas liga cada achado histórico à evidência atual.

Resumo executivo

GrupoCorrigidoMitigado / fail-closedParcialPendente
P0-01..096300
P1-01..1916120
P2-01..2117220
Total dos 49 achados39640

“Sem pendência” nessa tabela significa que cada achado possui ao menos uma correção ou contenção; não significa que as capacidades fail-closed foram implementadas. NFS-e real, Alipay RSA2, storage remoto, replicação genérica, transports MQTT/CoAP, HSM/PQC e os testes reais multi-instância/failover do rate limiting distribuído continuam fora do contrato entregue, como registra o capability ledger. O adapter Redis opcional é uma fundação implementada, não evidência de uma topologia de produção validada.

Achados críticos — P0

IDEstadoEvidência atual e limite
P0-01 — NFS-e/XMLDSig inválidaMitigado localmente / live fail-closedfiscal/signer.rs agora rejeita envelopes/IDs/certificados inválidos e produz XMLDSig envelopada RSA-SHA256/C14N inclusiva 1.0 a partir de PKCS#12; a assinatura é verificada localmente e o XML assinado passa o XSD oficial checksum-pinned quando o pacote é fornecido. fiscal/protocol.rs gera o envelope GZip/Base64 determinístico e só classifica HTTP 201 como autorização após vincular ambiente, DPS, chave, infNFSe e XMLDSig; rejeições e input malformado/tampered/bomb permanecem distintos. fiscal/schema.rs limita arquivos, hashes, resolução e tamanho; fiscal/client.rs constrói mTLS rustls limitado, mas não transmite. Regressões positivas/negativas cobrem signer, protocolo, XSD, credencial mock e contratos de ambiente. Política ICP-Brasil/emissor, idempotência, A1 real na produção restrita, revisão independente e homologação SEFIN continuam abertos; os modos reais falham fechados.
P0-02 — FieldEncryptor irreversível/falsoCorrigidovault.rs usa envelope versionado AES-256-GCM, nonce aleatório, AAD e key-id/keyring, rejeitando chave de tamanho incorreto e envelope legado ambíguo. Round-trip, adulteração e chave incorreta são cobertos em mfa_sri_vault_test.rs; vault/tests.rs também prova rejeição de nonce/ciphertext malformado, campos extras, key-id excessivo e plaintext autenticado não UTF-8.
P0-03 — stubs criptográficos IoT expostos como garantiasMitigado / fail-closedota.rs verifica assinatura Ed25519 de manifesto canônico, hash/tamanho/target e anti-rollback antes de permitir commit; APIs legadas falham com erro tipado. Os antigos bytes HSM/PQC e o formatador de valor MQTT foram renomeados para Simulated* e isolados por experimental-simulators. Os novos mqtt.rs e coap.rs são somente encoders de pacotes bounded, sem rede ou criptografia. Vetores e negativas ficam nos testes IoT. Transporte, hardware e ML-KEM reais continuam roadmap.
P0-04 — Nexus destrutivo aberto por padrãoCorrigidonexus/mod.rs exige política de autenticação em try_build, devolve MissingAuthenticationPolicy sem ela e instala RequireRoleLayer<NexusPrincipal>; construtores legados são deny-all/deprecados. access.rs exige credenciais fortes, peer rate limit e fronteira TLS verificável, e NexusAuthPolicy::protect_router aplica a mesma fronteira a rotas administrativas da aplicação. Testes: access/tests.rs e casos de build fail-closed no próprio módulo.
P0-05 — CORS reflect-origin geradoCorrigidocors_middleware.rs.template gera allowlist tipada, rejeita wildcard/origens inválidas, não habilita credenciais por padrão e limita métodos. Regressões do gerador estão em cors_jwt.rs. A etapa separada de aviso a consumidores antigos é tratada na Fase 0 abaixo.
P0-06 — storage confirma persistência inexistente/path traversalCorrigido / upload parcialstorage.rs valida componentes, confina o caminho ao diretório canônico, barra escape por symlink e retorna Unsupported para S3/R2/resize. TenantStorage liga o namespace local a TenantContext autenticado e prova não-interferência da mesma chave. uploads.rs acrescenta admissão/quarentena storage-agnostic com limite/tipo/nome/tenant, assinatura versus MIME/extensão, digest e scanner fail-closed; multipart, S3/R2, parsing profundo e scanner real continuam abertos. Testes incluem os negativos de persistência/path, TM-TENANT-04 e TM-ACADEMY-09.
P0-07 — ambiente de produção fail-open/inconsistenteCorrigidoconfig.rs define Environment e precedência validada RULLST_ENVAPP_ENV → config; valores inválidos são erros. server/builder.rs carrega dotenv sem mutar o ambiente global, propaga falhas de config/DB e instala apply_security_baseline em staging/produção. Essa composição injeta a configuração da aplicação antes de headers/CSP nonce, CORS exato, WAF, CSRF e PII opcional e possui regressão HTTP integrada; browser/proxy/TLS reais permanecem gate de deploy. Regressões: environment_resolution_has_one_precedence_and_validated_aliases e production_baseline_composes_nonce_cors_csrf_and_headers.
P0-08 — integridade de providers/webhooksMitigado / fail-closedproviders/mod.rs rejeita segredo real vazio e separa mock explícito; verificadores com timestamp aplicam freshness. webhook.rs adiciona replay limitado, modo produção que rejeita mock e falha fechada no teto sem expulsar prova ativa. O opt-in webhook/sql.rs compartilha claims bounded de payload/ID semântico em SQLite/PostgreSQL/MySQL/MariaDB, com perfil imutável, restart, concorrência, drift, capacity e transação relacional do caller testados nos quatro protocolos. alipay.rs retorna UnsupportedOperation no modo RSA2 real, em vez de simular HMAC como RSA. O middleware reivindica antes do handler e não é exactly-once; cross-system effects, reconciliação e RSA2 real permanecem abertos.
P0-09 — release fora da ordem topológicaCorrigido.github/workflows/release.yml verifica fmt/Clippy/testes, empacota todos os crates antes do primeiro publish e publica pela DAG real, incluindo Connect/IoT e ORM antes de Core; valida tag/versões, checksums e artefatos. É evidência estrutural do workflow, não evidência de que uma execução de release passou.

Achados altos — P1

IDEstadoEvidência atual e limite
P1-01 — invariantes WebAuthn incompletasParcialpasskey/service.rs e ceremony.rs validam tipo da cerimônia, origin/rpIdHash, cross-origin, UP/UV, COSE ES256, coordenadas X/Y, raw-id, contador monotônico e challenges compartilhados, bounded, one-time e com TTL. Os negativos, inclusive coordenadas ausentes, estão em invariant_tests.rs. O escopo de attestation permanece deliberadamente estreito (none/ES256) e ainda não há prova por suíte normativa ou biblioteca WebAuthn auditada; o ledger mantém “Full normative WebAuthn server” como parcial.
P1-02 — sessão/APP_KEY/rehash/legadoCorrigidoauth.rs valida força/placeholder do segredo nos ambientes seguros, compara algoritmo/versão/parâmetros Argon2 para rehash e só aceita envelope de sessão versionado e expirável. app_key_resolution.rs isola processos para provar precedência, config inválida, ambiente não Unicode, persistência privada da chave dev e cookie Secure em produção; outras regressões cobrem sessão sem versão, needs_rehash e expiração. O JWT de aplicação continua explicitamente responsabilidade do scaffold, conforme o ledger.
P1-03 — DLP/PII corrompem respostasCorrigidodlp.rs e pii.rs só reescrevem texto bufferizável suportado; binário, streaming, encoding e overflow têm bypass/erro explícito sem UTF-8 lossy ou falso sucesso, e headers de representação são recalculados/removidos. Regressões: invalid_utf8_and_incomplete_pem_are_not_corrupted, test_dlp_layer_middleware e core/security/tests.rs.
P1-04 — CSRF incompatível com webhooksMitigado / fail-closedcsrf.rs reconhece métodos seguros e apenas isenções POST exatas configuradas; o scaffold monta a rota de webhook assinada separadamente em saas/routes.rs. Capital exige assinatura/freshness/replay. Testes: safe_http_methods_do_not_require_a_token e only_exact_configured_post_webhook_path_is_exempt. A idempotência cross-instance ainda requer backend compartilhado.
P1-05 — WAF/RASP não inspecionam bodyCorrigidocore/security/waf.rs e security/rasp.rs inspecionam bodies text/JSON/form limitados, decodificam strings JSON, falham fechados em tamanho/encoding inválido e reconstroem a request. Regressões: waf_inspects_and_preserves_bounded_request_bodies, json_body_inspection_decodes_escaped_strings e middleware_fails_closed_for_uninspectable_textual_bodies em rasp/tests.rs. Continuam heurísticas de defesa em profundidade, não substitutos para parser, bind ou autorização.
P1-06 — CSP não corresponde a nonces/A+Corrigidocore/security/headers.rs e security/headers.rs emitem baseline sem unsafe-inline/unsafe-eval e expõem CspNonce ao renderer; policy estática continua presente quando nonce dinâmico está desligado. Testes verificam que nonce do header e extension coincidem em core/security/tests.rs e security_tests.rs. A documentação não deve prometer nota universal de scanner.
P1-07 — middleware de webhook perde request parts/bodyCorrigidowebhook.rs usa into_parts/from_parts, mantém método, URI, versão, headers, extensions e bytes originais, além do evento verificado. Regressão: reconstructed_request_preserves_parts_extensions_and_body.
P1-08 — inicialização ORM global parcial/panickingCorrigidopool.rs prepara primária/réplicas localmente com try_join_all e só então publica um único OnceLock<OrmState>; getters normais retornam Result. atomic_init_test.rs prova que falha de réplica não publica estado parcial.
P1-09 — janitor limpa clones do Login GuardCorrigidologin_guard.rs limpa diretamente os DashMap ativos via cleanup_if_due, aplica TTL e max_identities, sem task dona de clones divergentes. Testes test_login_guard_tarpit_and_jail, test_login_guard_global_and_expired_jail e concorrência em concurrency_tests.rs.
P1-10 — distributed rate limit no-op/IP forjávelCorrigidorate_limit/redis.rs, sob redis-rate-limit, usa script Lua atômico, namespace validado, TTL e chave de cliente SHA-256; empty/mock_* é modo local explícito e require_distributed() falha fechado. O selector legado sem configuração continua DistributedBackendUnsupported. resilience.rs ignora forwarded headers no extractor padrão e permite política explícita de proxy confiável. Além das regressões offline, redis_rate_limit_live.rs prova que duas instâncias independentes compartilham o mesmo budget num Redis real; CI e release sobem uma imagem fixada por digest. Failover/cluster e composição HTTP continuam gates separados.
P1-11 — XSS/autorização visual no NexusCorrigido no boundary definidoai_chat.rs sanitiza saída externa; crud/views.rs escapa/encodeia IDs sem JS inline; os handlers aplicam hidden/readonly, limitam batch e devolvem erros genéricos sem expor a falha SQL. O router inteiro exige papel admin. Modelos com coluna tenant explícita aplicam o TenantContext confiável em toda leitura/mutação/batch e with_required_audit só confirma a mutação junto com um registro minimizado na mesma transação. A regressão HTTP tenant_audit.rs prova isolamento, input protegido, audit e rollback. Identidade/membership, models globais, ownership dentro do tenant e custom routes continuam do host; a tabela de audit não é append-only nem storage separado.
P1-12 — Studio inventa métricas/rota/XSS/env leakCorrigidoradar.rs representa probes ausentes como Option e mede CPU real por deltas em Linux e Windows; radar_visualizer.rs atualiza os KPIs via /api/radar sem converter ausência em sucesso; security_radar.rs usa Unavailable, rota coerente e DOM textContent/replaceChildren; env_viewer.rs usa política de redaction; feature_flags.rs encodeia nomes e usa binds corretos. A construção exige LocalStudioAccess, que só existe como opção efetiva em debug e nega peer remoto ou ConnectInfo ausente. Regressões cobrem as páginas, a sonda Windows e a fronteira HTTP (200/403/403). Um Studio compartilhado com autenticação embutida continua explicitamente não implementado.
P1-13 — regressões concretas dos geradoresParcialFlags, IDs estáveis, path/package, Island/Resource, Auth, Billing e Docs SSG têm correções/testes em cargo-rullst/src. scaffold_contracts.rs valida paths, Rust e Cargo.toml nas 18 formas públicas da v12, materializa os seis blueprints e aplica o scanner IDOR; os seletores prerelease de três padrões ORM e cinco frontends foram removidos porque não possuíam paridade entre blueprints. generated_saas_check.rs executa checks Cargo reais sobre oito casos que cobrem os seis blueprints, o perfil Active Record + SSR html!/HTMX, API, banco, hot reload e um build release. generated_lms_modules_check.rs acrescenta três perfis destacados, inclusive assessment sem gamificação/outbox. O caso LMS aplica suas migrations em SQLite e prova as fronteiras HTTP autenticadas de autoria, publicação, rollback editorial, tarefa/rubrica/submissão/feedback/correção, conclusão/certificado e do ciclo grant/revoke; a regressão cobre revisão/pin imutável, rollback como nova revisão com autorização/replay/conflito/auditoria e preservação de pins antigos/novos, tarefa owner-only com prazo/tentativas, avaliação por papel limitada aos critérios persistidos, correção administrativa append-only com before/after/effective grade e negativos de replay/conflito/pontuação impossível, conclusão por ruleset fixado com incompletude/cross-user/replay/verificação sem PII/revogação auditada, papéis educacionais duráveis com expiração/revogação e grants privilegiados separados, política fail-closed de liberação/expiração/pré-requisito, progresso monotônico/auditado, quiz autoritativo com tempo/ordem persistidos e projeção atômica em ScoreEvent/leaderboard/outbox, exercícios owner-only single-choice, matching e typed com tentativa durável, binding transacional da configuração, pares/texto delimitados e replay exato/conflitante, além de agenda rullst-box-v1 durável no score com replay sem avanço e fila owner/school/enrollment-scoped, worker supervisionado, scheduler de publicação supervisionado com contenção/ativação/replay, APIs owner-only de notificação, correções, backoff, recuperação de lease e rejeição de token obsoleto. O gate .github/test-packaged-distribution.sh instalou o CLI do .crate, gerou os seis blueprints sem paths do monorepo e os compilou fora dele em 27e81152. Ainda faltam a repetição no SHA da RC, fmt/smoke mais amplo e ambientes de contrato para comandos externos.
P1-14 — guardrails/mocks/DeepSeek/structured outputCorrigidoguardrails.rs integra o estágio ao client de alto nível e providers repetem a proteção em chamadas diretas; deepseek.rs existe; empty/mock_* é offline determinístico em chat/vision/embeddings. structured.rs diferencia JSON parseável de schema nativo e retorna UnsupportedCapability quando o provider não o garante. guardrails_pipeline_test.rs percorre providers/capacidades e prova determinismo sem endpoint. Schema nativo em todos os LLMs continua visão, não claim.
P1-15 — constructors/OIDC/JWKS de ConnectCorrigidoConstructors gerados são fallible e aceitam impl Into<String> em macros.rs; credenciais mock/empty não podem ser redirecionadas a endpoint real. oidc/discovery.rs valida URL/host/issuer/endpoints, e o client HTTP desabilita redirects. provider/jwks.rs implementa TTL, refresh por kid desconhecido e stale limitado. Negativos/rotação estão em providers/oidc/tests.rs e provider/tests.rs.
P1-16 — invariantes de Mail/tenant/trackingCorrigidopipeline.rs, facade.rs e worker.rs centralizam CRLF, deliverability, links e tenant; o facade persiste scheduling delimitado pela Queue e o worker recusa claim antecipado antes de consumir o timestamp. resolver.rs liga diretamente o TenantContext autenticado do Core ao driver in-process, valida o registro e falha fechado se o lock estiver indisponível, com regressão de não interferência entre dois contextos; error.rs tipa falha permanente/transiente/rate-limit, limita/redige respostas HTTP e failover.rs suprime fallback para configuração, validação, HTTP 4xx não-429 e SMTP permanente, mantendo decisão estruturada sem body no tracing; action URLs exigem HTTP(S), host e ausência de credenciais; drivers/mock.rs seleciona offline deterministicamente; tracking.rs exige segredo forte, HMAC constant-time, TTL e replay store limitado. Os sete scaffolds, inclusive fiscal com proveniência explícita e dunning D+1/D+3/D+7, passam o contrato materializado em mail_scaffold_cli.rs; demais regressões estão nos testes de tracking, pipeline, scheduling de queue e integração Mail.
P1-17 — panics nos caminhos apontadosCorrigidoOs caminhos citados foram convertidos a erros/fallbacks: client Wasm e server em rullst-core/src, cache global e getters ORM, expansão de macros e Auth/Island/Omni gerados. .github/workflows/zero-panics.yml cobre bibliotecas, rullst-macros, CLI/binários, Wasm e testes de expansão; a auditoria AST genérica rejeita unwrap/expect/panic!/todo!/unimplemented! em literais de código runtime dos geradores. O escopo não inclui dependências, OOM ou falhas do host.
P1-18 — tenant escolhido pelo clienteCorrigido no boundary definidotenant_guard.rs deriva seleção de TenantMembership/TenantContext confiável e ignora headers como autoridade; multitenant.rs trata header/query/subdomínio apenas como seletor sujeito a membership, devolvendo 403 sem vínculo. RbacGuard carrega tenant validado e exige match exato sem bypass de admin; TenantStorage, TenantCache, TenantRealtime e TenantPresence oferecem namespaces locais ligados ao mesmo contexto. Nexus agora permite que um model registre uma coluna text tenant: todas as rotas CRUD/search/edit/batch usam o mesmo contexto confiável, create injeta o valor e ausência de contexto falha fechada. O LMS gerado persiste escolas/memberships/coortes/entitlements, resolve a escola no middleware e filtra mutações/leaderboard; outbox, automação derivada e notificações preservam school_id, o leaderboard integra cache local tenant-scoped e notificações novas podem ser projetadas para uma assinatura realtime tenant/user autenticada. Os testes negam seleção arbitrária/ambígua, regra estrangeira, Nexus cross-tenant, vazamento de notificação do mesmo usuário, admin cross-school e colisões locais da mesma chave/canal/sala de presença. Isso não cobre anexos/mídia, demais caches, models Nexus globais, custom routes, autorização ampla de salas, transporte distribuído, storage remoto, busca, métricas, exports, cache distribuído ou bancos reais.
P1-19 — bypass CSWSH por prefixo de hostCorrigidocswsh.rs normaliza e compara esquema/host/porta exatos, com origin ausente fechado por padrão. Testes deceptive_localhost_prefixes_are_rejected e middleware_rejects_a_deceptive_localhost_origin cobrem localhost.evil.

O teto local posterior de Mail complementa P1-16 com três wrappers opt-in: inspection.rs falha antes do transporte em assinaturas conhecidas incompatíveis, conteúdo ativo e indisponibilidade do scanner; suppression oferece store bounded process-local ou SQLite compartilhado-local com replay e quotas transacionais; e observability.rs omite destinatário, assunto, corpo e filenames. As provas exatas entram em TM-MAIL-01 a TM-MAIL-03. Isso não equivale a antivírus/CDR, autenticação de webhook do provider, replicação multi-host, operação de telemetria ou inbox delivery.

Achados médios — P2

IDEstadoEvidência atual e limite
P2-01 — audit chain ambígua/sem continuidadeCorrigidoaudit/chain.rs usa serialização domain-separated e length-prefixed, chave forte, sequência atômica e commit somente após o logger; valida continuidade. Testes: length_prefixing_prevents_delimiter_collisions, weak_keys_are_rejected e logger_failure_does_not_create_a_sequence_gap. Persistência durável continua responsabilidade de um sink.
P2-02 — telemetria finge HMAC/IP/tempoCorrigidotelemetry.rs força verified_hmac=false para evento local, não inventa loopback para IP inválido, emite RFC3339 absoluto e não duplica o total de prompts inspecionados ao registrar um bloqueio. O DurableSiemSpool preserva o formato compatível unsigned e valida versão/comprimento/SHA-256/JSON. O opt-in AuthenticatedSiemSpool somente retorna verified_hmac=true depois de verificar a cadeia HMAC inteira, chave nomeada/rotação e payload exato; isso autentica o frame local, não a verdade semântica do produtor. nexus/security.rs continua mostrando eventos comuns locais como não assinados e mantém a audit chain em Unavailable até existir uma fonte verificadora. Testes cobrem timestamps/proveniência/contagem, restart, quota, concorrência, symlink, forgery, chave errada/ausente, reorder, remoção interna e corrupção, além de event_integrity_badge_never_promotes_unsigned_events.
P2-03 — honeypot confia XFF/substrings/bans eternosCorrigidohoney/middleware.rs usa peer ConnectInfo, paths exatos, TTL e limites de cardinalidade. Testes: traps_use_exact_paths_and_bans_expire, ban_cardinality_is_bounded_and_invalid_ips_are_ignored e middleware_ignores_forwarded_identity_and_uses_socket_peer.
P2-04 — TrafficShield::new faz spawn/pode panicCorrigidoresilience.rs separa construção de start, retorna erro fora de runtime e possui ownership/shutdown/drop que aborta monitores. Regressões incluem construção sem runtime, shutdown compartilhado e dropping_final_shield_aborts_monitor_tasks.
P2-05 — scheduler sobrepõe jobs sem limiteCorrigidoscheduler.rs serializa ticks por job, impõe timeout, converte panic em erro/política de falha e oferece shutdown/drop. Testes cobrem start fallible, timeout/panic, não sobreposição e shutdown_aborts_current_handler.
P2-06 — queue spawn ilimitado/estado preso/JSON nullCorrigidoqueue/worker.rs limita concorrência, timeout e shutdown, contém panic, torna transições observáveis e recupera jobs stalled; queue/sqlite.rs falha JSON inválido e, com queue/redis.rs, persiste due times sem claim antecipado. Regressões em worker_tests.rs, queue/tests.rs e no contrato Redis live queue_scheduling_live.rs.
P2-07 — unload de dylib em uso/null para Box::from_rawCorrigido / limitadoserver/hotswap.rs retém handles para evitar unload com request em voo, serializa trocas, exige token efêmero comparado em tempo constante e limita o processo a 64 bibliotecas carregadas antes de exigir reinício; dylib_loader.rs converte o retorno a NonNull antes de Box::from_raw e documenta o contrato FFI. O cliente do navegador é local/offline e o build falho preserva o router anterior. O hot reload continua uma fronteira unsafe dev-only baseada em ABI Rust, sem promessa de ABI estável, migração arbitrária de estado ou garantia geral de segurança de memória.
P2-08 — env::set_var dentro do runtimeCorrigidoserver/builder.rs lê dotenv em mapa local e resolve configuração sem mutar o ambiente global. Ocorrências restantes de set_var estão em fixtures/testes controlados.
P2-09 — replicação de DB simuladaMitigado / fail-closeddb.rs retorna ReplicationError::Unsupported quando há sync_url, em vez de logar sucesso fictício; test_replication_manager_start fixa esse contrato. Replicação real/vendor-specific não foi implementada.
P2-10 — placeholders SQL por replace textualCorrigidopool/placeholders.rs possui lexer consciente de strings, identificadores, comentários, dollar quotes, parâmetros existentes e operadores JSON. Testes preserves_quoted_text_comments_and_dollar_quotes e preserves_json_operators_and_continues_existing_parameters protegem os casos citados. Não é um parser SQL completo para todo dialeto futuro.
P2-11 — identificador aceita hífenCorrigidoschema/validation.rs restringe componentes a alfanumérico/underscore e no máximo um separador de qualificação; hífen e formas ambíguas são rejeitados pelos testes do módulo.
P2-12 — auditoria ORM perde diff/segredo aninhadoCorrigidoaudit/diff.rs calcula diferenças recursivas, mascara objetos/arrays aninhados, preserva primitivos/arrays e registra sentinela para JSON inválido. Testes nested_secrets_are_redacted_in_objects_and_arrays e invalid_and_non_object_json_changes_are_not_dropped.
P2-13 — TOTP abreviado/URI com escape HTMLCorrigidomfa.rs exige seis dígitos ASCII, compara de forma constante e usa percent-encoding no URI. recovery_codes.rs acrescenta verificadores subject-bound salted/HMAC e consumo único explícito; persistência atômica continua da aplicação. Testes: totp_requires_exactly_six_ascii_digits, test_otpauth_uri_builder e codes_are_subject_bound_single_use_and_do_not_store_plaintext.
P2-14 — Auth gerado bloqueia Tokio/timing/busca linearCorrigidoauth/controllers.rs gera lookup indexado, hash/verify via spawn_blocking, dummy hash para usuário ausente e não emite calls panicking. Regressão: generated_auth_is_async_query_bound_and_panic_free; o modelo gerado também cria índice/constraint de email.
P2-15 — JWT gerado incompleto/versão divergenteCorrigidocors_jwt.rs injeta dependência idempotente do workspace, exige segredo forte e valida claims iss, aud, sub, iat e exp por configuração tipada. Regressões: generated_jwt_validates_secret_issuer_and_audience e teste de dependências atuais/idempotentes.
P2-16 — macro pública #[route] incompletaMitigado / fail-closedrullst-macros/src/lib.rs mantém somente marcador de compatibilidade deprecado: atributo vazio preserva a função e atributo que fingiria registrar rota produz erro orientando routes!. route_compat.rs fixa a compatibilidade. Não existe registro funcional por #[route]; remoção em major futura ainda é trabalho de contrato.
P2-17 — sinais semver incompletosParcialVários erros/configs centrais agora usam #[non_exhaustive], APIs antigas têm #[deprecated], e .github/workflows/semver.yml enumera todos os pacotes públicos publicados. A aplicação não é uniforme em toda a superfície pública; structs/enums históricos e política de transição ainda exigem inventário por crate.
P2-18 — ergonomia/panic de constructors inconsistenteParcialConnect, AI, Capital, Mail, IoT e os caminhos identificados em Core/ORM adotaram impl Into<String> e/ou construção fallible; Column, JoinClause, RawExpression, secrets, cache e queues também aceitam strings owned, e joins inválidos registram erro em vez de produzir SQL inseguro. A superfície histórica completa ainda precisa de inventário SemVer e migração gradual de builders/adapters restantes.
P2-19 — mutants como dependência runtime do ORMCorrigidorullst-orm/Cargo.toml moveu mutants para [dev-dependencies]; cfg(mutants) é declarado ao lint sem carregar tooling em consumidores.
P2-20 — compliance gerado imprime PASS incondicionalCorrigidoaudit_compliance.rs modela NoFindings, Findings, Generated, Observed, NotChecked e Error, descreve o limite da evidência e rejeita linguagem de certificação. Teste: report_never_fabricates_compliance_passes.
P2-21 — Basic Auth Nexus sem rate limit/user constant-time/TLSCorrigidoaccess.rs compara username e senha em tempo constante, exige marcador TLS confiável e aplica limiter por peer. Regressões em access/tests.rs: basic_credentials_require_both_exact_values, basic_auth_requires_verified_tls e basic_auth_locks_peer_after_bounded_failures.

Roadmap recomendado do §15 — Fases 0 a 4

Esta seção avalia o critério agregado de cada passo do §15, não reclassifica os achados individuais.

Fase 0 — contenção imediata

PassoEstadoEvidência/pendência
0.1 Conter Fiscal, IoT crypto/MQTT, Vault, S3/R2 e AlipayMitigado / fail-closedVault e OTA foram implementados; NFS-e/Alipay/remote storage falham fechados; simuladores IoT são explícitos. As integrações reais permanecem no capability ledger/ROADMAP.
0.2 Nexus fechado; remover credenciais geradasCorrigidoNexus::try_build exige policy, legacy é deny-all e blueprints não incluem admin/password; ver P0-04.
0.3 Corrigir CORS e avisar projetos já scaffoldadosCorrigidoO template e seus testes foram corrigidos (P0-05), e o cors-scaffold-security-advisory.md fornece detecção, correção e validação para projetos já scaffoldados.
0.4 Corrigir traversal do StorageCorrigidoValidação, canonicalização e teste de symlink em storage.rs; ver P0-06.
0.5 Unificar ambiente/fail-closed no startupCorrigidoEnvironment único, precedência e secure defaults testados; ver P0-07.
0.6 Segredo obrigatório em webhook realMitigado / fail-closedSegredos vazios/mock não entram no modo real; assinatura/freshness/replay existem. Falta store de idempotência cross-instance; ver P0-08/P1-04.
0.7 Corrigir e bloquear releaseCorrigidoWorkflow tag-only com preflight/package-all/DAG/checksum/attestation; ver P0-09. Nenhuma execução é inferida da leitura do YAML.
0.8 Alinhar README/SST/AUDIT/complianceCorrigidoREADME.md, spec.md, AUDIT.md, rullst-connect/AUDIT.md e SECURITY_COMPLIANCE.md distinguem implementação, mock, fail-closed e roadmap. O ledger é a matriz canônica de claims.

Fase 1 — segurança e confiabilidade do kernel

PassoEstadoEvidência/pendência
1.1 Enum de ambiente e precedênciaCorrigidoP0-07.
1.2 DB init atômica/fallible; getters normais sem panicCorrigidoP1-08.
1.3 APP_KEY e sessão legacyCorrigidoP1-02.
1.4 WebAuthn auditável/conformeParcialInvariantes listadas foram corrigidas e têm negativos, mas falta suíte normativa/biblioteca auditada; P1-01.
1.5 DLP/PII por content-type/stream/headerCorrigidoP1-03.
1.6 CSRF separado de webhook + freshness/idempotênciaMitigado / fail-closedComposição, freshness e replay local corrigidos; idempotência compartilhada permanece; P1-04.
1.7 Login Guard, rate, proxy, tenant e CSWSHCorrigido no boundary definidoOs bypasses/limites locais foram corrigidos e rullst-security/redis-rate-limit oferece contador Redis atômico opt-in, com fallback mock explícito, startup fail-closed e teste de duas instâncias contra Redis real. Failover/cluster e composição da identidade/origem no HTTP continuam gates de integração. P1-09/P1-10/P1-18/P1-19.
1.8 Zero-panic em produção e código geradoCorrigido no escopo declaradoOs caminhos enumerados foram corrigidos; o gate inclui bibliotecas, macros, CLI/binários, Wasm e auditoria genérica dos templates runtime (P1-17). Não é uma promessa impossível sobre dependências, OOM ou falhas do host.

Fase 2 — integridade de produto e scaffolding

PassoEstadoEvidência/pendência
2.1 Harness de todos os comandos + fmt/check/smokeParcialHá matriz estrutural das 18 formas públicas da v12, materialização dos seis blueprints, parse de templates e inventário de todos os comandos; projetos representativos passam cargo check real. fmt/check/smoke de toda combinação aplicável e comandos externos em ambientes de contrato continuam pendentes; P1-13.
2.2 Flags, nomes, paths, IDs, Auth, Billing e Docs SSGCorrigidoOs bugs concretos têm regressões em cargo-rullst/src e os dois projetos gerados compilam no harness focado.
2.3 Nexus server-side RBAC/ownership/field policyCorrigido no boundary definidoRole layer e field policy são server-side; batch/errors/escaping foram corrigidos; o tenant scope é opt-in por model e cobre todas as rotas built-in; audit de mutação pode ser exigido na mesma transação. P0-04/P1-11/P1-18. Identidade/membership, models globais, custom routes e storage de audit imutável continuam da aplicação hospedeira.
2.4 Studio: rotas, escaping, redaction e métricasCorrigidoP1-12.
2.5 Mocks offline AI/Mail/Connect sem fail-open realCorrigidoP1-14/P1-15/P1-16 e suites offline focadas.

Fase 3 — arquitetura e contrato

Decisão/açãoEstadoEvidência/pendência
Escolher Estratégia A ou BCorrigidoA SST e o capability ledger adotam explicitamente o resultado prático da Estratégia B: Connect é OAuth/OIDC; messaging, NFS-e real, transports MQTT/CoAP, HSM/PQC e outros providers permanecem roadmap/fail-closed.
Desacoplar Core de ORMCorrigido no boundary definidorullst-core/Cargo.toml é runtime-only por default; orm e queue-sqlite são bridges opt-in independentes, com boundaries em CI. A dependência opcional é integração explícita, não acoplamento do Core mínimo.
Consolidar SecurityParcialClaims e composição foram alinhados, e Core/Security reutilizam o mesmo CspNonce, evitando CSPs conflitantes. Ainda há ownership/headers/WAF/PII no Core e headers/RASP/DLP no Security. Uma dependência direta Core → Security criaria ciclo; o caminho seguro é um trait de stack no Core, implementação no crate dedicado e deprecation gradual das duplicações.
Completar umbrellaCorrigido no contrato atualrullst/Cargo.toml possui features/reexports explícitos para Security, IoT, Connect, SMTP e boundaries mínimos testados. Capacidades ausentes continuam roadmap, sem reexport fictício.
Padronizar semver/builders/Into<String>ParcialP2-17/P2-18.

Fase 4 — engenharia de release

PassoEstadoEvidência/pendência
4.1 Tríade AGENTS como gate --all-featuresCorrigidoci.yml e release.yml executam fmt, Clippy workspace/all-targets/all-features com -D warnings e testes workspace/all-features. A tríade normativa passou localmente em 27e81152 em 2026-09-04; uma execução de tag continua evidência separada.
4.2 Features strict DB isoladasCorrigido no workflowO job strict-database-features de ci.yml compila e executa CRUD específico em strict-postgres, strict-mysql e strict-sqlite, cada um somente com sua feature; boundary mínimo Core/umbrella também é exercitado.
4.3 Unsafe/WASM/Kani/Miri/mutation honestosCorrigido no contrato automatizadounsafe-policy.yml e wasm-matrix.yml são bloqueantes; Kani, Miri, mutants e udeps são explicitamente informativos nos YAMLs e em WORKFLOWS.md.
4.4 Cobrir 40 fuzz targets ou documentar tiersCorrigido no workflowHá 40 arquivos em */fuzz/fuzz_targets; o inventário compartilhado .github/fuzz-targets.json alimenta a campanha e a manutenção de corpus, e um validador bloqueante o compara com todos os manifests/fontes. WORKFLOWS.md registra o limite: configuração não equivale a uma campanha executada com sucesso.
4.5 Package-all antes do primeiro publishCorrigidorelease.yml usa cargo package --workspace ... --all-features --locked no job verify, antes do job publish; P0-09.
4.6 SBOM/audit/compliance por tag + digest/assinaturaCorrigido no workflowA release tag-only agrega metadata/Cargo.lock, Cargo Audit com exceções governadas, SBOM CycloneDX, relatório de evidência limitado, policy/advisory ledger, checksums e contexto tag/commit. O CLI recebe os mesmos IDs por --audit-ignore, valida sua gramática e registra NO FINDINGS OUTSIDE EXCEPTIONS, enquanto o workflow mantém owner/expiry e igualdade entre ledger, Deny e chamadas. Os .crate e o bundle são atestados e anexados. Isso não é certificação nem prova de uma execução verde ainda não observada.
4.7 Alinhar RC/changelog/tag/crates/release notesParcialOs 16 manifests publicáveis e requisitos internos estão sincronizados em 12.0.0-rc.1, e o preflight rejeita tag ou grafo divergente. CHANGELOG.md marca corretamente a RC como Unreleased; 27e81152 é uma candidata limpa validada, mas ainda não é o commit aprovado/tagueado e crates.io/release notes permanecem sem evidência até a publicação.

Evidência de testes e limites da validação

Há três tipos de evidência neste documento:

  1. Evidência estática local: arquivos de implementação e testes nomeados nas tabelas foram inspecionados no worktree atual.
  2. Execuções focadas registradas durante o hardening: Connect, AI, Mail, CSWSH/Security, tenant/Core, macros ORM, cache global, CSP compartilhado, scaffolding e strict-SQLite tiveram regressões focadas. Antes da simplificação da superfície v12, a matriz estrutural validou 270 combinações internas; sete projetos representativos cobrindo os seis blueprints passaram checks Cargo offline, inclusive um em release.
  3. Tríade local final reexecutada em 27e81152 em 2026-09-04, com Rust/Cargo 1.98.1:
cargo test --workspace --all-features                                PASS
cargo clippy --workspace --all-features -- -D warnings               PASS
cargo fmt --all -- --check                                           PASS
cargo +1.96.0 check --workspace --all-features                       PASS
.github/check-feature-boundaries.sh                                  PASS
.github/check-threat-model-release-minimum.sh                        PASS
.github/check-ai-evals.sh                                            PASS
.github/check-crates-ownership.sh                                    PASS
git diff --check                                                     PASS

O teste workspace/all-features foi executado sobre o worktree depois da auditoria dos exemplos e incluiu os doc-tests. Nove dos dez doctests antes marcados ignore agora compilam; o único restante está justificado na crate proc-macro e tem cobertura equivalente na facade. O teste de driver de banco ignorado sob a combinação artificial --all-features continua exigindo as matrizes exclusivas por driver. A dependência do Swagger UI foi posteriormente alterada para o bundle vendorizado, removendo o download GitHub/DNS durante builds limpos.

Os testes de integração que abrem loopback foram executados fora do sandbox restritivo; o restante permaneceu local. A dependência transitiva proc-macro-error2 2.0.1 citada na execução histórica foi posteriormente removida junto com a dependência Leptos não utilizada de Connect.

As matrizes CRUD exclusivas strict-sqlite, strict-postgres e strict-mysql de rullst-orm passaram localmente e na CI hospedada; PostgreSQL e MySQL usam serviços isolados. O candidato 27e81152 também passou a auditoria dos 16 pacotes e o consumidor externo, mas não houve upload. Campanhas manuais de fuzz, Kani, Miri, mutation e DAST, a pipeline de tag e homologações externas continuam evidências separadas. Arquivos YAML não constituem, por si sós, resultado.

Lacunas técnicas remanescentes

Em ordem de risco/impacto, o código e a governança ainda precisam de:

  1. backend compartilhado e atômico para idempotência de webhooks/tracking e testes Redis reais multi-instância/failover para o rate limiter disponível;
  2. suíte normativa/biblioteca auditada para WebAuthn, além dos negativos atuais;
  3. matriz de geração cobrindo todos os comandos/blueprints com fmt/check/smoke;
  4. conclusão da arquitetura Security da Fase 3: um contrato de stack no Core, implementação canônica no crate dedicado e deprecation das duplicações;
  5. inventário semver e migração uniforme de constructors/builders fallible;
  6. revisão específica da fronteira FFI/ABI do hot reload dev-only, incluindo testes apropriados de ciclo de vida; retenção de dylibs corrige o unload inseguro, mas não transforma ABI Rust dinâmica em contrato estável;
  7. atualizar ou substituir a cadeia opcional Leptos que ainda traz proc-macro-error2 2.0.1, antes que o lint futuro E0365 vire erro do Rust;
  8. execução verde da pipeline de tag no commit/release exato, alinhando versão, changelog, tag, crates.io e release notes;
  9. somente quando houver mantenedor, ambiente de interoperabilidade e suite de contrato: NFS-e homologada, Alipay RSA2, storage remoto, replicação específica, messaging, MQTT, HSM/PQC e ciclo real de flash/boot. Até lá, manter Unsupported/experimental é o comportamento seguro.

Security architecture and boundaries

Vision preserved: unfinished enterprise controls and absolute former claims remain visible, with a recommendation for each, in the capability ledger.

rullst-security contains composable controls for HTTP applications. The crate does not install every control automatically and does not replace secure domain logic, a trusted reverse proxy, operating-system hardening, or independent security testing.

Defense in depth

flowchart LR
    Peer[Trusted peer identity] --> Edge[Rate limit and honeypots]
    Edge --> Request[CSRF, CSWSH and bounded RASP/WAF]
    Request --> Identity[Authentication, MFA and RBAC]
    Identity --> App[Application and parameterized data access]
    App --> Response[Secure headers and supported DLP filtering]
    App --> Audit[Tamper-evident audit records]

Each arrow is an application integration point. If a layer is not mounted, its counter and API being present in the crate do not protect traffic.

Canonical production preset

ProductionPreset::middleware_order() is the v12 machine-readable ordering contract. From the outermost inbound boundary to the handler, the order is:

  1. trusted-proxy policy;
  2. request-body limit;
  3. request ID;
  4. tracing;
  5. secure headers;
  6. explicit CORS allowlist;
  7. bounded WAF/RASP;
  8. CSRF;
  9. session validation;
  10. authentication;
  11. tenant membership resolution;
  12. role, permission and object-ownership authorization;
  13. identity/direct-peer rate limit;
  14. application handler.

Tower response flow unwinds in reverse, so the secure-header layer still observes and protects responses returned by the inner guards and handler. Server mounts the framework-owned staging/production baseline. Session, authentication, tenant membership and authorization are deliberately application-owned: the framework cannot infer those policies without creating an insecure universal default. Generated applications must mount those layers in the declared slots, and protected parameterized routes must still perform an object-level ownership check.

The direct socket peer is the default network identity. A deployment must not accept Forwarded or X-Forwarded-For until its exact proxy hops are configured and tested. Body limits must be outer to any middleware that buffers content. Webhook routes may bypass browser CSRF only by exact path and only when their provider signature middleware is mandatory.

Academy production-boundary diagnostic

ProductionPreset::academy() layers twelve Academy-specific integration requirements over the canonical middleware order. Missing observations become NOT_EVALUATED, duplicate observations are rejected, and validation succeeds only when every requirement has one explicit PASS. The contract covers identity, school membership, entitlements, object authorization, tenant isolation, assessments, score events, durable automation/audit, content safety, privacy and distributed abuse controls. It cannot inspect or certify a deployed topology by itself.

cargo rullst academy:doctor emits the normalized contract in text or JSON and returns a failing exit status until all requirements pass. An evidence document uses the versioned rullst.academy-evidence.v1 schema:

{
  "schema_version": "rullst.academy-evidence.v1",
  "checks": [
    {
      "requirement": "authenticated_identity",
      "status": "PASS",
      "evidence": ["test:session_rejects_forged_identity"]
    }
  ]
}

Run it with cargo rullst academy:doctor --evidence academy-evidence.json --json. Omitted checks remain NOT_EVALUATED, and a PASS without a non-empty evidence reference is invalid. Evidence strings are caller declarations rather than independently verified proof. The output therefore always includes certification: false; satisfying this contract is not an audit, grade or production-readiness claim. Process-level CLI tests cover both the incomplete failing report and a complete declared-evidence report.

Parameterized-route access contract

cargo rullst audit --idor requires every recognized parameterized route to carry an adjacent // rullst-access: public|owner|role|admin — reason marker. public is accepted only for a recognized GET route. owner requires RbacGuard::authorize_owner_or_role; role requires a recognized role guard; and admin requires RequireRoleLayer or NexusAuthPolicy::protect_router. The latter lets application operational routes reuse the same fail-closed peer/credential and administrator boundary as Nexus.

The marker records intent and the scanner catches common omissions; neither is a proof of domain ownership. Protected object routes still need negative HTTP tests in which an authenticated subject requests another subject’s resource and receives a denial before data or side effects are exposed.

Control map

Risk areaAvailable primitivesBoundary
Broken access controlRbacGuard, ownership helpers, authenticated user context.The application must apply a check to every protected object and operation.
Cryptographic storageAES-256-GCM field encryption and zeroizing secret wrappers.Operators own key generation, storage, rotation, separation, and recovery.
InjectionSQLx binds, strict identifier validation, sanitizer and bounded RASP patterns.Heuristics are not a complete language parser; domain validation and parameterization remain mandatory.
MisconfigurationNonce-based CSP and a strict HTTP-header baseline.Proxies and page content change the deployed policy; no scanner grade is guaranteed.
Authentication abuseLogin jail, local limiter, optional atomic Redis limiter, timing helpers, TOTP, subject-bound recovery-code verifiers and WebAuthn integration.Real Redis topology/failover, durable transactional recovery consumption and UX, RP/origin configuration, trusted peer identity, and capacity planning remain application concerns.
Data integrityHMAC audit records plus an opt-in bounded HMAC-chained local SIEM journal with named rotation keys.Whole-tail rollback detection, independent checkpoints, multi-writer operation, key protection, retention and remote delivery require external storage and operations.
Data leakageText-aware DLP, PII masking, and log redaction helpers.Unsupported content types, encodings, streams, and oversize bodies follow explicit policy and must be tested.
AI input riskPrompt-injection heuristics and PII masking in the high-level AI client.No heuristic can prove a prompt safe or guarantee detection of every secret.

This is a control mapping, not a claim of complete OWASP Top 10 coverage.

Identity and network trust

Rate limiting, bans, honeypots, and audit attribution use the direct peer address unless a trusted-proxy policy explicitly accepts forwarded metadata. Never trust X-Forwarded-For, tenant headers, or role headers directly from an arbitrary client. Tenant membership and roles must come from an authenticated session or a cryptographically trusted internal gateway.

Request and response inspection

RASP/WAF rules are bounded to avoid uncontrolled CPU or memory work. They can reject known suspicious patterns in supported URI, header, and bounded body data, but application queries must still use binds and access control.

DLP modifies only supported textual responses whose body can be safely buffered within configured limits. Applications must test JSON, HTML, binary, compressed, SSE, streaming, and oversized responses so headers and bodies remain protocol-correct.

Audit chains

Audit records use an unambiguous canonical representation and a non-empty HMAC key. Verification must cover the ordered sequence, not just isolated records. A valid chain is tamper-evident; it cannot stop an attacker who can delete every record or steal the key. Store the log and key in separate protected systems.

Supply-chain and compliance evidence

Repository workflows can run dependency policy, RustSec, CodeQL, fuzzing, sanitizers, SemVer checks, SBOM generation, and release attestations. Their scope and blocking status are defined by the workflow files. Informational checks must not be described as formal gates.

Neither those workflows nor this crate certify an application for SOC 2, ISO 27001, PCI DSS, FedRAMP, or any OWASP level. See AUDIT.md and SECURITY_COMPLIANCE.md for reproduction and evidence requirements.

Deployment checklist

  • Mount middleware in a tested order and use exact webhook CSRF exemptions.
  • Configure secure session, webhook, audit, and encryption keys; reject weak or empty live credentials.
  • Define trusted proxies and use the direct peer as the default identity.
  • Apply owner/tenant/role checks server-side for every data operation.
  • Validate per-response CSP nonces against rendered pages.
  • Export telemetry and audit records to durable, access-controlled storage.
  • Run negative integration tests and an independent security review for the application’s actual configuration.

Rullst v12 threat models

Model version: TM-12.10 Applies to: the v12 release candidate source and generated applications Last source review: 2026-09-03 Status: maintainer baseline; application owners must extend it for their data, topology and providers. It is not a pentest or certification.

These models turn security ambitions into named abuse cases. A control is not effective merely because its type exists: it must be mounted in the deployed application and its negative case must pass. The security architecture defines the canonical HTTP boundary and the hardening status records repository evidence.

Method and common boundaries

The method is a lightweight STRIDE review: spoofing, tampering, repudiation, information disclosure, denial of service and elevation of privilege. Every model distinguishes untrusted network input, authenticated application identity, process-local state, shared durable state and third-party/hardware trust.

Process-local replay caches, counters, rate limits and audit buffers are useful single-instance controls. They are not distributed guarantees. Forwarded addresses are untrusted unless a separately reviewed proxy policy establishes the direct peer as trusted.

The machine-readable release minimum in .github/threat-model-release-minimum.json binds 55 distinct abuse-case IDs to 67 evidence rows and 59 exact test executions across thirteen crates. The gate rejects missing markers, missing tests and zero-test filters before executing Core, ORM, Auth, Nexus, Studio, tenant ownership, Capital, AI, Mail, IoT, generated-default and Academy negatives. Passing that bounded minimum does not imply that every case below is closed.

TM-CORE-1 — readiness and graceful request drain

Assets: service availability, readiness state, accepted requests and dependency-health observations.

Trust boundaries: orchestrator ↔ health endpoint; application dependency checks ↔ process-local readiness bits; new requests ↔ draining server.

Abuse caseRequired dispositionRepository evidence or remaining work
CORE-01 forged readiness or diagnostic disclosureReadiness must fail during startup, any required-component failure, corrupted component state and drain; keep liveness process-only and return counts rather than component labels or error details.The lifecycle-aware HTTP regression crosses startup → ready → draining, proves 503/200 transitions and verifies that the private component label is absent. Applications still own bounded dependency probes and determine which failures should gate traffic.
CORE-02 shutdown race admits new work or abandons accepted workChange admission monotonically to draining before the server’s graceful wait, reject later requests and bound the operator’s wait for already accepted requests.The concurrency regression holds one admitted request, begins drain, rejects a second request with 503, observes a typed timeout, releases the first and reaches zero. A real ephemeral-listener test proves the Server ready → shutdown → stopped lifecycle. Load-balancer propagation, client retry/idempotency, supervisor kill deadlines and multi-replica coordination remain deployment/application work.

Release-negative minimum: unready/draining response without private labels; request accepted before drain completes while a later request is denied.

TM-AUTH-1 — sessions, passwords, OAuth/OIDC and passkeys

Assets: password verifiers, session keys/tokens, OAuth state and nonce, passkey challenges/credentials, recovery paths and account identity.

Trust boundaries: browser ↔ application; application ↔ OAuth/OIDC provider; application ↔ session/challenge store; operator ↔ key store.

Abuse caseRequired dispositionRepository evidence or remaining work
AUTH-01 forged, truncated, expired or legacy sessionReject before constructing identity; authenticate encryption version, expiry and payload.Negative session parsing/encryption tests in rullst-auth.
AUTH-02 weak/missing application keyFail startup/configuration closed outside explicit deterministic mocks.Weak-key and malformed-key tests.
AUTH-03 password timing/CPU starvationUse Argon2id off the async executor, bound input and normalize failure behavior.spawn_blocking implementation and password tests; deployment capacity remains application work.
AUTH-04 OAuth login CSRF/code substitutionBind state, nonce, redirect URI, issuer, audience and one-time callback context.The optional Axum/tower-sessions path generates a ten-minute state/PKCE challenge plus OIDC nonce, stores verifier/nonce server-side, removes and immediately saves the one active challenge before validation, and tests sequential replay, mismatch, expiry, replacement and redaction. The store trait does not provide distributed compare-and-delete across already-loaded requests. Redirect registration, durable session/cookie/TLS policy, idempotent account linking/recovery and live-provider conformance remain RC application/deployment tests.
AUTH-05 passkey origin/RP/challenge confusionRequire exact RP/origin, ceremony type, single-use bounded challenge, UV/UP flags and supported attestation/key formats.Negative tests cover the bounded ES256/none scope; normative WebAuthn conformance remains open.
AUTH-06 session fixation/replay/revocation gapRotate on privilege change, expire server-side and provide device/session revocation.Versioned expiring cookie sessions exist. The optional JWT policy uses bounded expiry, kid rotation, token IDs and subject session versions; production rejects its process-local store. The SQLite profile adds shared-local JTI/session-version revocation and bounded passkey inventory/revocation with counter CAS. Cookie-session inventory, refresh flow, shared WebAuthn challenges, multi-host replication and application device ownership remain open.
AUTH-07 account enumeration/recovery takeoverNormalize public responses, rate limit attempts, protect recovery factors and audit changes.Login jail/timing helpers and subject-bound 80-bit recovery-code verifiers exist. Plaintext is returned only at enrollment and zeroized on drop; comparison is constant-time and consumption removes the verifier. Durable transactional consume, enrollment UX, step-up policy and full recovery workflow remain application-owned.

Release-negative minimum: malformed/expired session, wrong key, cross-origin passkey, replayed challenge, wrong RP, invalid callback state and repeated login failure.

TM-CONNECT-1 — durable local OAuth token generations

Assets: access and refresh tokens, provider identity, application-account binding, encryption keys and the latest accepted token generation.

Trust boundaries: application authorization ↔ account binding; provider response ↔ refresh coordinator; local processes ↔ shared SQLite state; and operator secret manager ↔ encrypted snapshot key.

Abuse caseRequired dispositionRepository evidence or remaining work
CONNECT-24 stale or competing local writer rolls a rotated token generation backStore only authenticated ciphertext plus non-secret key/generation metadata, serialize local writes and replace/delete only after an exact generation compare-and-swap. Reject configuration drift, quota exhaustion, malformed rows and unsafe existing file targets without exposing the database path or token material.The opt-in SQLite store uses BEGIN IMMEDIATE, an immutable persisted row ceiling and exact successor CAS. Two independent pools race generation one; exactly one succeeds, the loser fails with GenerationConflict, restart recovers the winner, and a stale delete is rejected. Separate negatives cover key mismatch, ciphertext authentication, metadata corruption, quota/configuration, plaintext absence and symlink targets. Provider-call leases, recovery after a losing remote refresh, key-manager operation, trusted directory permissions, backup, multi-host replication and live-provider conformance remain application/deployment work.

Release-negative minimum: two local writers cannot both replace one observed generation, and restart cannot turn a stale generation into the winner.

TM-NEXUS-1 — administrative CMS

Assets: administrative session, model records, bulk actions, AI assistant tools, audit evidence and security telemetry.

Abuse caseRequired dispositionRepository evidence or remaining work
NEXUS-01 anonymous/default accessFail closed without explicit production policy. Local shortcut requires debug build and verified loopback peer.Router policy and loopback tests.
NEXUS-02 IDOR/BOLA on CRUD/batch routesResolve subject/tenant, then authorize object ownership or role for every ID and bulk member.Models that explicitly register a text tenant column now scope every built-in read/mutation/batch predicate to a trusted TenantContext, inject it on create and fail closed without context. A real SQLite HTTP regression proves cross-tenant list/update/delete/batch denial and protected create input; pure SQL tests keep the all-feature release minimum portable. Global models, custom routes, identity/membership resolution, within-tenant object ownership and independent review remain host work.
NEXUS-03 stored/reflected XSSEscape dynamic HTML; make raw HTML explicit; enforce nonce CSP.Core proves renderer/header nonce identity. Generated LMS auth/catalog/course/player style elements consume the request nonce, remove remote shell dependencies/inline style attributes and the materialized catalog escapes script-shaped search text; browser validation and a route-by-route Nexus audit remain open.
NEXUS-04 AI assistant privilege escalationTreat model output as untrusted; allowlist typed tools and authorize each invocation as the human subject.Prompt filtering exists; tool approval/audit policy remains open.
NEXUS-05 destructive CSRFRequire CSRF on cookie-authenticated mutations and exact signed-webhook exemptions only.The exact Core baseline regression proves a production cookie write is denied without the matching double-submit value, accepted with it and retains the outer header/CORS policy on denial. Exact signed-webhook exemptions have separate unit negatives; a full Nexus browser/proxy flow remains open.
NEXUS-06 audit repudiationRecord actor, tenant, object, operation, outcome and correlation ID in durable separate storage.The opt-in required policy records the built-in authenticated actor, optional tenant, table/action, optional known key, count, committed outcome, bounded correlation ID, timestamp and format version in the same transaction; unavailable storage rolls the mutation back. A real SQLite regression covers commit and rollback, while schema tests cover all SQL dialects. It is same-database mutable evidence, not separate append-only or tamper-evident storage; denied attempts and automatically assigned create keys are not uniformly persisted. Host retention, backup, replication, immutable export and review remain open.

Trust boundaries are browser ↔ Nexus, Nexus ↔ application policy/database and Nexus ↔ LLM/provider.

TM-STUDIO-1 — local developer control room

Assets: environment/configuration, logs, traces, database browsing, job controls, prompts and source-error tooling.

Abuse caseRequired dispositionRepository evidence or remaining work
STUDIO-01 remote exposureCompile/mount shortcuts only in debug development and bind loopback. Production exposure needs application-owned auth and TLS.Generated startup and Studio boundary tests/docs.
STUDIO-02 secret leakageMask environment/log fields by key and value patterns; never render raw credentials.Environment viewer/redactor tests; novel formats remain residual risk.
STUDIO-03 SQL/table injectionParameterize values and strictly validate dynamic identifiers.Data-browser identifier/query-builder tests.
STUDIO-04 arbitrary source file accessRequire debug loopback, canonical allowlisted paths/extensions and bounded files.Traversal, sensitive-file, non-loopback and extension tests.
STUDIO-05 forged telemetryLabel local/unverified events accurately; never invent source IP, HMAC verification or provider status.Telemetry integrity tests.
STUDIO-06 destructive job/database actionRequire verified local policy and same-origin checks; remote production controls remain disabled until separately authenticated, authorized and reviewed.Data-browser writes additionally require an unforgeable middleware marker, inspected complete PK, typed binds, exactly one affected row and exact delete confirmation. Importing the raw router is denied by an exact release-negative test. Application tenant/RBAC, durable audit and rollback remain open.
STUDIO-07 forged or replayed remote span batchKeep ingestion push-only; bind each endpoint to one producer name/key, authenticate the exact body and bounded source/timestamp/nonce fields, consume valid nonces atomically, reject stale/future requests and validate the complete batch before storage.Wrong-source/key, HMAC tamper, stale timestamp, concurrent replay, schema/cardinality, deduplication and capacity regressions. TLS, key custody/rotation, clock synchronization and producer admission remain deployment work.
STUDIO-08 cache secret exposure or broad deletionReturn metadata only, replace logical keys with keyed opaque browser tokens, omit values, omit bulk flush and require the verified-local mutation marker for one-entry invalidation.Memory plus live Redis metadata contracts, HTML non-disclosure tests and forged/missing-marker mutation negatives. Application cache-key classification and operator policy remain external.

Trust boundaries are local browser ↔ loopback listener, authenticated trace producer ↔ push-only application endpoint, Studio ↔ telemetry/database/cache, and error console ↔ source filesystem.

TM-TENANT-1 — multi-tenant data access

Assets: membership, tenant-scoped records, billing/workspace IDs, cache keys, jobs, files, logs and exports.

Abuse caseRequired dispositionRepository evidence or remaining work
TENANT-01 trusted client headerNever accept tenant/role solely from an arbitrary header; bind to authenticated membership or a trusted gateway.Strict tenant guard rejects absent context; gateway policy is application-owned.
TENANT-02 object ID crosses tenantQuery with tenant/owner predicate and authorize the returned object server-side.UserContext carries a validated optional tenant and RbacGuard::authorize_tenant[_owner_or_role] requires an exact match that even admin cannot bypass. The materialized LMS test exercises bounded school-scoped IDs; general route coverage remains open.
TENANT-03 bulk/list/export leakageApply tenant predicate before pagination/count/export and validate every bulk member.Must be proven per generated/application route.
TENANT-04 cache/queue/storage collisionInclude canonical tenant identity in keys, jobs and storage roots; validate again in workers.Core’s TenantStorage, TenantCache, TenantRealtime and TenantPresence can only be constructed from a validated TenantContext, apply immutable tenant namespaces and have exact same-key/channel/presence-room non-interference tests. The cache wrapper exposes no cross-tenant flush; the realtime wrappers validate names and bound payloads. Academy’s database outbox/worker persists and validates school_id; its leaderboard cache is tenant-scoped, validates decoded scope and is invalidated after score, quiz and correction. Application room authorization, distributed realtime/liveness, search, metrics, exports, distributed cache/failover, remote bucket policy and broader Academy integration remain open.
TENANT-05 confused-deputy background workPersist actor/tenant/authorization intent and revalidate sensitive execution.Shared durable job authorization contract remains open.

Trust boundaries are identity ↔ membership, route ID ↔ object and application ↔ database/cache/queue/storage.

TM-ORM-1 — portable document recovery

Assets: document contents, portable identifiers, application/collection scope, snapshot encryption keys and the destination collection.

Trust boundaries: application writer ↔ repository inventory; source store ↔ application-owned snapshot storage; operator key custody ↔ snapshot opener; and snapshot contents ↔ destination repository.

Abuse caseRequired dispositionRepository evidence or remaining work
ORM-01 copied, tampered or cross-scope snapshot discloses or substitutes documentsEncrypt and authenticate the complete versioned payload with a fresh nonce, explicit rotation key ID, length-delimited application/collection binding and fixed decode limits; authenticate before JSON decoding.The exact negative changes ciphertext, key material and application binding and proves all three fail closed. Key bytes are consumed through a zeroizing temporary, opaque snapshot/key Debug output is redacted and plaintext labels are absent from the envelope. Key generation/custody/rotation, external snapshot permissions and deletion remain operator responsibilities.
ORM-02 partial retry overwrites conflicting destination data or silently accepts extrasAccept only an empty destination or an exact matching subset, insert without replacement, treat only an exact raced duplicate as replay and verify the final complete inventory. Never delete an extra row or claim transactionality across stores.The exact negative proves a differing row and an extra row fail before mutation. Deterministic tests cover partial resume and idempotent replay; the live matrix exports MongoDB→SurrealDB→MongoDB and checks ordered IDs. Export performs two equal observations, but formal consistency still requires the application to quiesce writers; a failed restore can retain prior successful inserts for the documented retry path.

Release-negative minimum: ciphertext/key/scope substitution and conflicting or extra destination state.

TM-SEC-1 — declared HTTP payload contracts

Assets: route semantics, validated request data, schema configuration and security telemetry.

Abuse caseRequired dispositionRepository evidence or remaining work
SEC-16 malformed, ambiguous or schema-confused JSON bodyRequire exact JSON media type on unsafe schema-bound requests; reject malformed, duplicate-key, oversized or deeply nested input before applying a precompiled closed schema. Never fetch attacker-selected schema references.The bounded route policy compiles JSON Schema 2020-12 or one OpenAPI 3.1 component with local references, no network/filesystem resolver and linear-time regexes. The exact negative covers shape/additional-property confusion and external references; middleware tests cover 415/400/422 and exact body preservation. Auth, ownership, domain rules and non-JSON parameters remain separate.

Trust boundaries are application schema configuration ↔ compiled validator and untrusted HTTP body ↔ route handler.

TM-SEC-2 — anomaly assessment and proof-of-work admission

Assets: service availability, canonical client subjects, challenge key, challenge capacity and replay state.

Abuse caseRequired dispositionRepository evidence or remaining work
SEC-07 forged, replayed, cross-subject or resource-exhausting challengeAuthenticate the complete challenge, bind it to one canonical subject, cap difficulty/TTL/cardinality, reject invalid work, expire it and atomically consume one successful proof. Classification must remain explainable and must not become authorization by itself.The exact negative tampers with the token, changes the subject, submits invalid work, verifies concurrently and replays/expires the challenge; only one local verifier succeeds. Aggregate collection, proxy/device identity, accessible alternatives, distributed replay state, adaptive evaluation and enforcement remain application/deployment work.

Trust boundaries are host-supplied aggregates ↔ deterministic classifier, application subject ↔ challenge and process-local replay state ↔ distributed deployment.

TM-SEC-3 — authenticated local security-event journal

Assets: normalized security-event evidence, HMAC keys, key identifiers, record order and durable local file state.

Abuse caseRequired dispositionRepository evidence or remaining work
SEC-33 forged, substituted or rotation-confused local eventAuthenticate a domain-separated canonical frame containing sequence, key identifier, predecessor tag, payload length and exact normalized event bytes; reject absent/wrong keys, forgery, reordering and removed interior records before returning an event as HMAC-verified.The exact negative proves payload tampering, same-identifier wrong-key substitution and absent historical-key rotation fail closed. Additional deterministic tests cover restart across active-key rotation, predecessor/sequence attacks, quotas, symlink targets, external length changes and redacted/zeroizing key storage. This is one local writer and a maximum of eight keys/4,096 records/16 MiB; trusted key/path custody, whole-tail rollback checkpoints, multi-writer coordination, compaction, retention, remote delivery and acknowledgement remain operator/application work.

Trust boundaries are local event producer ↔ authenticated journal, key manager ↔ rotation ring and filesystem state ↔ restart verifier.

TM-PAY-1 — webhooks, billing, payouts and fiscal boundaries

Assets: provider secrets, event IDs, subscription/payout state, amount/currency, invoice identity, replay state and fiscal documents.

Abuse caseRequired dispositionRepository evidence or remaining work
PAY-01 forged webhookVerify the provider’s exact signed bytes and algorithm cryptographically before parsing side effects.Axum and Actix call the same verifier; provider-specific signature negatives plus Actix body/event preservation prove the bounded adapters.
PAY-02 replay/stale eventEnforce timestamp window and unique event ID in durable shared state before effects.Freshness and bounded local replay are enforced before dispatch. The opt-in SQL ledger persists provider-scoped payload/event claims across SQLite/PostgreSQL/MySQL/MariaDB processes, fails closed on capacity/storage/profile errors and has restart/contention/four-protocol live evidence. Middleware claims before dispatch and is not exactly-once delivery; stable event IDs can instead share the caller’s relational domain transaction.
PAY-03 amount/owner substitutionDerive product/currency/owner from authenticated server state, never arbitrary client values.Materialized SQLx/Turso billing scaffolds bind checkout to authenticated identity and deny cross-owner subscription reuse before customer binding; route/plan/provider review remains open.
PAY-04 duplicate/partial transitionUse a transactional idempotent state machine and reconcile with the provider.check_and_record_event_key_with_transaction can bind one stable provider event ID to one mutation in the same supported relational database. Cross-system atomicity, provider reconciliation, automatic state-machine policy, and middleware crash recovery remain open.
PAY-05 payout destination takeoverRequire step-up authentication, allowlist/change delay and independent audit.Application workflow remains open.
PAY-06 false fiscal authorizationMock only explicitly; live/homologation fail Unsupported until XMLDSig, mTLS and SEFIN homologation exist.Fail-closed fiscal tests.
PAY-07 forged or confused SEFIN issuance responseBound body size, status, environment, submitted DPS ID, 50-digit access key and signed infNFSe/@Id; malformed, mismatched, unsigned, tampered or decompression-amplified material must never become authorization.The feature-gated offline protocol codec emits deterministic GZip/Base64 request JSON and distinguishes HTTP 201 authorization from bounded 400/403/500 rejection. It verifies both embedded XMLDSig values locally. Certificate trust, durable idempotency, live restricted-environment evidence and homologation remain open.
PAY-08 replayed or substituted direct chargeRequire integer minor units, currency, authoritative provider customer/payment-method identity and a durable order idempotency key; bind the accepted response before side effects.ChargeRequest validates and redacts the bounded inputs. Stripe forwards Idempotency-Key, confirms off-session and rejects responses with mismatched amount/currency or non-accepted status. Offline receipts carry the distinct non-success Mock status and other adapters fail unsupported. The host still owns mandate/SCA setup, identity authorization, durable key uniqueness, webhook reconciliation and entitlement state.
PAY-09 substituted or replayed paid invoice deliveryBind the recipient, exact minor-unit total and currency to final non-mock payment evidence; expose a stable delivery identity and require durable claiming before retryable effects.PaidInvoice rejects Processing/Mock and any e-mail, amount or currency mismatch. The opt-in Mail bridge renders bounded HTML/PDF, runs mandatory pre-flight and retains a stable delivery key. The host still owns authenticated order construction, webhook reconciliation, atomic outbox claiming, retry policy and provider acceptance; delivery is not exactly once.
PAY-10 duplicated or response-confused metered usageBind every accepted response to provider-specific customer/item, metric identity, quantity, timestamp/action and retry evidence; never infer missing provider fields from a uniform subscription ID.Stripe Meter Events forward a bounded identifier and recheck customer/event/value/timestamp/identifier. Lemon Squeezy Usage Records recheck item/quantity/action and mark the application event key as requiring durable outbox claiming. Both cap response bytes, redact identities and keep mocks visibly non-live. Provider-account acceptance, configured aggregation, durable storage/retry and invoice reconciliation remain application/operational work.

Trust boundaries are provider ↔ webhook, user ↔ checkout, application ↔ provider and application ↔ durable billing state.

TM-MAIL-1 — outbound content, suppressions and delivery telemetry

Assets: recipients, message content and attachments, provider event identities, suppression state, tenant identity and operational observations.

Abuse caseRequired dispositionRepository evidence or remaining work
MAIL-01 executable, active or type-confused attachment leaves the processValidate bounded metadata and recognizable signatures, reject active content and fail closed when authoritative inspection is unavailable.The opt-in static AttachmentInspectionGuard completes inspection before the wrapped transport. Its strict local heuristic rejects executable magic, spoofed known types, active PDF/SVG, secrets and unsafe text links. It is not antivirus, sandboxing, recursive archive inspection or CDR; production risk policy may require an independently operated scanner adapter.
MAIL-02 forged, replayed or lost suppression event permits unwanted deliveryAuthenticate the provider event before mutation, bind provider/event/payload exactly, persist suppression durably and check it before transport.The opt-in SQLite store provides exact replay conflict detection, monotonic manual/bounce/complaint state, immutable quotas and shared-local restart/two-instance evidence; SuppressionGuard fails closed on suppressed recipients or unavailable state. Rullst does not authenticate provider webhooks in this API, and only already-verified events may be recorded. Replay-ID retention must cover the provider window. Multi-host replication, webhook adapters and provider-account acceptance remain open.
MAIL-03 message or recipient leaks through delivery telemetryEmit only bounded low-cardinality outcomes and never recipient, subject, body, filename or provider response content.ObservedMailDriver records provider label, outcome, elapsed time, attachment count and two booleans through a non-failing static sink. The bounded default sink is process-local; externally operated metrics/tracing export, retention and alerting remain deployment work.

Trust boundaries are authenticated application state ↔ mail message, application ↔ inspection/suppression adapters, verified provider event ↔ local suppression state and transport result ↔ observation sink.

TM-MESSAGING-1 — durable messages, wire frames and correlation

Assets: message payloads and headers, idempotency state, ACK leases, consumer progress, storage keys, broker routing metadata and trace correlation.

Abuse caseRequired dispositionRepository evidence or remaining work
MESSAGING-01 protected message content is recovered from a copied SQLite fileOffer an explicit authenticated-encryption profile, encrypt header values and payload before persistence, and fail startup on profile/key mismatch.connect_encrypted uses AES-256-GCM with random nonces and a bounded keyring. A raw-database/restart regression proves the selected header and payload are absent while delivery round-trips. Topic, event/content type, IDs, timestamps, idempotency key, fingerprint and delivery state remain visible metadata; host key custody, permissions, backup and erasure remain required.
MESSAGING-02 ciphertext or metadata is copied, reordered or alteredBind namespace, topic, sequence, message ID, event/content type, timestamp and rotation key ID into AAD; reject authentication failure before claiming the message.The exact negative swaps two valid ciphertext rows and receives StorageAuthenticationFailed; probe tamper and wrong-key tests cover startup. AES-GCM does not prevent deletion, rollback of the complete database or availability loss, so protected backup/rollback detection remains deployment work.
MESSAGING-03 unsafe rotation silently makes retained records unreadableTrack every retained record’s non-secret key ID, require every referenced prior key at startup, use only the primary key for new writes and permit removal only after old records are purged.The rotation regression opens old+new, writes with the new primary, rejects new-only while an old record remains, then permits it after terminal purge. External secret-manager rotation, escrow, recovery rehearsal and multi-host rollout remain operator work.
MESSAGING-04 malformed, oversized, cross-namespace or future-version wire frame enters broker stateBound the frame before payload allocation, validate every field and canonical ordering, require exact namespace and reject unknown versions/trailing bytes.A fixed byte digest freezes v1; exact negatives cover every truncation, wrong magic, unknown version, trailing data and namespace mismatch. The codec is only an envelope primitive, not a remote transport or provider protocol.
MESSAGING-05 attacker-controlled trace metadata leaks baggage or creates ambiguous correlationPropagate only strictly validated W3C version-00 traceparent and a conservative tracestate subset; exclude baggage and redact diagnostics.Grammar, duplicate-key, zero-ID, uppercase and malformed-member negatives are exact; an in-memory delivery proves only the two allowlisted headers. Sampling, tenant authorization, exporter security and retention remain host work.
MESSAGING-06 process stops after broker publication but before acknowledging the relational outboxCommit domain state plus outbox row together, claim with a lease, publish the exact outbox event key as broker idempotency, then ACK the exact claim. Never call the two systems one atomic transaction.The opt-in static OrmOutboxRelay maps one configured stream/topic, validates the claimed JSON and keeps payload/event/claim keys out of Debug. An exact crash-window regression publishes, lets the claim expire, republishes through a new claim, observes the broker’s duplicate receipt and only then ACKs; one message exists. Worker supervision, retention, tenant authorization and destination idempotency remain application work.

Trust boundaries are publisher/consumer ↔ broker contract, process ↔ SQLite file/key manager, local envelope ↔ future remote adapter and untrusted correlation headers ↔ tracing infrastructure.

TM-AI-1 — prompts, RAG and tool execution

Assets: system prompts, tenant content, provider keys, retrieved data, tool credentials, destructive operations and audit evidence.

Abuse caseRequired dispositionRepository evidence or remaining work
AI-01 direct/indirect prompt injectionTreat prompts/retrieval as untrusted and never equate heuristic pass with safety.The versioned rullst-ai-guardrails-v1 offline corpus runs injection/jailbreak cases across all built-in transports; adaptive and live-model evals remain open.
AI-02 PII/secret exfiltrationMinimize/redact before dispatch and enforce tenant-aware retrieval.The versioned offline corpus fixes exact implemented email/card redactions across built-in transports. RagPipeline guards/masks every selected passage, requires trusted tenant context and rejects mismatched tags; the application retriever must still apply authoritative datastore tenant/ownership predicates, and raw provider calls remain an explicit bypass boundary.
AI-03 unauthorized tool/argumentsAllowlist typed tools, validate schema and authorize as the initiating subject after selection.Local registry dispatch requires exact allowlist, principal authorization, closed bounded JSON, call budget and audit; destructive/financial approvals are one-use and payload-bound. Provider-native selection loops, domain authorization, approver authentication and durable audit remain open.
AI-04 destructive autonomous actionRequire human approval and idempotency for finance, deploy, deletion or privilege change.Not yet a general framework guarantee.
AI-05 SSRF/egress exfiltrationValidate destinations, block private/link-local/metadata networks and cap redirects/content/time.EgressPolicy::strict() denies all hosts until an exact allowlist is configured. The opt-in EgressFetcher resolves under deadline, validates every answer, pins them into a proxy-free client, verifies the peer, revalidates manual redirects and bounds declared/streamed bytes; deterministic negatives stop private/mixed DNS before transport. It does not automatically wrap provider/application clients, and tenant-aware destination authorization, response validation plus a successful live-origin redirect/stream contract remain open.
AI-06 unbounded cost/availabilityEnforce body/token/time/concurrency budgets, cancellation and circuit breaking.Built-in transports have configurable request deadlines and local tools have call/payload budgets; provider-neutral cancellation, concurrency limits and circuit breaking remain open.
AI-07 hallucinated structured resultRequire schema and authoritative server-side validation; prose is never authorization.Structured-output foundations exist.
AI-08 cross-tenant, reordered or incompletely erased chat memorySelect storage only from trusted tenant/conversation context, commit complete exchanges atomically, reject stale writers and erase the exact key.The reusable stores bind tenant plus validated conversation ID and enforce bounded consecutive user/assistant pairs. The SQL adapter uses transactional revision CAS; its exact SQLite negative proves isolation, a single winner, deletion with foreign keys disabled and no orphaned messages, while live matrices cover PostgreSQL/MySQL/MariaDB protocols. Authentication/ownership inside a tenant, encryption, retention deadlines and backup erasure remain host policy.

Trust boundaries are content ↔ model context, application ↔ provider, model output ↔ tool dispatcher and retriever ↔ network/data sources.

TM-IOT-1 — OTA manifest and device lifecycle

Assets: provisioned public key, firmware image/hash, target, monotonic version, boot state and telemetry.

Abuse caseRequired dispositionRepository evidence or remaining work
IOT-01 forged/tampered manifestVerify Ed25519 over canonical bounded bytes with the provisioned key.Signature/tamper tests.
IOT-02 rollbackRequire version above a persisted monotonic counter before boot.In-process anti-rollback exists; persistent integration remains open.
IOT-03 wrong target/imageVerify target, declared length and cryptographic hash before flashing.Manifest checks exist; downloader/flasher is roadmap.
IOT-04 power loss/partial flashUse a recoverable A/B boot flow and commit counter only after verified boot.Hardware/bootloader integration remains open.
IOT-05 signing-key compromiseUse offline protected signing plus rotation/revocation.Operational/HSM program remains open; simulators are not HSMs.
IOT-06 telemetry spoof/replayAuthenticate channel/device and bind identity plus sequence/time.Transport identity/MQTT remains open.

Trust boundaries are signer ↔ distribution, distribution ↔ device, verified manifest ↔ flasher/bootloader and device ↔ backend.

TM-ACADEMY-1 — education, assessment and games

Assets: school and tenant membership, learner identity, protected content, enrollment/entitlement, lesson progress, submissions and grades, score events, leaderboards, rewards, automations, payment state, minors’ data and administrative evidence.

Actors and trust boundaries: learner, guardian, instructor, evaluator, moderator, support operator, school owner and platform administrator; browser or game client ↔ application; application ↔ school membership/database/cache/ queue/storage; application ↔ billing/media/notification provider; automation or AI output ↔ authorized domain command. The client is never authoritative for identity, entitlement, grade, score, reward or payment state.

Abuse caseRequired dispositionRepository evidence or remaining work
ACADEMY-01 forged learner, role or school contextDerive the subject and memberships from an authenticated server-side session or reviewed gateway; never trust form/header identity.The LMS starter derives its numeric learner ID from the encrypted session, loads active persisted school memberships, accepts X-School-ID only as a selector within that set, rejects invalid/absent/ambiguous selection and binds the chosen tenant plus school-scoped active roles into server-created contexts. Invite/provisioning and the separate production identity lifecycle remain application work.
ACADEMY-02 cross-user lesson or progress accessResolve the lesson and active enrollment, authorize the enrollment owner before returning media or writing progress, and deny before side effects.The generated learning and completion services require owner plus active school membership/course scope; the materialized LMS cargo test executes owner/cross-user and cross-school HTTP/database negatives before side effects. The player rejects insecure sources and incomplete accessibility metadata, but signed media delivery and broader route enumeration remain open.
ACADEMY-03 unenrolled or expired content accessCheck a current server-side entitlement on every protected lesson, download and media request; signed URLs must be short-lived and subject-bound.The starter requires an active enrollment plus exactly one valid active lesson policy. Server time enforces release/expiration, and a same-course prerequisite checks persisted learner progress; missing, duplicate, malformed or cross-course policy fails closed in the common lesson guard used by player, progress and assessment. Paid entitlement, remote storage/CDN, signed media/download routes and concurrent multi-database proof remain open.
ACADEMY-04 cross-school data leakScope reads, counts, exports, cache keys, jobs, files, search and telemetry by authenticated school membership and test non-interference.School, membership, course scope, cohort and entitlement tables have explicit unique/query indexes. Learning, publication/rollback, assignment grading/correction, score correction/leaderboard, completion/certificate mutation, roles and scheduled publication enforce the authenticated school. Outbox records and derived automation/notification state preserve school_id; the bounded leaderboard cache uses authenticated tenant namespaces, validates cached scope and is invalidated by authoritative mutations. The materialized SQLite test rejects arbitrary/ambiguous selection, same-user notification leakage and a foreign automation rule, and proves a foreign admin cannot read/mutate the bounded resources. Other caches, files, search, metrics, exports, Nexus, distributed cache/failover and PostgreSQL/MySQL non-interference remain open.
ACADEMY-05 assessment or grade tamperingVersion authoritative questions/rubrics, enforce attempt/time limits server-side, grade trusted inputs and audit every override.The LMS starter persists versioned single-choice questions/options, authorizes the enrolled owner, grades from server-side answer keys, enforces a bounded attempt count and commits immutable answers, ScoreEvent, leaderboard update, score_recorded and quiz_graded in one transaction. Timed attempts use persisted server start/expiry epochs, consume the limit at start and cannot extend the deadline by replay. A server-random seed produces a persisted question/option order; replay returns it exactly and grading rejects an ID set changed under the same ruleset. Authenticated start/submit routes derive quiz/learner from path/session. Cross-user, unknown-option, unstarted and expired submissions fail. Text assignments additionally persist versioned task/rubric policy, owner-bound attempts, human grades and criterion feedback. Submission derives the learner from the session and enforces enrollment, deadline, attempt bound and exact replay; grading requires evaluator/instructor/admin distinct from the learner, covers exactly the persisted criteria and rejects scores above server maxima. Admin-only corrections remain append-only, revalidate the same rubric, bind exact replay and preserve before/after, reason, actor, time and outbox; the original grade is never overwritten and an effective-grade query selects the latest correction. The materialized regression covers HTTP, cross-user, late, non-evaluator correction, conflicting replay and impossible-score negatives. Attachments, visual authoring, distributed-clock analysis and concurrent multi-database proof remain open.
ACADEMY-06 replayed, impossible or reordered scoreAuthenticate a versioned ScoreEvent, recompute or validate results, enforce unique attempt/deduplication keys and deterministic leaderboard ordering.The LMS scaffold derives the actor from UserContext, validates version/origin/IDs/keys/bounds, records the event and leaderboard update transactionally, enforces unique event/attempt keys, orders ties deterministically and provides admin-only idempotent corrections with reason/before/after. Owner-only single-choice/matching/typed routes accept only attempt key plus option/pair IDs/bounded text; opaque evaluator outcomes are persisted after a transaction-locked check against database-owned kind/maximum/ruleset/season/evidence and exact answer-policy configuration, alongside strict ScoreEvent/score_recorded v2. Matching requires a complete permutation of two to eight known unique ID pairs. Typed recall rejects controls/oversize, applies configured trim/case semantics and retains a policy-bound SHA-256 replay key instead of raw text. Attempt uniqueness is learner/activity-scoped and event identity is server-derived. Exact normalized retry is a no-op and changing an option/pairing/text under the same key is rejected. An enabled rullst-box-v1 policy is locked and updates durable review state in the score transaction; exact replay cannot move the due time, and the owner-only due queue rechecks school/course/enrollment scope. The materialized test covers HTTP cross-user, future schema, impossible score, evidence/policy mismatch, malformed pairs/text, raw-text absence, replay, durable/future review queue and non-admin correction. Typed digests and review history remain privacy data; listening/game evaluators, algorithm migration/efficacy and real-database concurrency tests remain open.
ACADEMY-07 duplicate or confused-deputy automationCommit a transactional outbox with domain state, deliver at least once, make handlers idempotent and reauthorize sensitive actions as the recorded actor/tenant.Score, first lesson completion, enrollment, achievement, publication, editorial rollback, assignment submission/grading/correction, course completion and certificate revocation commit versioned outbox events with domain state. Claims use bounded leases, bind ACK/failure to an exact token, schedule bounded retry, recover expiration, count attempts and dead-letter at the limit; the test rejects the stale token after recovery. The only automation executor action is non-destructive award_achievement; it rederives the plan from the claimed event and current enabled rule, then atomically commits unique execution, learner achievement and achievement_awarded. A database-backed supervised worker performs claim→validation/rule load→plan→execute→idempotent in-app notification→ACK/fail with explicit shutdown, safe drop and local counters. Its achievement template is closed/versioned and renders Portuguese, Spanish or English with deterministic fallback. A newly committed unsuppressed notification and that rendered projection are also sent best-effort through tenant-scoped in-process realtime to an owner/admin-authorized subscription; the database remains the replayable source. Passive assignment envelopes use closed schemas and exact learner/actor/ruleset/score bounds before ACK. The materialized test proves assignment/rollback/completion/revocation and FIFO domain-envelope delivery, localization/fallback denial, owner-only read, realtime receipt and ACK-loss redelivery no-op. External Mail/push, other event catalogs, distributed realtime/replay, exported telemetry, cross-tenant policy and approval envelopes for future actions remain open.
ACADEMY-08 admin, support or AI privilege escalationApply least privilege and separation of duties, require step-up/approval for sensitive mutations and treat AI output only as untrusted input to guarded tools.Course publication requires an instructor/admin author, owner-bound review submission, a distinct admin reviewer and the same authenticated school; scheduled activation filters due versions by school under an exact renewable lease. Editorial rollback is admin-only, school-scoped, separation-bound and append-only. Eight durable roles carry school_id, grantor/reason/window; support expires, owner/admin grant/revoke requires a distinct school owner, replay is exact and authentication loads only active roles for the chosen school. The materialized negatives cover reviewer/rollback separation, scheduler containment, role expiry/revocation/replay and foreign-school denial. Step-up, durable external admin audit, visual authoring and AI tool composition remain open.
ACADEMY-09 malicious upload or active contentEnforce real type/size/name limits, isolate parsing, quarantine/scanning and distinct SVG/HTML/archive/document/media policies.Core now exposes a bounded in-memory admission contract with a hard size ceiling and application allowlist, canonical tenant/name checks, exact declared MIME/extension versus recognized signature, active-text rejection, tenant-prefixed randomized quarantine keys, SHA-256 binding, a static-dispatch scanner contract and fail-closed release. The deterministic scanner is explicitly mock-only. The exact negative rejects active SVG text, traversal, invalid tenant and MIME/extension spoofing. Multipart streaming, remote S3/R2 persistence, sandboxed parsers/transcoding, archives and a production malware adapter remain open.
ACADEMY-10 minors’ privacy or unsafe retentionMinimize collection, separate guardian consent when required, implement export/deletion/retention/anonymization and keep PII out of logs/telemetry.The starter stores a school-scoped age band rather than birth date, versioned retention, purpose-specific guardian consent/revocation and idempotent export/deletion request state. A bounded sweep schedules durable delete requests. Fulfillment uses exact leases, abandoned-claim recovery, delayed retry/dead-letter, a hard ten-attempt ceiling and actor/digest-bound completion. A supervised static-dispatch executor bounds adapter time, shutdown and local metrics; its deterministic mock is explicitly protocol-only. Materialized SQLite proves supervised success, adapter-failure dead-letter, foreign-school non-interference, stale-token denial, hard-limit dead-letter and replay. It also fails minors closed without active consent. The product’s cross-table fulfillment adapter, guardian verification, PII-safe observability, legal review and end-to-end integration remain required.
ACADEMY-11 payment-to-entitlement substitutionDerive product, school, learner and amount from server state; apply verified idempotent provider events before granting or revoking entitlement.Capital webhook foundations exist, but the LMS starter has no billing-to-entitlement integration and makes no paid-course claim.
ACADEMY-12 availability, farming or multi-account abuseApply identity and origin budgets, replay controls, anomaly review and accessible rules without using a manipulable cache as source of truth.The optional redis-rate-limit adapter provides atomic shared counters, bounded windows and hashed client keys; empty/mock_* configuration is visibly process-local and fails require_distributed(). The exact negative gate proves that fail-closed boundary, while a separate CI/release contract proves two independent clients consume one budget against digest-pinned Redis. Academy HTTP composition, cluster/failover and domain-specific farming/multi-account tests remain open.

Academy negative minimum before a comparative security claim: anonymous, cross-user, cross-course and cross-school content/progress requests; expired entitlement; assessment replay/time manipulation; duplicate/impossible score; automation redelivery; unauthorized grade/admin mutation; hostile upload; payment replay; and multiple-account abuse. The exact release-negative gate now exercises owner/cross-user access, bounded cross-school HTTP/database mutation denials, versioned/idempotent score handling and the explicit distributed-rate- limit startup boundary. Cross-subsystem tenant isolation, activity-specific score recomputation, Redis failover and domain-specific multi-account behavior remain outside that bounded evidence.

TM-DEPLOY-1 — CLI, artifacts and release/deployment

Assets: source, generated projects, registry token, release tag, .crate archives, evidence, deployment credentials and production target.

Abuse caseRequired dispositionRepository evidence or remaining work
DEPLOY-01 tag/version/DAG mismatchMatch every package/internal requirement to the tag and publish topologically.Machine-readable order and metadata preflight.
DEPLOY-02 artifact/source substitutionPackage from tag, inspect/checksum/attest, reproduce in publish job and compare bytes.Workflow gates exist; real RC run remains open.
DEPLOY-03 secret/local artifact leakageDeny .env*, key/certificate patterns, unsafe paths and unexpected archives..crate audit gate.
DEPLOY-04 partial irreversible publishWait for index/checksum after each crate and resume only if the existing checksum matches. Never reuse a version.Workflow exists; recovery runbook remains open.
DEPLOY-05 CLI command/path injectionValidate identifiers, paths and image names; avoid shell interpolation and propagate failures.Generator injection/path tests.
DEPLOY-06 generated insecure defaultCompile materialized matrices and assert production fails closed while local shortcuts are loopback debug-only.Structural/representative blueprint matrices plus materialized auth, mail, chat and SQLx/Turso billing contracts exist; final RC/multi-OS evidence remains open.
DEPLOY-07 compromised dependency/actionPin actions, lock dependencies, audit advisories/licenses/sources and govern expiring exceptions.Policy exists; tag-bound result remains open.

Trust boundaries are contributor/CI ↔ repository, tag ↔ artifact, artifact ↔ registry and CLI ↔ filesystem/process/cloud.

Review and change control

  • Changes to authentication, authorization, tenant resolution, webhooks, AI tools, OTA or release flow must reference at least one abuse-case ID in their test or review description.
  • New boundaries receive new IDs; IDs are never silently reused.
  • Closing residual risk requires code/configuration, a negative test and commit-bound evidence. Documentation alone cannot claim a control is deployed.
  • Before stable v12, maintainers must review TM-12.10 against the exact RC, applications must add topology/provider threats and an independent reviewer must cover the highest-impact paths.

Aviso de segurança — CORS em scaffolds antigos

Estado

  • Componente afetado: aplicações geradas por versões antigas de cargo-rullst make:cors ou por blueprints que copiavam uma política CORS permissiva/refletida.
  • Framework atual: corrigido. Novos scaffolds usam uma allowlist explícita e falham quando CORS_ALLOWED_ORIGINS está ausente, vazia, contém * ou uma origem inválida.
  • Aplicações já geradas: não são modificadas automaticamente por uma atualização do CLI. Cada repositório precisa revisar o middleware que já foi copiado para seu código-fonte.
  • Severidade: depende da aplicação. O risco é maior quando respostas autenticadas por cookie ou bearer token podem ser lidas por uma origem não confiável.

Este é um aviso de migração do scaffold, não um CVE, pentest ou afirmação de que toda configuração final de CORS é segura.

Como identificar uma aplicação potencialmente afetada

Revise o middleware CORS gerado e procure qualquer um destes padrões. O trecho abaixo é deliberadamente incompleto e inseguro: serve somente como padrão de busca, não como código para copiar ou executar.

AllowOrigin::mirror_request()
.allow_origin(Any)
.allow_credentials(true)

Também é inseguro copiar o valor do header Origin para Access-Control-Allow-Origin sem compará-lo com uma allowlist administrada pelo servidor. A combinação de origem refletida ou wildcard com credenciais merece correção imediata.

Uma busca inicial pode ser feita na raiz da aplicação:

rg -n 'mirror_request|allow_origin\(Any\)|allow_credentials\(true\)|Access-Control-Allow-Origin' src

O resultado exige revisão humana: a presença de allow_credentials(true) não é por si só uma vulnerabilidade quando a lista de origens é fechada e correta.

Migração recomendada

  1. Faça uma cópia/revisão do middleware existente e gere a versão atual em uma branch separada com cargo rullst make:cors.

  2. Substitua reflexão/wildcard por uma lista exata de origens com esquema, host e porta, por exemplo:

    CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
    
  3. Mantenha credenciais desabilitadas, salvo quando a aplicação realmente usa cookies ou autenticação cross-origin. Se forem necessárias, habilite-as somente depois de validar a allowlist exata.

  4. Restrinja métodos e headers ao contrato real da API. O scaffold atual inclui um baseline, não conhecimento automático de todos os endpoints da aplicação.

  5. Teste pelo menos uma origem permitida e origens negativas com host parecido, subdomínio não autorizado, porta diferente, null e ausência de Origin.

Exemplo de verificação manual (uma origem não autorizada não deve receber Access-Control-Allow-Origin):

curl -i -X OPTIONS https://api.example.com/resource \
  -H 'Origin: https://evil.example' \
  -H 'Access-Control-Request-Method: POST'

Critério de conclusão

A migração está concluída quando:

  • nenhuma origem é refletida ou aceita por wildcard;
  • o processo falha de forma explícita quando a configuração exigida está ausente ou inválida;
  • credenciais só são aceitas para origens exatas autorizadas;
  • testes negativos confirmam que origens enganosamente parecidas não recebem autorização CORS;
  • caches/proxies recebem Vary: Origin quando a resposta depende da origem.

O template vigente pode ser consultado em cargo-rullst/src/generators/cors_middleware.rs.template.

Security advisory exceptions

Rullst does not treat an ignored scanner finding as remediated. Any temporary exception must have a narrow scope, a compensating control, an owner, and an expiry date. CI must fail for every advisory that is not actively governed here.

Last reviewed: 2026-09-08.

There are no active advisory exceptions in the v12 release-candidate dependency graph.

Remediation history

  • On 2026-09-08, RUSTSEC-2023-0071 was removed from the production and fuzz dependency graphs by moving jsonwebtoken from its rust_crypto backend to aws_lc_rs. The CLI scaffolds the same backend for newly generated JWT middleware. Auth and Connect’s RS256/OIDC tests remain enabled, and Cargo Audit runs without an exception.
  • On 2026-08-26, RUSTSEC-2026-0173 (proc-macro-error2) and RUSTSEC-2024-0436 (paste through Leptos) were removed from the resolved dependency graph. Their workflow ignores were removed in the same change.

These are historical remediations, not active exceptions.

Advisory response SLA

The clock starts when a maintainer receives a credible private report or an automated advisory first appears on a protected branch. Severity uses the highest credible impact while triage is incomplete.

SeverityAcknowledge and assign ownerMitigate or release targetMaximum temporary exception
Critical1 business day72 hours14 days
High2 business days7 calendar days30 days
Medium5 business days30 calendar days90 days
Low / unmaintained without known vulnerability10 business days90 calendar days180 days

If the release target cannot be met, maintainers must disable or isolate the affected capability, yank an affected prerelease when appropriate, or create a time-bounded exception below. Critical/high exceptions require release-owner approval and a documented reason that disabling the capability creates greater risk. An exception is never evidence that the vulnerability is fixed.

Review procedure

At or before expiry, the owner must either remove the dependency, upgrade to a fixed dependency graph, or record a new review with fresh evidence and a new short deadline. The reviewer must inspect the dependency path with Cargo Tree and run the repository’s Cargo Audit, Cargo Deny, and OSV workflows.

Patched rustls-webpki, event-listener, and lru findings and the removed HTTP/2 dependency from Actix’s disabled default features are deliberately not ignored. This makes a regression in any of those versions block CI again.

v12 partial-publication and recovery runbook

This runbook covers an interrupted multi-crate publication. A crates.io upload is irreversible: a version can be yanked but cannot be deleted or replaced with different bytes. Recovery therefore resumes forward from verified artifacts; it never tries to overwrite an accepted version.

Preconditions

  • Freeze merges and deployments that consume the affected prerelease.
  • Preserve the release workflow run, tag, commit, checksums.txt, attestation, SBOM, audit output and every .crate archive.
  • Revoke a registry token immediately if credential compromise is suspected.
  • Assign an incident owner and record times, commands, registry responses and affected package versions.

Name ownership and first-publication credentials

The machine-readable policy in .github/crates-ownership-policy.json separates the expected crates.io owner (venelouis) from the GitHub identity trusted to run the release (Rullst/Rullst). The bootstrap allowlist is not an ownership claim: it is the reviewed set of names that may still return 404 before their first publication.

.github/check-crates-ownership.sh runs once in the verification job and again immediately before publication. Every registered package must include venelouis among its owners. Every unregistered package must be present in the bootstrap allowlist. A name registered by another owner, an unexpected missing name, an API error, or a malformed policy stops the release.

Before the first v12 RC:

  1. Protect the GitHub crates-io environment with required review and tag deployment rules.
  2. Configure Trusted Publishing for every already-registered package using GitHub owner Rullst, repository Rullst, workflow release.yml, and environment crates-io.
  3. Create a shortest-lived crates.io token with the publish-new endpoint scope. Restrict its crate-name scope to the reviewed bootstrap names if the crates.io UI permits that combination. It does not need publish-update: registered packages are published with the OIDC credential.
  4. Store it only as the CRATES_IO_BOOTSTRAP_TOKEN secret in the protected crates-io environment. Never put it in repository secrets, logs, command history, documentation, or a local Cargo credentials file.
  5. Start the tag workflow only after reviewing the generated ownership evidence. The publish loop selects the bootstrap token only for packages classified as unregistered-reviewed-bootstrap; registered packages use the short-lived Trusted Publishing token.
  6. After all new packages are indexed, configure the same Trusted Publisher for each of them, enable crates.io’s trusted-publishing-only protection, revoke the bootstrap token, delete the GitHub environment secret, and remove the names from the bootstrap allowlist in a reviewed change.

Trusted Publishing cannot be configured before a crate’s first release. The one-time token is therefore an explicit, bounded exception, not a permanent fallback. See the official crates.io Trusted Publishing announcement and Cargo publishing rules.

Determine the exact state

For each package in .github/release-order.json:

  1. query https://crates.io/api/v1/crates/<crate>/<version>;
  2. classify it as not indexed, indexed with the expected checksum, or indexed with an unexpected checksum;
  3. compare the registry checksum to the retained verified archive;
  4. verify that every already-published internal dependency precedes its dependent in the release order.

Do not infer success only from the exit status of cargo publish. Registry indexing is asynchronous, so poll with a bounded timeout. The release workflow performs this check after each package.

Recovery decisions

Nothing was accepted

Correct the failure on a new commit. If the tag or package content must change, use a new prerelease version such as 12.0.0-rc.2. Never move a published or publicly attested tag to unrelated content.

A prefix was accepted with matching checksums

Keep the accepted packages. Re-run the release workflow from the same immutable tag and verified artifact set. Its resume logic skips only versions whose crates.io checksum equals the retained archive and continues at the first missing package.

If source, manifest or artifact content needs any change, bump all publishable packages and internal requirements atomically to a new prerelease. Do not mix rebuilt bytes into the interrupted version.

A checksum is unexpected

Stop immediately. Do not publish dependents. Preserve evidence, revoke credentials, contact crates.io support and treat the event as a potential supply-chain incident. Yank the affected version when that reduces user risk, but remember that yanking does not remove downloaded bytes.

A dependency never becomes index-visible

Stop before its dependents. Retain the successful upload response and poll the API/index within the bounded operational window. If crates.io reports an incident, wait for service recovery; do not reorder the DAG or weaken checksum verification.

Consumer mitigation

  • Yank a broken or unsafe prerelease and publish a corrected new prerelease.
  • State exactly which packages/versions were accepted, which were yanked and which replacement users should select.
  • Never describe yanking as deletion or as proof that no consumer downloaded the artifact.
  • For a stable vulnerability, follow the advisory SLA and coordinated disclosure policy in Security advisory exceptions.

Completion evidence

Recovery is complete only when:

  • all 16 expected versions are indexed with retained checksums;
  • a clean consumer resolves only registry packages and compiles;
  • documentation/index pages are reachable;
  • the incident timeline and any yanks/replacements are recorded;
  • temporary credentials are revoked and Trusted Publishing is restored;
  • the next release includes a regression for the initiating failure.

This process is package recovery, not application rollback. Database migrations, traffic rollback, secrets and deployed binaries require a separate application-specific operational plan.

Threat Radar and security telemetry

Vision preserved: external intelligence, verified audit feeds, durable telemetry, and SOC ambitions remain itemized with an implementation opinion in the capability ledger.

Rullst exposes development dashboards for security events in Studio and Nexus. They are observability surfaces, not a managed SOC, a penetration test, or proof of compliance.

The current dispatch_siem_alert compatibility facade records a bounded local event; it does not transmit or acknowledge delivery to an external SIEM. The dashboards therefore label this metric as local SIEM-candidate alerts.

Surfaces and access

  • Studio mounts its security view under /studio/security.
  • Nexus exposes its administrative security view only inside the authenticated, authorized Nexus router.

Both surfaces contain sensitive operational information. Bind Studio to a trusted interface and protect any deployed administrative route with TLS, authentication, role checks, and rate limiting.

What the counters mean

The dashboards read counters and bounded recent events from the in-process SecurityStore. A value increments only when the corresponding middleware or helper is installed and records an event. A zero means “nothing recorded by this process”, not “the application is proven attack-free”. Restarting a process may reset in-memory state unless the application exports it to durable telemetry.

Typical sources include:

  • honeypot route hits and active TTL-limited bans;
  • sanitizer, DLP, CSRF/CSWSH, RBAC, rate-limit, and login-jail events;
  • prompt-injection filtering and PII masking events;
  • secure-header applications and timing-guard executions.

Client identity comes from the trusted peer by default. Forwarded headers are usable only when the application has explicitly configured and authenticated a trusted proxy boundary.

Audit-chain status

AuditChain signs canonical, length-delimited records with HMAC and can verify record integrity and sequence continuity. It detects modifications only when the key is protected and the complete sequence is retained.

The dashboard must display Unavailable until an audit source and continuity verifier are actually connected. It must never infer “verified” merely because an event was emitted. An HMAC chain is tamper-evident; it cannot prevent deletion, key compromise, or loss of the entire log.

Security controls and limits

ControlWhat it providesImportant limit
RASP/WAF inspectionBounded heuristics for common malicious request patterns.It is not a complete parser or substitute for parameterization and domain validation.
Secure headersA strict nonce-based CSP/header baseline.The deployed page, proxy, browser, and policy determine scanner results; no A+ grade is guaranteed.
HoneypotsExact synthetic trap paths and temporary bans.They do not identify every scanner or replace edge rate limiting.
Login guardShared failure tracking, delay, cleanup, and jail policy.Account and recovery policy remain application responsibilities.
DLP and PII filtersSupported textual-response masking with content-type and size safeguards.Binary, encoded, streaming, and unsupported responses must follow explicit fail-open/fail-closed policy.
AI guardrailsPrompt checks and masking in the high-level AI client.Heuristics cannot guarantee that every adversarial prompt or sensitive value is detected.

Local verification

Use a test-only application instance and assert both the HTTP response and the recorded event. Do not probe a third-party or production target without written authorization.

Recommended negative tests include:

  • exact honeypot paths versus innocent paths containing similar substrings;
  • spoofed forwarding headers from untrusted peers;
  • ban expiry and bounded-cardinality behavior;
  • valid, invalid, reordered, and truncated audit sequences;
  • JSON, HTML, binary, compressed, SSE, streaming, and oversized DLP responses;
  • CSP nonces that match the rendered response and differ between requests;
  • authorization checks for every Nexus CRUD and batch operation.

Production checklist

  • Mount every security layer required by the application; availability in a crate does not install it automatically.
  • Configure trusted proxies explicitly and retain the direct peer address.
  • Export logs and metrics to durable, access-controlled storage.
  • Protect audit keys separately from audit records and rotate them with a documented verification procedure.
  • Test application-specific CSP, CSRF exceptions, tenant boundaries, and authorization rules.
  • Keep Studio private and verify Nexus authentication, TLS, rate limiting, and admin-role enforcement end to end.

Rullst Telemetry, Spans & Process Observability 📡

Rullst v12 exposes three related but separate observability surfaces:

  • RadarSnapshot samples supported process and Tokio runtime data;
  • radar_metrics_router() exposes those samples in Prometheus text format;
  • SpanCollector is a bounded, process-local buffer for spans that application or framework code records explicitly.

The optional telemetry Cargo feature also installs an OpenTelemetry tracing layer. None of these components proves a performance target or replaces a durable production observability backend.

Process and Tokio observations

RadarSnapshot::collect_async() measures one scheduler yield and samples the probes available on the current platform:

#![allow(unused)]
fn main() {
use rullst_core::radar::RadarSnapshot;

async fn inspect_process() {
let snapshot = RadarSnapshot::collect_async().await;
println!("uptime: {}s", snapshot.uptime_seconds);
println!("rss: {:?} MB", snapshot.memory_rss_mb);
println!("cpu: {:?}%", snapshot.cpu_usage_percent);
println!("tokio tasks: {:?}", snapshot.active_tokio_tasks);
println!("yield observation: {:?} us", snapshot.tokio_latency_micros);
}
}

The option-valued fields are deliberately None when a real probe is not available. Linux and Windows provide the current RSS/CPU implementations; active-task data requires a Tokio runtime. A yield observation is not a complete event-loop latency distribution.

Prometheus endpoint

Mount the metrics router explicitly:

#![allow(unused)]
fn main() {
use axum::Router;
use rullst_core::radar::radar_metrics_router;

let app = Router::new().merge(radar_metrics_router()); // GET /metrics
}

Only available metrics are emitted. The exporter formats a point-in-time local snapshot; authentication, network exposure, scraping, retention, dashboards, alerts, and multi-instance aggregation belong to the deployment.

Bounded local span collector

The global collector holds at most 500 TraceSpan records in memory. Recording is explicit; merely constructing a server does not instrument every HTTP, SQL, AI, mail, or security operation.

#![allow(unused)]
fn main() {
use rullst_core::telemetry_spans::{TraceSpan, global_span_collector};
use std::time::{Instant, SystemTime, UNIX_EPOCH};

let started = Instant::now();
// Run the operation being observed.

let timestamp = SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .map(|duration| duration.as_secs())
    .unwrap_or_default();

global_span_collector().record(TraceSpan {
    name: "catalog.refresh".to_string(),
    kind: "job".to_string(),
    duration_us: u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX),
    timestamp,
});
}

Studio’s /studio/traces and /studio/radar pages display the records that are actually present in this process. The buffer is not distributed, persistent, or a parent/child tracing model.

OpenTelemetry export

Enable the feature and point it at an OTLP/HTTP collector:

[dependencies]
rullst-core = { version = "12.0.0-rc.1", features = ["telemetry"] }
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
RUST_LOG=info

Then initialize the tracing subscriber once at startup:

#![allow(unused)]
fn main() {
fn initialize() -> Result<(), Box<dyn std::error::Error>> {
rullst_core::telemetry::init_telemetry()?;
Ok(())
}
}

Server::run also attempts this initialization, but an application that needs to fail closed on telemetry configuration should initialize it explicitly and handle the returned error before starting the server. The current exporter uses OTLP over HTTP and the service resource name rullst-app.

RedactPersonalDataLayer detects a small list of sensitive field names and emits a warning. It cannot rewrite a tracing event already observed by another layer, so secrets must be removed or redacted at the call site.

Studio boundaries

  • Radar cards poll the local /api/radar endpoint and display Unavailable instead of fabricated values.
  • Local span pages reflect only the in-memory collector in the Studio process.
  • Studio binds to loopback by default and has no built-in shared-environment password mode. Do not expose it publicly without an authenticated boundary.
  • Measure collector/export overhead against the real application workload and release build; Rullst publishes no universal latency or memory number.

💳 Payment Gateways & Financial Infrastructure Guide

Rullst Capital (rullst-capital) provides typed payment, subscription, payout, and webhook adapters. Unsupported operations return typed errors, and mock credentials select deterministic offline behavior.

The provider modules share common traits, but they do not all implement every operation. An adapter’s presence is not a promise of geographic availability, tax treatment, settlement time, pricing, or regulatory suitability.


🏛️ Provider Landscape & Strategic Archetypes

graph TD
    Capital[rullst-capital] --> Direct[Direct Merchant]
    Capital --> MoR[Merchant of Record - MoR]
    Capital --> Domestic[Domestic Payments]
    Capital --> APAC[Asia-Pacific & China Cross-Border]
    Capital --> Crypto[Web3 & Crypto]
    Capital --> Payouts[Global Payouts]

    Direct --> Stripe[Stripe]
    Direct --> Razorpay[Razorpay India]
    Direct --> MercadoPago[Mercado Pago]
    Direct --> PicPay[PicPay]

    MoR --> LemonSqueezy[Lemon Squeezy]
    MoR --> Polar[Polar.sh]
    MoR --> Paddle[Paddle]

    Domestic --> InfinitePay[InfinitePay Brazil]
    APAC --> Alipay[Alipay / Alipay+ China]
    Crypto --> Coinbase[Coinbase Commerce]
    Payouts --> Wise[Wise Transfers]

📊 Adapter inventory

Adapter groupIncluded modulesRullst contract
Direct payment APIsStripe, Mercado Pago, InfinitePay, PicPay, RazorpayImplemented trait methods perform signed/credentialed requests; unsupported methods fail explicitly.
Merchant-of-record APIsLemon Squeezy, Polar, PaddleProvider-specific checkout/subscription methods only; tax and merchant-of-record obligations remain governed by the provider contract.
Cross-border and walletsAlipayRSA2 operations that are not implemented fail closed; HMAC fixtures are not represented as RSA2.
Crypto commerceCoinbase CommerceProvider-specific charge and webhook flows; chain settlement is outside Rullst’s trust boundary.
PayoutsWiseProvider-specific payout operations; identity, compliance, currency, and availability checks remain external.

Provider pricing and terms change. Check the provider’s current official documentation and the concrete trait implementation before selecting an adapter.


Failures, timeouts, and retry ownership

Reviewed live methods use a single bounded egress contract: five-second connect and twenty-second whole-request timeouts, no redirects, no ambient proxy variables, and at most one MiB of JSON. Checkout responses additionally require an absolute credential-free HTTPS URL without a fragment. These controls do not prove that a provider account, product, price, or operation is accepted live.

CapitalError::Provider exposes only static provider/operation labels, a ProviderFailureKind, optional HTTP status, bounded numeric Retry-After, and one of three dispositions: permanent, transient, or rate-limited. It does not retain a raw URL, credential, response body, or reqwest diagnostic. Log those structured fields instead of formatting the original request.

Rullst deliberately does not retry mutations. A transient classification means only that a later attempt may succeed. Before retrying, the application must prove that the concrete operation forwards the same persisted idempotency key; otherwise reconcile provider state first. Backoff, jitter, attempt budgets, dead-letter handling, and operator alerts remain explicit application policy.


🔍 Selection model

Choose a provider only after checking which trait methods the Rullst adapter implements, the currencies and countries enabled on the actual merchant account, the current provider contract, webhook replay/idempotency requirements, and the application’s legal and tax responsibilities. Merchant-of-record status and tax handling are external contractual properties, not guarantees made by Rullst.


💻 Rust Code Integration Examples

1. Initializing Your Preferred Gateway

In your main.rs:

use rullst_capital::{
    init_provider, StripeProvider, LemonSqueezyProvider, InfinitePayProvider,
    PolarProvider, PaddleProvider, MercadoPagoProvider, CoinbaseCommerceProvider,
    PicPayProvider, AlipayProvider, RazorpayProvider,
};

#[rullst::runtime::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Select your active provider:
    
    // Example A: InfinitePay for a configured Brazilian merchant account
    init_provider(Box::new(InfinitePayProvider::new(
        std::env::var("INFINITEPAY_API_KEY")?,
        std::env::var("INFINITEPAY_WEBHOOK_SECRET")?,
    )));

    // Example B: Alipay for China & APAC Cross-Border E-Commerce
    // init_provider(Box::new(AlipayProvider::new(
    //     std::env::var("ALIPAY_APP_ID")?,
    //     std::env::var("ALIPAY_PRIVATE_KEY")?,
    //     std::env::var("ALIPAY_PUBLIC_KEY")?,
    // )));

    // Example C: Stripe for Global SaaS
    // init_provider(Box::new(StripeProvider::new(
    //     std::env::var("STRIPE_SECRET_KEY")?,
    //     std::env::var("STRIPE_WEBHOOK_SECRET")?,
    // )));

    // Example D: Polar.sh for Open-Source Devs
    // init_provider(Box::new(PolarProvider::new(
    //     std::env::var("POLAR_ACCESS_TOKEN")?,
    //     std::env::var("POLAR_WEBHOOK_SECRET")?,
    // )));

    Ok(())
}

2. Generating Checkout Sessions

#![allow(unused)]
fn main() {
use rullst_capital::provider;
use axum::response::Redirect;
use rullst_capital::CapitalError;

pub async fn start_checkout(customer_email: String, plan_id: String) -> Result<Redirect, CapitalError> {
    let p = provider().ok_or_else(|| CapitalError::ConfigurationError(
        "No billing provider configured".to_string(),
    ))?;

    let checkout_url = p.create_checkout_session(
        &customer_email,
        &plan_id,
        "https://myapp.com/billing/callback",
    ).await?;

    Ok(Redirect::to(&checkout_url))
}
}

3. Cryptographically Verified Webhook Endpoint

Rullst Capital provides Axum and Actix Web adapters for one canonical webhook verifier. It bounds the original payload, verifies the selected provider before dispatch, restores the exact signed bytes, and passes a strongly typed WebhookEvent into the handler. The production entry points reject empty and mock_* webhook configuration.

#![allow(unused)]
fn main() {
use axum::{Router, routing::post, Extension};
use rullst_capital::{verify_webhook, WebhookEvent, SubscriptionStatus};

async fn handle_billing_event(Extension(event): Extension<WebhookEvent>) {
    match event.status {
        SubscriptionStatus::Active => {
            println!("🎉 Subscription activated for: {}", event.customer_email);
            // Grant premium access in database
        }
        SubscriptionStatus::Canceled => {
            println!("⚠️ Subscription canceled for: {}", event.customer_email);
            // Revoke access or downgrade plan
        }
        SubscriptionStatus::PastDue => {
            println!("🚨 Payment failed: {}", event.customer_email);
            // Trigger automated dunning email
        }
        _ => {}
    }
}

pub fn billing_routes() -> Router {
    Router::new()
        .route("/webhooks/capital", post(handle_billing_event))
        .layer(axum::middleware::from_fn(verify_webhook))
}
}

Actix Web adapter

Enable rullst-capital with default-features = false, features = ["actix"], or enable rullst/capital-actix through the umbrella crate, and add actix-web as a direct application dependency. An explicit provider-bound state avoids global provider configuration and makes the replay boundary visible:

#![allow(unused)]
fn main() {
use actix_web::{App, HttpMessage, HttpRequest, HttpResponse, HttpServer, middleware, web};
use rullst_capital::{
    InMemoryWebhookReplayStore, StripeProvider, WebhookEvent,
    WebhookMiddlewareState, verify_webhook_actix_with_state,
};
use std::sync::Arc;

async fn handle_billing_event(request: HttpRequest) -> HttpResponse {
    let Some(event) = request.extensions().get::<WebhookEvent>().cloned() else {
        return HttpResponse::InternalServerError().finish();
    };
    // Apply an idempotent subscription transition using `event`.
    HttpResponse::NoContent().finish()
}

async fn serve() -> std::io::Result<()> {
    let provider = Arc::new(StripeProvider::new(
        "sk_live_from_secret_store",
        "whsec_from_secret_store",
    ));
    let replay = Arc::new(InMemoryWebhookReplayStore::default());
    let state = WebhookMiddlewareState::production_with_provider(provider, replay);

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(state.clone()))
            .wrap(middleware::from_fn(verify_webhook_actix_with_state))
            .route("/webhooks/capital", web::post().to(handle_billing_event))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
}

The default in-memory replay store is atomic only inside one process. Enable rullst-capital/webhook-sql (or umbrella rullst/capital-webhook-sql) to share a bounded replay ledger across SQLite, PostgreSQL, MySQL, or MariaDB processes:

#![allow(unused)]
fn main() {
use rullst_capital::{
    SqlWebhookReplayStore, StripeProvider, WebhookMiddlewareState,
};
use std::{sync::Arc, time::Duration};

async fn webhook_state(
    database_url: String,
) -> Result<WebhookMiddlewareState, rullst_capital::CapitalError> {
    let replay = Arc::new(
        SqlWebhookReplayStore::connect(
            database_url,
            100_000,
            Duration::from_secs(24 * 60 * 60),
        )
        .await?,
    );
    replay.prepare_schema().await?;
    let provider = Arc::new(StripeProvider::new(
        "sk_live_from_secret_store",
        "whsec_from_secret_store",
    ));
    Ok(WebhookMiddlewareState::production_with_provider(
        provider, replay,
    ))
}
}

Run equivalent reviewed DDL through deployment migrations instead of relying on request-time setup. Capacity/TTL are immutable for an existing ledger; drift, corruption, storage failure, and a full unexpired ledger fail closed. Only provider-scoped SHA-256 claims are stored, not raw payloads or event IDs.

SQL-backed middleware claims the payload before handler dispatch. It prevents cross-process replay but cannot make handler delivery exactly once: a crash can still occur between admission and a business mutation. For an atomic relational path, verify the exact provider payload through its low-level contract, select the provider’s stable event ID, and call check_and_record_event_key_with_transaction in the same transaction as the domain mutation. Do not also pre-claim that event through SQL middleware. External calls, e-mail, and queues still need an outbox, idempotent consumers, and reconciliation.

4. Provider-Specific Metered Usage

Use MeteredBillingProvider with StripeMeterEvent or LemonSqueezyUsageRecord. The Stripe request carries customer, configured event name, positive value, bounded timestamp and an identifier forwarded to the provider and HTTP idempotency header. The Lemon Squeezy request carries the numeric subscription-item relationship, positive quantity and an explicit increment/set action matching the provider-side aggregation.

Do not retry Lemon submissions from memory alone: its reviewed request has no application event-key field. Claim the request key durably before sending and make reconciliation idempotent. Stripe’s provider identifier also has only a rolling uniqueness window. Protocol fixtures verify request/response shape and bounds; they do not replace live provider-account testing.

5. Payment-Bound PDF Invoice Delivery

With the umbrella capital-mail feature, bind an authoritative invoice to the final charge receipt and prepare a pipeline-validated HTML/PDF message through rullst::mail::PaidInvoiceDelivery. Non-final/mock receipts and mismatched recipient, minor-unit total or currency fail before delivery. Persist the stable delivery key under a unique constraint before calling send; the bridge is at-least-once and does not infer webhook reconciliation.

The complete runnable shape and its outbox boundary are shown in Tutorial 19.

6. International Payouts with Wise

#![allow(unused)]
fn main() {
use rullst_capital::{CapitalError, WiseProvider};

pub async fn disburse_affiliate_commission(
    provider: &WiseProvider,
    affiliate_email: &str,
    amount_usd_cents: u64,
) -> Result<String, CapitalError> {
    provider
        .send_payout(affiliate_email, amount_usd_cents, "USD", "affiliate commission")
        .await
}
}

🛡️ Security controls and boundaries

  1. Bounded verification: webhook handlers should bound the body before parsing and reject a missing or malformed signature. Reading and parsing still allocate according to the concrete HTTP stack and payload.
  2. Cryptographic verification: supported webhook adapters use HMAC or constant-time verification for the exact signed bytes. Each provider’s timestamp/replay policy and deployed secret lifecycle still require review. The default replay store is process-local; multi-instance deployments need a durable shared idempotency boundary owned by the application.
  3. Typed parsing: supported provider responses map into Rust enums and structs without runtime reflection. A typed response does not establish authorization, idempotency, or correctness of the upstream service.

Examples and generated starters

The repository examples and CLI blueprints serve different purposes:

ArtifactPurposeTrust boundary
examples/blogWorkspace integration showcase with local data, interactive demos, and offline provider fixtures.Development-only; not a production template or compliance proof.
CLI blueprintsSmall starting structures generated into a new project.Generated output must be reviewed, configured, formatted, checked, and tested by the application owner.

No example is expected to exercise 100% of workspace behavior. External provider paths use deterministic mock credentials so CI and local development do not make live purchases, send email, call cloud LLMs, or issue fiscal documents.

Blog showcase

The blog package demonstrates:

  • server-rendered HTML and Active Record persistence;
  • a parameterized repository query;
  • LiveView/WebSocket and Wasm-island presentation examples;
  • Pico CSS and Tera presentation paths;
  • Billable quota evaluation and payment-adapter mock fixtures;
  • an escaped, unsigned DPS XML preview that is explicitly not an NFS-e authorization;
  • bounded security-helper demonstrations and a local AI/vector fixture;
  • a debug-only standalone Studio and Nexus access that is loopback-only in debug builds and credential-protected in release builds.

The complete, current route list is maintained in examples/blog/README.md alongside its configuration requirements.

Tenant selection

The example inserts a static test-only TenantMembership before the tenant layer. The X-Tenant-ID header can select only one of those fixed memberships. This models the separation between an untrusted selector and trusted authenticated claims.

In a real application, authentication middleware must derive membership from a verified session or token. Never construct membership from the same client header used to select a tenant.

Fiscal and provider fixtures

The /pricing page uses mock_* credentials and performs no live checkout. The DPS snippet is not XMLDSig-signed, transmitted, homologated, or authorized. Homologation and Production NFS-e modes remain fail-closed.

Mock URLs and sample provider metadata are test fixtures, not a promise of live capability, pricing, tax treatment, or regional availability.

Running locally

touch examples/blog/blog.db
cargo run -p rullst-blog-example

Before local startup, configure APP_KEY and DATABASE_URL as documented in the example README. The debug build needs no Nexus password, but verifies the socket peer as loopback. A release build does not start Studio and refuses to construct Nexus without validated NEXUS_ADMIN_USERNAME and NEXUS_ADMIN_PASSWORD values. Studio is local developer tooling; keep it on a trusted interface.

Verifying examples and blueprints

For the checked-in example, run package tests and the workspace trifecta. For CLI output, use a temporary directory and verify every generated project:

cargo fmt --all -- --check
cargo check --all-features
cargo test --all-features

CI smoke or DAST workflows cover only the routes and assertions present in those files. Their logs, commit digest, toolchain, and skipped steps are the evidence; the example itself is not evidence of production readiness.

Axum and SQLx interoperability guide

Rullst builds on Axum, Tokio, Tower and SQLx and preserves direct access to their APIs. This reduces coupling for HTTP and database code; it does not make every optional framework subsystem free to remove.

Mount an existing Axum router

rullst::Router supports conversion to and from axum::Router:

#![allow(unused)]
fn main() {
use axum::{Router as AxumRouter, routing::get};
use rullst::Router;

async fn existing_handler() -> &'static str {
    "existing Axum route"
}

let existing = AxumRouter::new().route("/existing", get(existing_handler));
let rullst_router: Router = existing.into();
let axum_router: AxumRouter = rullst_router.into();
let _ = axum_router;
}

The standard Axum extractor and response types remain available, including through Rullst’s documented re-exports.

Keep raw SQLx where it is useful

Application-owned SQL can live beside generated ORM queries. Bind every dynamic value and review structural SQL separately:

#![allow(unused)]
fn main() {
use sqlx::PgPool;

async fn active_names(pool: &PgPool) -> Result<Vec<String>, sqlx::Error> {
    sqlx::query_scalar("SELECT name FROM users WHERE active = $1")
        .bind(true)
        .fetch_all(pool)
        .await
}
}

Generate an escape-hatch snapshot

cargo rullst eject
cargo check

The command writes an inspectable Axum/Tokio entry-point snapshot. Review it; ORM models, migrations, authentication policy, Studio/Nexus integration and other selected subsystems can still require deliberate migration work. Use --force only when replacing src/main.rs is intended and the worktree is backed up.

Practical migration sequence

  1. Keep domain types and handlers independent of framework globals.
  2. Convert router boundaries incrementally.
  3. Replace generated ORM calls with raw SQLx only where that trade-off helps.
  4. Inventory authentication, middleware order, jobs, cache and admin surfaces.
  5. Run application-specific integration and authorization tests before removing dependencies.

Interoperability is a maintained design goal. “Zero lock-in” or zero migration cost is not a framework guarantee.

Community dependency helper and package roadmap

Rullst v12 does not define a runtime RullstPackage plugin ABI, execute third-party generators, discover a private package registry, or register routes automatically. Community integrations are ordinary Rust crates selected through Cargo and reviewed like any other dependency.

Current cargo rullst pkg boundary

The CLI provides two small local manifest helpers:

cargo rullst pkg add rullst-auth
cargo rullst pkg list
cargo check

pkg add:

  • accepts only ASCII Cargo names of at most 64 bytes beginning with rullst- or rullst_ and ending in an alphanumeric character;
  • parses Cargo.toml as TOML and inserts the dependency into the real [dependencies] table using the installed CLI’s version;
  • leaves an existing dependency and its features/version unchanged;
  • does not contact a registry, execute code, edit routes, or run a scaffold.

The subsequent cargo check performs normal Cargo resolution and compilation. A prefix is only a naming filter; it is not proof that a crate is official, safe, compatible, maintained, or endorsed by Rullst. Review the crate source, publisher, checksum, license, advisories, feature graph and release policy before adding it.

pkg list reads dependency keys from the parsed manifest and prints those with the same Rullst prefix. It is not a vulnerability, license or provenance scan; use the repository’s audit and dependency-policy tooling for those questions.

Integrating a community crate today

A community crate can expose ordinary Axum routers, Tower layers, SQLx types or constructors. The host application initializes it explicitly and mounts only the capabilities it intends to trust:

#![allow(unused)]
fn main() {
use axum::Router;

fn community_router() -> Router { Router::new() }
fn application_router() -> Router {
    Router::new().nest("/community", community_router())
}
}

Authentication, authorization, tenant policy, secrets, migrations, shutdown, telemetry and failure handling remain visible application responsibilities.

Future package protocol

A first-class extension protocol remains roadmap work for the next feature line. It should not be declared stable until the repository has all of the following:

  • a versioned manifest and compatibility contract;
  • explicit capability permissions for routes, storage, network and secrets;
  • deterministic lifecycle and failure semantics without runtime reflection;
  • package ownership, provenance and revocation policy;
  • compile/runtime conformance tests and safe uninstall/upgrade behavior;
  • a decision on whether untrusted extensions require a real Wasm sandbox with enforceable CPU, memory and I/O limits.

Until that contract exists, documentation and packages must not claim that RullstPackage.toml, RullstPackage, automatic route registration, or third-party generator execution are implemented v12 APIs. See the capability ledger and v12/v13 classification for the preserved vision.

Rullst Blueprints Roadmap 🗺️

“A practical collection of reviewed Rullst application starters”

This document maps the expansion plan for the Rullst Starter Blueprints ecosystem. The goal is to provide reviewable application starting points with explicit production checklists and capability boundaries.


🚀 Blueprints Design Philosophy

Every blueprint added to the CLI must meet three fundamental principles:

  1. Clear first experience: Responsive, accessible interfaces whose browser assets and CSP are explicit.
  2. Native Rust/Rullst Features: Demonstrate measured resource use, typed concurrency and explicit server/realtime boundaries.
  3. Production-minded defaults: Generate .env.example, database configuration, and a conservative .gitignore; deployment readiness remains an application-level review.

🗺️ Proposed blueprints (ordered from easiest to hardest)

Except for ERP, these rows describe design targets and are not selectable CLI blueprints. A proposal becomes implemented only when its generated project and negative boundaries pass the release gates.

IDBlueprint NameTechnical Focus in RullstCommercial Differentiator
4💼 ERP Pocket (Inventory)Embedded SQLite + rullst::nexus (Auto-CMS) + Single BinarySmall/medium-business inventory starter; crash recovery and backup remain application work.
5📋 Member/Club ManagementValidation + Nexus + reviewed receipt adapterProposed member and billing domain starter.
7🤖 AI Agent & RAG Boilerplaterullst-ai + opt-in document parsing/embedding adaptersProposed RAG starter; uploaded content remains untrusted.
8🪙 AI Credit-Based SaaSSSE + transactional usage ledger + payment adapterProposed AI SaaS starter with server-owned credit reservation.
9🏥 Scheduling & ClinicsHTMX calendar + scheduler + database conflict policyProposed scheduling starter with database-specific contention tests.
10🚪 Biometric Access ControlWebSocket foundation + real-time concierge panelPlanned access-control starter; device trust and latency need deployment-specific validation.
11📈 Affiliate CheckoutSSR + commission-split domain model + landing pagePlanned sales starter; performance and Lighthouse scores must be measured per application.
12🏢 B2B Multi-Tenant PlatformTenant context + RBAC + rullst-mailPlanned B2B starter; isolation must be proven across every storage and messaging boundary.
13💬 Discord-Like Realtime ChatServer-driven UI + authenticated WebSocketsProposed chat starter; distributed presence and load evidence are required.
14🛵 Delivery / Food AppBackground queue + explicit order state machineProposed delivery starter with idempotent jobs and notification adapters.

🔍 Highlighted Architectural Details

🪙 8. AI Credit-Based SaaS (The Token-Burner)

  • Architecture goal: a server-owned chat flow with bounded SSE streaming, cancellation and provider error handling.
  • Data Security goal: use a database transaction and provider-specific lock semantics to reserve credit before an LLM request. This workflow is not yet a generated, cross-database guarantee.
  • Monetization goal: integrate a reviewed usage ledger with a supported payment adapter; billing portals remain provider/application work.

🏢 12. B2B Multi-Tenant Platform (The Corporate Boilerplate)

  • Isolation goal: derive a validated TenantContext at the HTTP boundary and carry it explicitly through database, cache, queue and realtime operations. Rullst does not inject a tenant predicate into every arbitrary SQL statement.
  • Permissions goal: typed roles (Admin, Member, Billing) enforced in middleware and again at sensitive service boundaries.
  • Invitations goal: hashed, single-use, expiring invitation tokens delivered through a configured mail adapter.

💬 13. Discord-Like Realtime Chat

  • Client goal: use server-rendered messages and an explicit WebSocket client without making bundle size a proxy for correctness.
  • Scale gate: publish connection count, message mix, backpressure behavior, CPU/RSS, hardware and distributed topology before attaching capacity numbers.

🏥 9. Scheduling & Clinics (The Scheduler)

  • Conflict-prevention goal: enforce a database constraint plus a transaction strategy tested on every declared backend; isolation level alone is not a universal double-booking proof.
  • Reminder goal: use bounded, idempotent scheduler jobs and a configured mail adapter. Multi-instance leadership and durable retry must be explicit.

🤖 7. AI Agent & RAG Boilerplate (AI-Native)

  • Structure goal: quarantine and scan bounded uploads, parse supported formats in an isolated adapter, generate embeddings and store an authorized index.
  • Provider goal: expose an explicit provider selection and capability check. Changing an environment variable does not make data policy, schema or model behavior interchangeable.

Rullst Master Roadmap 🗺️

“The Path to the Ultimate Full-Stack Rust Framework” — an aspiration, not a guarantee

Rullst’s ambition is an asset. This roadmap preserves that ambition while separating what exists today from what is only a prototype, a research program, or a vision. An idea is never deleted merely because it is unfinished.

Our philosophy: “Security, Developer Experience and Performance, Architected for Humans and AI.”

Single roadmap source: ROADMAP.md is canonical. docs/src/roadmap.md embeds it directly in mdBook instead of maintaining a divergent copy. The deeper evidence and decision record is docs/src/capability-ledger.md. The release gates and executable checklist for the next major version live in docs/src/v12.md.

Status language

  • [x] Implemented: a bounded, testable implementation exists. This never means that every imaginable provider or production environment is covered.
  • [~] Partial: useful foundations exist, and the parenthetical says whether the remaining work is worthwhile and why.
  • [ ] Not implemented: the idea is preserved, and the parenthetical says whether it is worth pursuing and under which conditions.
  • [!] Do not promise: the absolute wording cannot be an honest framework guarantee; a narrower measurable goal is retained when useful.

Target windows are planning intentions, not release guarantees. Promotion to [x] requires code, focused tests, truthful documentation, and the release gates at the end of this document.

Audit of the detailed crate roadmaps

The per-crate roadmaps are intentionally preserved as detailed design backlogs. Some predate this status policy, so an old [x] can record the original author’s milestone claim rather than today’s verified end-to-end contract. This table is the current interpretation; the capability ledger contains the evidence boundary and recommendation for the highest-risk claims.

Detailed roadmapWhat is verifiably implemented nowPartial, experimental, or not implemented
rullst-aiGuarded high-level client; OpenAI/Gemini/Anthropic/DeepSeek/Ollama adapters; deterministic offline paths and eval corpus; JSON/schema distinction; guarded local tools; bounded tenant-aware audited RAG orchestration and process-local cosine retrieval.Streaming/cancellation, derived JSON Schema, provider-native authorized tool loop, durable memory/ORM hooks, first-party external retriever adapters, and live/adaptive model evals are not implemented.
rullst-authArgon2/local sessions, RBAC middleware, declarative Gate, OAuth/OIDC re-exports, and a substantial custom ES256 passkey foundation.WebAuthn is partial until normative conformance; first-class application JWT, TOTP with recovery codes inside Auth, magic links, and device/session management are not implemented. They are worthwhile, with WebAuthn first.
rullst-capitalProvider trait/adapters, explicit offline mocks, canonical fail-closed webhook verification with Axum/Actix adapters, shared bounded webhook replay claims and team/workspace quotas over four relational protocols, provider-specific coupon/trial contracts, billing scaffolding, analytics, and bounded NFS-e preparation.Live method coverage varies by gateway; cross-system exactly-once/reconciliation, Alipay RSA2, full tax/proration contracts, and homologated live NFS-e are not implemented. NFS-e is extraordinary and worthwhile only as a dedicated homologation program.
rullst-connectOAuth2/OIDC/social providers, a bounded tower-sessions state/PKCE/nonce lifecycle, process-local automatic token refresh, typed revocation contracts for Google/GitHub/Discord/Apple/Auth0/Cognito, fallible credential modes, pluggable HTTP client, discovery/JWKS validation, retry, mocks, and Axum/Actix callback extraction.Revocation for the remaining providers and some checked DX/provider conveniences remain narrower than their wording; encrypted token persistence/distributed refresh leases, SAML/SCIM/DPoP/JWE/mTLS/risk ML remain unimplemented. The old Phase 9 broker vision moved to the separate Messaging roadmap.
rullst-iotno_std frames/telemetry, bounded MQTT 5 PUBLISH and CoAP request encoders, the Ed25519 OTA manifest gate, and a typed durable-counter CAS boundary with restart/retry/conflict proof.Download, a concrete hardware-backed counter, flash/boot/rollback, MQTT/CoAP/LoRaWAN transports and session state, real hardware, HSM and PQC are not implemented; deterministic Simulated* types are experimental fixtures only. Keep the vision, but require target hardware and interoperability programs.
rullst-mailCore REST/SMTP/log/memory/mock drivers, failover, bounded attachment/CID serialization, scheduling foundations, mandatory security/deliverability pipeline, deterministic mocks, tenant resolution, tracking tokens, factories, background worker integration, opt-in bounded attachment inspection, shared-local SQLite suppression and minimized delivery observations.A checked item does not prove provider acceptance or inbox delivery; provider limits may be tighter, the local inspector is not antivirus/CDR, and provider webhook authentication plus multi-host suppression remain open. Compile-time mailables/CSS inlining, inbound MIME, AI dunning, DMARC/DKIM/S-MIME, Studio Mail Radar and extra gateways are not implemented; add providers only with a shared contract suite.
rullst-messagingVersioned bounded envelopes, topic-scoped idempotency, consumer groups, competing claims, expiring one-shot ACK leases, retry, dead-letter, explicit purge, deterministic time, redacted diagnostics, a reusable concurrent contract suite, and a fixed-schema durable local SQLite adapter with restart/corruption/two-instance evidence.Stable remote codec/storage plus Kafka, RabbitMQ, Redis Streams, NATS/JetStream, SQS/SNS, Google Pub/Sub, Pulsar, replication and provider-specific fault evidence remain unimplemented.
rullst-nexusFail-closed authenticated admin construction, compile-tested Nexus derive, server-side CRUD/search/pagination/sorting, explicit typed widgets, selected-record delete/deactivate, threat radar and AI assistant surfaces.Enum variants/multiline intent require explicit metadata; tenant ownership and durable audit remain host contracts. Custom dashboard injection and a visual SQL builder are not implemented; AI/data mutation remains host-policy-bound.
rullst-ormSQLx pools/dialects, Active Record/repository/query/schema foundations, fail-closed tenant scopes, strict DB modes, transactions, relations/soft deletes, audit/privacy, typed Turso primary, bounded MongoDB/DuckDB/SurrealDB adapters, Qdrant vectors and Redis native structures.Several historical [x] entries remain partial or absent: transparent edge replication, universal external-search durability, autonomous schema/index changes, automatic graph traversal, Wasm drivers and PQC. The 45 unique claims are now individually classified in v12.md.
rullst-securityBounded honeypot, sanitizer/CSP, RBAC, HMAC audit chain, RASP/DLP, AES-GCM vault, headers, applied Login Jail tarpit, TOTP with SVG QR, CSWSH origin policy, strict JSON/log guards, file-backed SRI, CEF formatting, compatible unsigned and opt-in HMAC-chained bounded local SIEM journals, timing/prompt filters and fail-closed CLI evidence/SBOM/doctor tools.“Autonomous”, live reputation/external SIEM delivery, A+ guarantees, zero-leak/zero-latency, certification and total OWASP/memory-safety claims are not established. Trusted whole-tail checkpoints, spool compaction/remote acknowledgement, CSRF WebSocket tickets/frame crypto, distributed rate limits/audit sinks, KMS/rotation, adaptive WAF, SQL firewall and all PQC/kernel/Wasm containment items remain partial or absent.
rullst-studioRead/filter SQLx browser, supplied-OpenAPI playground, bounded queue snapshot with opt-in pruned SQLite completion history, relational ER diagram, DB-backed flag toggles with same-process cache invalidation, redacted environment/typed-config view and local telemetry with unavailable states.Browser writes, automatic route-to-OpenAPI inference, secret-bearing request capture, cross-process flag invalidation, Redis queue inspection, N+1 profiling and Cache/Redis inspection remain roadmap work.

This audit does not downgrade ambitious ideas merely because they are difficult. Capabilities that require continuous operations, homologation, hardware, or a separate release lifecycle can follow the Maybe SaaS incubation strategy instead of being forced into the framework core. It prevents a checkbox from becoming a production promise before the necessary code, tests, provider/hardware environment, and operational semantics exist.

Executive milestone tracker

IDPillar and capabilityHonest status and recommendationTarget window
M1DX: CLI empowerment and make:* generators[~] Partial (worth finishing — the commands exist, but every generator/blueprint combination still needs a compiling temp-project matrix)v12 hardening
M2DX: fast linkers, build tuning, and responsible hot reload[~] Partial (v12 provides an authenticated, measured and generation-bounded first-party Rust-ABI development swap; worth benchmarking further, but sub-100ms depends on the machine/change graph and must not be guaranteed. v13 research may evaluate a versioned ABI, supervised process-restart fallback, opt-in state handoff and reproducible cross-platform latency baselines before promising any of them)Continuous / v13 research
M3DX: Axum/SQLx escape hatches, granular features, proc-macro diagnostics, and ejection[~] Partial (worth improving as migration tooling — bare Core is now runtime-only, ORM/SQLite queues are explicit features, and the umbrella maps them; universal “zero lock-in” is still not worth promising because optional subsystems carry migration cost)Next SemVer cycle
M4DX: make:resource and Ignition-style error console[x] Implemented (scoped) — resource scaffolding and a local developer error console exist; autonomous mutation is evaluated separately in M37v12 hardening
M5DX: documentation hub (mdBook), OpenAPI, and AST TypeScript generation[~] Partial (worth finishing — generators exist, but generated-project and serialization contract tests are still needed; AST inference is not a complete API contract)v13
M6ORM: Active Record, repository pattern, seeders, and Turso/libSQL vision[~] Partial (SQLx foundations and the bounded Turso-primary Hrana transport/matrix exist; relation/hook/auto-diff parity and transparent synchronization do not)v13
M7Edge/data: portable Wasm request/response runtime, distributed data, and autonomous upgrades[~] Partial (worth the portable edge runtime; distributed replication should use vendor-specific semantics, and autonomous upgrades are not worth enabling without signed artifacts, rollback, and operator approval)v13 research
M8ORM/AI: intent-based modeling and self-optimizing production indexes[ ] Not implemented (worth an advisory, explain-and-approve implementation — automatic production DDL without review is not worth the operational risk)v13 research
M9Auth: local auth, OAuth/OIDC, TOTP, passkeys, and WebAuthn[~] Partial (worth completing at high priority — useful auth pieces exist, but normative WebAuthn conformance and a first-class application JWT policy remain incomplete)v13
M10Security utilities: mail, DTO validation, rate limiting, and Shield[~] Partial (worth completing — local controls and mail transports exist, while distributed rate limiting and some provider invariants require real backends and conformance tests)v13
M11SaaS: hardened Nexus, Omni vision, billing, and entitlements[~] Partial (worth building in bounded modules — Nexus and billing foundations exist, but Omni, uniform live gateway coverage, and declarative entitlements are not complete)v13+
M12Defense in depth: RASP/WAF, Vault, honeypots, HMAC audit, secure headers, Login Jail, DLP, TOTP, fingerprinting, CLI inspection, and Threat Radar[~] Partial (worth continuous hardening — concrete controls exist, but they do not prove universal OWASP coverage, zero leakage, external intelligence, or certification)Continuous
M13Post-quantum web architecture, rullst-quantum, NIST PQC, and sandboxed Wasm plugins[ ] Not implemented (worth later only for a concrete protocol and threat model, using audited primitives; home-grown “quantum-safe” crypto is not worth implementing)v13 research
M14Frontend: HTMX-first SSR and Leptos/Dioxus interoperability[~] Partial (worth improving — HTMX/HTML support is real, while the current Leptos/Dioxus types are compatibility wrappers rather than full framework integrations; “zero bundle” is a selectable architecture, not a universal guarantee)v13
M15Runtime: queues, cache, scheduler, multi-stage Docker, and brokered messaging[~] Partial (bounded Core Memory/SQLite/Redis foundations plus rullst-messaging envelopes, idempotency, groups, leases, retry/DLQ, deterministic broker, contract suite and durable local SQLite state exist; remote codec/replication and RabbitMQ, Kafka, Redis Streams, NATS, SQS/SNS, GCP Pub/Sub and Pulsar adapters do not)Foundation v12; remote adapters v13+
M16Wasm islands and #[client_component][~] Partial (the bounded #[server_function] transport is now implemented over rullst.client v1 with a generated Axum route, Wasm caller, compile diagnostics and native/Wasm/scaffold evidence; island hydration, packaging, real-browser interoperability and a stable component ABI remain open)v13
M17Real-time, object storage, media, and cargo rullst pkg[~] Partial (worth modular expansion — WebSocket/SSE and local storage foundations exist; S3/R2, image processing, and a production package-registry contract do not)v13+
M18LiveView-style server-driven UI and make:live[~] Partial (worth hardening — a WebSocket component loop exists, but auth, reconnect, backpressure, diff semantics, and browser E2E coverage remain)v13
M19AI/telemetry: Radar, agent tool schemas, spans, and Prometheus /metrics[x] Implemented (bounded) — local telemetry and export surfaces exist; unavailable sources must remain unavailable rather than becoming invented valuesv12 hardening
M20Persistence: zero-copy event streaming and immutable ledger engine[ ] Not implemented (interesting but lower priority — worth implementing only after defining persistence, consistency, recovery, and verification semantics; the HMAC audit chain is not a distributed ledger)v13 research
M21Omni-frontend protocol and mobile hypermedia bridge[~] Partial (the web-first Tauri shell, shared rullst.client v1 envelope and bounded native offline-state foundation exist; platform persistence/secure keys, concrete network/background orchestration, native capabilities, physical-device evidence and store publication remain open)v13 research
M22Agentic DevOps and autonomous infrastructure provisioning[~] Partial (worth keeping as human-reviewed recommendations — telemetry advice exists; unattended infrastructure mutation is not worth enabling by default without preview, scoped credentials, audit, rollback, and policy)v13
M23Polymorphic core and auto-healing runtime/database[~] Partial (worth keeping as diagnostics — a schema-error suggestion helper exists; automatic code/schema mutation is not worth enabling by default without validated plans, approval, and rollback)v13
M24Embedded IoT: no_std frames and an Ed25519 OTA manifest gate[~] Partial (the frame/MQTT-PUBLISH/CoAP-request encoders, verification foundation and durable-counter CAS adapter contract exist; download, a hardware-backed store, flashing, boot slots, HSM/PQC, and transport interoperability do not)v12 foundation / v13 integrations
M25Async embedded IoT with Embassy[ ] Not implemented (worth implementing after transport and hardware traits stabilize, because executor integration before those boundaries would create churn)v13+
M26Guided PaaS/VPS deploy for Fly, Railway, Render, and Caddy[~] Partial (worth hardening — scaffolding and helpers exist, but “one click” and zero downtime are not framework guarantees because credentials, DNS, migrations, health, and rollback remain operator concerns)v13
M27Kubernetes manifest scaffolding and /health//ready probes[x] Implemented (scaffolding scope) — generated manifests remain deployment inputs that operators must reviewv12 hardening
M28Compile-time DI and Inject<T>[x] Implemented (foundation) — the typed container exists; “zero cost” remains a benchmarkable goal rather than a guaranteev12 hardening
M29Scalar playground at /docs and OpenAPI generation[~] Partial (worth finishing — the UI/router/generator exist, but full OpenAPI fidelity requires typed schemas and validation rather than syntax inference)v13
M30Tonic/gRPC and Protobuf scaffolding[~] Partial (worth finishing — make:grpc emits a starting service, but a distinct supported rullst-grpc crate and generated-project conformance matrix do not yet exist)v13
M31Aerospace, autonomous vehicles, robotics, and defense (rullst-orbit / rullst-auto)[ ] Not implemented (extraordinary, but not worth placing inside the web-framework Core; consider a separate safety-critical project only after hardware, standards, certification, and governance exist)Separate future program
M32Architecture: first-class Axum/Tower escape hatches and precise proc-macro diagnostics[x] Implemented (bounded) — router conversion/interoperability and syn::Error diagnostics exist; continue compatibility testsv12 hardening
M33SaaS: #[rullst::gate] and GateGuard declarative entitlements[ ] Not implemented (worth implementing for SaaS only if enforcement is server-side, tenant-bound, auditable, and independent of hidden UI controls)v13
M34Multi-target SDK generator for TypeScript, React, Dart, and Swift[ ] Not implemented (worth implementing from one canonical typed API schema; multiplying AST heuristics across languages is not worth the drift)v13+
M35Distributed OpenTelemetry trace-waterfall visualizer in Studio[~] Partial (worth implementing — Studio has trace surfaces, but a distributed OTel waterfall needs real ingestion, clock/skew handling, sampling metadata, and unavailable states)v13+
M36Natural-language-to-SQL Studio data copilot[ ] Not implemented (worth a read-only, explainable assistant with schema allowlists, parameterization, preview, limits, and approval; autonomous production writes are not worth the risk)v13 research
M37One-click AI error-console autofix[~] Partial (worth retaining as a local, reviewable patch workflow — an autofix endpoint exists, but autonomous edits need diff preview, workspace confinement, audit, tests, and rollback)v13
M38In-memory/local-NVMe SQLite read replicas with background synchronization[ ] Not implemented (worth vendor-specific adapters when demanded; generic “transparent replication” is not worth claiming because consistency and failover semantics belong to the selected database)v13 research
M39Optional self-hosted Rullst Gateway and load balancer[ ] Not implemented (worth a phased v13 design as a separate opt-in rullst-gateway crate/binary, preferably on a maintained proxy foundation such as Pingora. It should consume explicit readiness/drain signals and begin with bounded upstream selection, health checks, WebSocket forwarding and telemetry. It must not live inside rullst-core or claim parity with a managed global cloud service, whose network, DDoS controls, multi-zone operations and SLA are external infrastructure.)v13 research/foundation

Quantified planning horizon through v13

This second progress lens answers a different question from release readiness: how much of the canonical long-term milestone programme through v13 remains if every milestone that is not yet [x] stays in scope?

The snapshot below was recalculated on 4 September 2026 from M1–M39. It includes v12 hardening, continuous, next-SemVer, v13 and v13-research rows. M31 is excluded because the tracker explicitly assigns aerospace/autonomous/defence work to a separately governed future programme rather than the general v12/v13 framework suite. Detailed crate-roadmap checkboxes are not added again: they overlap with and decompose these canonical milestones, so a raw sum would double-count work.

StateMilestonesShare of the 38-milestone horizon
[x] bounded completion513.2%
[~] useful but incomplete foundation2463.2%
[ ] not implemented923.7%
Total in scope through v1338100%

Two calculations are intentionally retained:

  • Strict closure: 5/38 are closed, so 86.8% remains open (33 milestones). This is the correct answer when a partial milestone counts as unfinished.
  • Weighted engineering maturity: (5 + 24 × 0.5) / 38 is 44.7% complete, leaving 55.3% equivalent work. That remainder is the nine untouched milestones (23.7 percentage points) plus the unfinished half of the 24 partial milestones (31.6 points).

This is a scope/maturity indicator, not a duration estimate. Provider accounts, physical hardware, store acceptance, fiscal homologation, independent audits and research-grade cryptography cannot be completed by repository code alone. The 55.3% must not be added to the historical-claim campaign or the v12 release checklist because those lenses substantially overlap.

AI-native vision, without absolutes

The original goal of becoming an AI-native Rust framework suite is preserved as a design ambition, not a historically provable “first” claim. The dedicated AI maintainability and project-building roadmap defines the post-v12-RC acceptance work for generated instructions, bounded context, golden tasks and reproducible model evaluation.

  1. “Zero Runtime Magic, Pure Compilation”: derives, typed routes, and compiler diagnostics can make AI-assisted changes easier to inspect. (Partial and worth pursuing as an architectural preference; literal zero magic, “zero hallucinations,” and instant correction are not promises any framework can make.)
  2. Context-rich scaffolding: generated projects should receive a maintained AGENTS.md/AI ruleset describing the actual selected blueprint. (Partial and worth implementing; do not document .ai-rules or .cursorrules as generated until the generator and snapshots prove it.)
  3. Structured system discovery: a versioned schema should expose active routes, controllers, models, policies, and source locations. (Partial and worth completing; the CLI can inspect rullst-schema.json, but generation and freshness must become an end-to-end contract.)

Preserved extraordinary capability decisions

These items were previously easy to mistake for shipped functionality. They are kept deliberately, with the opinion requested for each gap. The capability ledger contains the more detailed evidence and acceptance boundaries.

Architecture and product-contract ambitions

  • Runtime-only Core with optional ORM (implemented in current hardening — bare Core no longer selects SQLx/ORM, orm and queue-sqlite are independent, Studio/Nexus opt in explicitly, and the application umbrella retains ergonomic database defaults).
  • One canonical security stack (partial — worth treating as high priority; keep policy/middleware in rullst-security and only minimal bootstrap contracts in Core so WAF, headers, and telemetry cannot drift).
  • Static dispatch everywhere (partial — not worth forcing absolutely; generic fast paths are valuable, but runtime-selected providers legitimately need a documented dynamic-dispatch boundary).
  • Every production source file below 500 lines (partial — worth continuous responsibility-based refactoring, but it is a design target rather than a release claim and large test fixtures may need a looser limit).
  • Uniform #[non_exhaustive], fallible builders, and impl Into<String> (partial — worth completing incrementally under SemVer review; a mechanical mass rewrite is not worth breaking consumers).
  • Zero lock-in, zero panic/crash, zero latency/allocation, 100% memory safety, and 100% Pure-Rustls ([!] Do not promise as absolutes — migration tools, scoped zero-panic linting, benchmarks, a tiny documented unsafe allowlist, and a feature-specific transport inventory are all worth maintaining).
  • Framework-wide “production-ready” badge ([!] Do not promise as one boolean — worth publishing stability per crate/capability because routing can be stable while live fiscal and hardware integrations remain unavailable).
  • A first-party load balancer embedded in every application ([!] Do not make the default — an opt-in rullst-gateway process is worth researching for self-hosted deployments, but application serving and edge proxying need independent failure, upgrade and privilege boundaries. Matching a managed cloud load balancer’s global infrastructure or SLA is not a repository-code claim).
  • Static competitor matrix claiming other frameworks lack capabilities ([!] Do not maintain without dated sources — comparative research and a reproducible benchmark repository are worthwhile; timeless absence claims are not).

Security, identity, and compliance

  • Full WebAuthn/FIDO2 conformance (partial — absolutely worth completing before a stable passkey claim, preferably with an audited library or normative conformance suite).
  • Zero-downtime key rotation and Cloud KMS (not implemented end to end — worth implementing through provider-neutral envelope/key-version contracts and named KMS adapters, not by embedding custody in the framework).
  • Adaptive WAF and eBPF kernel threat containment (not implemented — worth research only as opt-in, platform-specific defense in depth; not worth making a portability or complete-protection promise).
  • Anti-timing user-enumeration guard and Prompt Shield v2 (implemented foundations — worth keeping and testing, but timing equalization and heuristic prompt filtering cannot guarantee elimination of every side channel or injection technique).
  • External reputation feeds, verified audit feeds, and SIEM delivery for Threat Radar (partial — worth pluggable connectors; never render a source as healthy or verified unless it is connected and current).
  • Studio automatically stripped from every release at zero cost ([!] Do not promise — explicit feature selection and route mounting are worth documenting; a universal debug/release assumption is not).
  • Distributed rate limiting and durable tamper-evident audit storage (partial/not implemented — worth pluggable Redis and append-only sink backends with atomicity, tenant namespacing, retention, and verification tests).
  • Automated SBOM, SPDX/CycloneDX, cargo-vet, signed provenance, and advisory governance (partial — worth making release gates; not equivalent to SLSA Level 3 or organizational certification without independent evaluation).
  • Loom/Shuttle, Kani/Miri, mutation, fuzz, and unsafe governance (partial — worth scoped blocking suites plus a reviewed cargo-geiger inventory; a full mathematical proof of the whole framework is not worth claiming).
  • An IDOR scanner that proves authorization ([!] Do not promise proof — the AST scanner is worth keeping as a heuristic warning tool, paired with route-level ownership and cross-tenant negative tests).
  • DevSecOps git-hook installer (partial — hook:install writes pre-commit and Conventional Commit hooks; worth adding backup/idempotency/permission tests, while CI remains authoritative because local hooks are bypassable).
  • Automatic SOC 2/ISO/FedRAMP PASS reports ([!] Do not implement as an unconditional verdict — evidence export is worthwhile; certification covers an organization and deployment, not a crate).

Fiscal, payments, messaging, storage, and mail

  • Live NFS-e Nacional with PKCS#12, XML C14N/XMLDSig, XSD validation, mTLS, official rejection parsing, and SEFIN homologation (not implemented — an extraordinary and worthwhile Brazilian-market program, but only as a dedicated maintained fiscal workstream with official homologation and independent crypto validation).
  • Alipay RSA2 and uniform live support across every advertised gateway (not implemented/partial — worth only with provider sandbox access, demand, and a method-by-method capability matrix; adapter names must not imply every payment, subscription, payout, portal, tax, and webhook method exists).
  • Static fee/settlement/tax tables and “zero-cost invoicing” ([!] Do not promise — transparent links to current provider terms are worthwhile, but framework docs cannot erase certificate, accounting, infrastructure, support, compliance, or changing commercial costs).
  • Durable cross-instance webhook replay/idempotency (partial — worth a pluggable database/Redis uniqueness contract before multi-instance production billing).
  • RabbitMQ, Kafka, Redis Streams, NATS JetStream, SQS/SNS, and GCP Pub/Sub (remote adapters not implemented — the separate rullst-messaging crate now provides the bounded envelope, in-memory broker, durable local SQLite adapter and common contract foundation; add providers only after their delivery semantics pass provider-specific restart and fault evidence).
  • S3, Cloudflare R2, and image resizing (not implemented — worth isolated optional storage/media crates with official signing, multipart/retry semantics, strict path/pixel limits, deterministic mocks, and fuzzing).
  • Mailgun, Brevo, MailerSend, Plunk, and Scaleway transports (not implemented — worth demand-driven adapters only when each has a maintainer and passes the shared offline/live mail contract suite).

IoT, edge, AI, and critical systems

  • MQTT 5, CoAP, Sparkplug B, CAN/J1939, LoRaWAN, GPIO/I2C, real firmware download/flashing/rollback, and hardware-in-the-loop CI (not implemented — worth separate transport and target-hardware packages after named boards and interoperability environments are selected).
  • Hardware HSM/secure-element and NIST ML-KEM/PQC backends (not implemented; simulators are experimental — worth audited adapters for named hardware and protocols, never home-grown crypto presented as secure hardware).
  • Autonomous AI admin, NL-SQL writes, self-healing code/schema, and DevOps mutation (not implemented as a safe production contract — read-only advice, dry runs, and human-approved changes are worthwhile; default autonomous production mutation is not).
  • Native JSON Schema enforcement on every LLM (partial — capability-typed support is worth completing; parseable JSON must remain distinct and providers that cannot enforce a schema should return UnsupportedCapability).
  • Any local model over any arbitrary HTTP API ([!] Do not promise — named Ollama and a capability-declared OpenAI-compatible local/cloud adapter are implemented, while arbitrary APIs differ in authentication, streaming, tools, schema, and error semantics and use the public provider trait).
  • Automatically air-gapped/zero-leak AI ([!] Do not promise — local endpoints can be useful, but the host network, logs, model runtime, and telemetry determine the real data boundary).
  • Aerospace/autonomous/defense framework (not implemented — the research is inspiring, but it is not worth conflating safety certification with web framework quality; incubate it independently if expertise, hardware, and governance become available).

Execution plan aligned with gpt.md §15

Phase 0 — containment and truthful boundaries

  • Keep live Fiscal, unfinished IoT integrations, S3/R2, Alipay, and other absent provider paths fail-closed with typed Unsupported results.
  • Keep Nexus fail-closed, generated credentials absent, production configuration validated, webhook secrets mandatory, local storage confined, and the release workflow blocked until its dependency order and evidence agree.
  • Label every capability implemented, partial, experimental, not implemented, or intentionally unsupported; never delete the vision to obtain truthful docs.

Phase 1 — kernel security and reliability

  • Complete environment precedence, atomic/fallible DB initialization, APP_KEY policy, WebAuthn conformance, content-aware DLP/PII, signed-webhook composition, trusted proxies, tenant isolation, CSWSH, bounded workers, scheduler shutdown, and the production-path zero-panic policy.

Phase 2 — product integrity and scaffolding

  • Compile all generated projects in temp directories; enforce server-side Nexus policy; use real or explicitly unavailable Studio telemetry; and keep offline mocks deterministic without allowing live endpoints to fail open.

Phase 3 — architecture and contract

  • Keep the new Core/ORM feature boundary regression-tested, consolidate the canonical security stack, standardize public API evolution, and split OAuth identity from future messaging adapters. The umbrella feature map is now complete and must remain covered by its powerset test.
  • Implement ambitious providers only where a maintainer, conformance suite, and real interoperability environment exist.

Phase 4 — release engineering

  • Require formatting, strict Clippy, full workspace tests, exclusive DB-feature checks, generated-project checks, fuzz tiers, unsafe review, package preflight, SBOM/advisory evidence, provenance, and topological publishing for the exact tag.

Release strategy

VersionStatusHonest scope
v12.0.0[ ] Unreleased hardeningPermit only bounded implementation tied to the audited A-grade gate (IoT may remain B), then freeze framework features, close the remaining release gates and prove the result against an exact public RC. Version numbers in manifests do not make a release complete.
v12.0.x[ ] Maintenance onlyBackward-compatible fixes for confirmed defects or security issues; no new capability programme.
v13.x[ ] Next feature lineCompatible and breaking improvements move together into the next deliberate cycle: generated-project coverage, auth/session consolidation, typed SDKs, selected adapters, security-stack consolidation and research-heavy architecture all require fresh acceptance boundaries.

The framework may call a milestone implemented only when the same commit passes the repository’s formatting, strict lint, full-test, feature-matrix, security, and packaging gates. Performance numbers must cite a reproducible benchmark; security and compliance claims must state their threat model and evidence scope.


"All glory and honor to God יהוה in the name of Yeshua the Messiah (Jesus Christ)."

💡 Rullst CLI - Full Command Reference

The Command Line Interface (cargo-rullst) scaffolds projects, invokes build tools, and provides bounded static-analysis and deployment helpers.

The CLI’s --help output is authoritative for the installed version. This page documents the principal version 12 commands and their security boundaries.


🏗️ 1. Project Initialization & Maintenance

cargo rullst new <name>

Creates a Rullst project from scratch. Version 12 intentionally generates one audited application architecture: Active Record for database-backed code and server-rendered html! views enhanced with HTMX for full-stack pages. The interactive wizard prompts for the product capabilities that materially change the generated application:

  • Starter Blueprint: Blank Starter, Portfolio, LMS Platform, SaaS App, Blog/Press, ERP Pocket.
  • Persistence: a primary relational backend (SQLite, PostgreSQL, MySQL, MariaDB, or bounded Turso-primary for blank/API) plus optional Turso/libSQL, MongoDB, DuckDB, SurrealDB, and Qdrant capabilities. The optional selector accepts zero or more choices and omits capabilities already selected by the primary profile or flags. Specialized adapters remain separate from SQLx Active Record.
  • Application profile: HTML blueprints use the audited html! SSR/HTMX path; --api uses the headless JSON path. Repository, LiveView, Wasm Island, Pico.css and Tera foundations remain application-owned APIs and are not presented as equivalent v12 generated profiles.
  • Arguments:
    • <name>: The folder and package name (e.g., my_startup).
  • Optional Flags:
    • --api: Scaffolds a headless JSON API from the Blank starter (no HTML view rendering); SQLx-specific product blueprints reject it instead of ignoring it.
    • --docker: Adds the current multi-stage Dockerfile packaging scaffold; Compose services and deployment hardening remain explicit project work.
    • --turso: Adds the direct Hrana HTTP v3 Turso/libSQL adapter, checked migrations, and its real-SQL offline development fallback to the selected primary backend. It does not imply transparent replication.
    • --mongodb: Enables typed MongoDB document CRUD and its deterministic offline store.
    • --duckdb: Enables in-process DuckDB analytics; the optional native dependency increases the first build time.
    • --surrealdb: Enables SurrealDB HTTP document CRUD and bounded read-only graph queries.
    • --qdrant: Enables bounded dense-vector Qdrant operations and generates empty/mock_*-compatible environment fields; it is additive, not the SQL primary.
    • --nix: Adds flake.nix and .envrc (direnv) starting points; reproducibility still depends on pinned inputs and external services.
    • --buildah: Adds rootless Buildah container-build files where supported.
    • --default: Uses deterministic non-interactive defaults, intended for CI and reproducible scaffolding.
    • --blueprint <blank|lms|saas|blog|portfolio|erp>: Selects a blueprint when used with --default.
    • --database <sqlite|postgres|mysql|mariadb|turso>: Selects the primary relational backend with --default; network databases must be configured before migration bootstrap. Turso-primary currently supports the blank/API starter and rejects SQLx-specific blueprints explicitly.
    • --no-database: Generates the blank blueprint without a primary relational database; it conflicts with --database and rejects database-dependent blueprints.
    • --ai: Enables the umbrella AI facade in the generated manifest.
    • --redis: Enables the umbrella Redis queue/cache/ORM capabilities and the direct ORM Redis feature.
    • --lms-modules <modules>: With --default --blueprint lms, selects a detached LMS profile. Version 12 currently accepts auth, auth,learning, or auth,learning,assessment; unsupported/duplicate combinations and the profiles’ not-yet-supported hot reload fail explicitly. Omitting the flag generates the complete LMS starter.
    • --skip-initial-migration: Generates the project without running the best-effort initial database migration. Run cargo rullst db:migrate explicitly after configuring the database.

Without --skip-initial-migration, project creation performs the first Cargo build before applying migrations. A clean first build can take several minutes, especially for the larger LMS/SaaS profiles; the animated status remains visible while Cargo is working. Later migration and server runs reuse that project-local build cache.

For example, the release gate can generate a SaaS starter without prompts or network-dependent bootstrap work:

cargo rullst new packaged-saas --default --blueprint saas --skip-initial-migration

A complete deterministic profile can pin every supported v12 generation axis:

cargo rullst new operations-portal --default --blueprint erp \
  --database mariadb \
  --ai --redis --skip-initial-migration

Generated SQLx applications disable the umbrella dependency’s default features and select exactly one strict primary profile (strict-sqlite, strict-postgres, or strict-mysql; MariaDB uses the MySQL protocol). This prevents an implicit SQLite default from masking the chosen backend.

Generated-project verification boundary

The repository does not treat template rendering as sufficient evidence. A structural contract materializes 18 internal blueprint/profile shapes (nine public directly linked layouts plus nine legacy DLL layouts retained for regression) and checks paths, Rust syntax and manifests. A slower eight-case set crosses every blueprint, hot and non-hot layouts, database/API boundaries and a release build, runs every generated test target, and constructs the public router of each hot-reload project using offline-safe defaults. A separate seven-case test invokes the public cargo rullst new binary and verifies exact feature selection for SQLite, PostgreSQL, MySQL, MariaDB, AI, Redis, Turso, MongoDB, DuckDB, SurrealDB and Qdrant across all six public blueprints plus a polyglot profile. The invocation starts outside the source checkout, proving that an unpublished pre-release CLI retains its exact matching checkout as a path source instead of requesting unavailable registry packages.

The public polyglot profile uses cargo check in that CLI-level set because a second bundled-DuckDB test build adds no adapter behavior and can consume several GiB on small machines. DuckDB, MongoDB, SurrealDB, Turso and Qdrant runtime behavior is exercised by their dedicated ORM matrices instead. These gates prove reproducible local generation and bounded offline construction; they do not prove provider accounts, production deployment, browser behavior or application-specific authorization.

The bounded LMS foundation omits assessment, gamification, automation and notification files while retaining authenticated catalog/enrollment/progress:

cargo rullst new academy-identity --default --blueprint lms \
  --lms-modules auth --skip-initial-migration

cargo rullst new academy-foundation --default --blueprint lms \
  --lms-modules auth,learning --skip-initial-migration

cargo rullst new academy-assessment --default --blueprint lms \
  --lms-modules auth,learning,assessment --skip-initial-migration

The assessment foundation adds owner-only quiz presentation and server-authoritative, idempotent grading with bounded attempts. It deliberately does not pull in scoring, leaderboards, achievements, automation, outbox, or notification modules.

cargo rullst upgrade

Plans or applies a transactional application upgrade. The target defaults to the exact installed cargo-rullst version; --to <VERSION> accepts an exact version in the same major release train as that CLI.

# Human-readable plan; no writes or dependency resolution
cargo rullst upgrade --dry-run

# Versioned machine-readable plan
cargo rullst upgrade --dry-run --json

# Backed-up apply + cargo fix + cargo check
cargo rullst upgrade

# Deliberately inspect a failed partial migration instead of auto-rollback
cargo rullst upgrade --keep-on-failure

# Recover a persisted snapshot, including after interruption
cargo rullst upgrade --restore target/rullst-upgrades/<run-id>

The CLI uses Cargo metadata to scope workspace manifests, preserves TOML comments/order, updates normal, inline, workspace, target-specific and renamed Rullst dependencies, and reports unversioned path/git entries. Before applying, it snapshots workspace manifests, the root Cargo.lock, and Rust sources under target/rullst-upgrades/; a failed Cargo gate restores them by default. The reports use the rullst.upgrade-plan.v1 schema and include version-selected source findings.

Process-level fixtures select the rule catalog independently for documented v5, v6 and v11 origins, verify atomic restoration across multiple workspace members, retain a deliberately failed edit only with --keep-on-failure, and restore that retained snapshot on demand. Symlinked Rust sources are rejected before a transaction begins. This is recovery evidence for the bounded file and Cargo operation; it is not an automatic application, database or deployment migration.

The command does not install the CLI globally, rewrite Axum/SQLx/Tokio imports, run database migrations, modify secrets or authorization, validate live providers, or replace the project’s test suite. Follow the assisted upgrade tutorial and the relevant v12 migration guide.

cargo rullst pkg <action> [name]

Manages third-party community packages and extensions conforming to the RullstPackage trait standard.

  • Subcommands:
    • add <package_name>: Injects a community extension dependency (e.g., cargo rullst pkg add rullst-auth) into Cargo.toml.
    • list: Scans and lists all active rullst-* community extensions installed in your project.

🛠️ 2. Architecture Scaffolding (make:*)

Rullst generators write the files described under each command. Some commands also register modules and refresh .llms.txt; this is command-specific, and a failed best-effort context refresh does not roll back generated source. Review the diff and run cargo check after scaffolding.

cargo rullst make:resource <name>

Scaffolds the bounded starting files for a CRUD resource in one command: a Model (src/models/<name>.rs), Migration (migrations/<timestamp>_create_<name>s_table.rs), Controller (src/controllers/<name>.rs), and HTML view placeholders (views/<name>/index.html and views/<name>/form.html). It does not infer application fields, register routes, establish ownership/RBAC, or turn the placeholder handlers into a complete authorized CRUD implementation. Mount the routes behind the canonical security baseline, render request-scoped CSRF tokens in state-changing forms, complete validation/persistence, and run the application’s authorization-negative tests.

  • Arguments: <name> (e.g., Product or product).
  • Optional Flags:
    • --api: Scaffolds a headless JSON API resource controller instead of HTML views.

cargo rullst make:controller <name>

Generates a new Controller in the src/controllers/ directory. It creates placeholder CRUD methods (index, show, store, update, delete) and registers the Rust module in main.rs when that file exists; it does not add application routes automatically.

  • Arguments: <name> (e.g., UsersController or users).
  • Optional Flags:
    • --api: Instead of returning HTML Views via the html! macro, the generated methods will automatically extract/return Json<T>.

cargo rullst make:model <name>

Creates a model struct in src/models/ with the ORM annotations. SQLx projects receive FromRow plus Orm; Turso-primary projects receive #[derive(rullst_orm::Orm)] #[orm(backend = "turso")] and an i64 primary key. Backend detection reads the generated manifest and does not treat an additive --turso integration as the primary ORM.

  • Arguments: <name> (e.g., BlogPost).
  • Optional Flags:
    • --migration or -m: Simultaneously generates a reversible migration with the correctly pluralized table name.

cargo rullst make:chat-session

Adds application-owned conversational memory for the project’s primary ORM. It generates and registers ChatSession and ChatMessage, a reversible migration, and StatefulChat. SQLx and the bounded Turso-primary profile receive backend-specific code; the command also enables the orm and ai umbrella features if necessary.

cargo rullst make:chat-session
cargo rullst db:migrate

Save the generated ChatSession before constructing StatefulChat. Each service instance serializes concurrent sends, restores at most the newest 100 messages in chronological order, persists the user message before provider dispatch and persists the assistant response only after success. Database and provider failures are returned as StatefulChatError; they are never silently discarded. Multi-process ordering, tenant authorization, retention and deletion remain application responsibilities. The command refuses to overwrite an existing chat scaffold.

cargo rullst make:middleware <name>

Generates a standard Axum/Rullst Middleware struct in src/middlewares/. Perfect for injecting headers, checking authentication, rate limiting, or logging.

cargo rullst make:island <name>

Creates a frontend interactive “Islands Architecture” component (similar to Fresh or Astro) in src/islands/. It generates the Rust infrastructure that, during build, will be transparently compiled to WebAssembly to run in the browser.

cargo rullst make:worker <name>

Creates an asynchronous background worker in src/workers/ against the queue backends currently implemented in Core (memory, SQLite, and optional Redis). RabbitMQ is not generated by this command.

cargo rullst make:migration <name>

Generates a timestamped reversible Rust migration for the project’s primary backend. SQLx projects use the schema DSL; Turso-primary projects use TursoMigration and parameterized TursoStatement values, and regenerate a fallible typed migration registry.

cargo rullst make:billing

Scaffolds a SaaS billing starting point with subscription models, authenticated billing routes, and signed-webhook integration points. Provider credentials, tenant policy, and deployment behavior still require application configuration.

cargo rullst make:mail <Name>

Scaffolds a registered transactional mailable. --welcome, --reset, --otp and --invoice select the bounded built-in variants; without a flag the command generates a custom message type. It enables the umbrella mailer feature, uses the rullst::mail facade, escapes dynamic HTML and refuses invalid identifiers, path traversal or an existing target. Delivery credentials, URL semantics, tenant policy and provider operation remain application responsibilities.

cargo rullst make:mail-invoice [Name]

Generates FiscalInvoiceEmail by default and enables mailer plus capital. The result supports an international commercial receipt and an NFS-e message constructed from typed FiscalResponse provenance. An OfflineMock is always rendered as [PREVIEW — NOT AUTHORIZED]; the generator cannot turn local DPS, XSD, or XMLDSig validity into a tax authorization. A custom valid struct name may be supplied positionally.

cargo rullst make:mail-dunning [Name]

Generates PaymentDunningEmail by default with explicit gentle D+1, action-required D+3, and service-status D+7 stages. The application remains responsible for calculating the due state, scheduling delivery, enforcing its disclosed billing policy, and reconciling payment. The generated build path runs the mandatory pre-flight and rejects dangerous links.

cargo rullst make:jwt

Injects a pre-configured boilerplate Middleware into your project for strict JWT Authentication (verifying Bearer tokens in the Authorization header).

cargo rullst make:cors

Generates and configures full CORS (Cross-Origin Resource Sharing) options in your project with recommended security defaults (blocking unused methods, restricting origins).

Projects generated by older CLI versions retain the middleware that was copied into their source tree and must be reviewed manually. Follow the CORS scaffold security advisory to detect origin reflection/wildcards and migrate to the current fail-closed allowlist.

cargo rullst make:omni

Generates a Tauri/Omni shell and development configuration for desktop, Android or iOS. Interactive use prompts for platforms. Automation can select one or more targets deterministically:

cargo rullst make:omni --platform desktop
cargo rullst make:omni --platform android \
  --backend-url http://10.0.2.2:3000 --identifier com.acme.myapp
cargo rullst make:omni --platform ios \
  --backend-url https://app.example.com --identifier com.acme.myapp
cargo rullst make:omni --platform desktop,ios \
  --backend-url https://app.example.com --identifier com.acme.myapp \
  --product-name "Acme App" --app-version 1.2.3

Mobile generation requires an explicit backend URL. HTTPS is required except for the bounded localhost/Android-emulator development hosts; embedded credentials are rejected. Mobile also requires an application-owned lowercase reverse-DNS --identifier; reserved framework and com.example placeholders are rejected. --product-name and --app-version are optional validated overrides and otherwise inherit the host package metadata. Desktop-only development can derive a documented com.example placeholder, which must be replaced before distribution.

The generator installs an exact Tauri npm CLI, creates platform icons, initializes mobile targets non-interactively, emits a restrictive local CSP and fails if a requested prerequisite step fails. iOS initialization requires macOS and Xcode. Native-side navigation is restricted to the packaged bootstrap and the configured backend’s exact origin. Remote pages receive no privileged Tauri IPC surface; cross-origin OAuth/external-link behavior needs a separate reviewed system-browser/deep-link integration.

The canonical product remains the Rullst web application and the generated client packages that application; it does not by itself implement native plugins, offline synchronization, production network policy, release signing, privacy declarations, physical-device validation, Play Store/App Store publication or review acceptance. The generated README contains the application-owned distribution checklist. Path-aware repository workflows generate fresh desktop, Android and iOS shells and compile only their declared targets; those runs are packaging evidence, not store, physical-device or universal behavior guarantees.

cargo rullst make:iot <DeviceName>

Scaffolds and registers a telemetry-only IoT module in src/iot/ using the public rullst::iot::SensorTelemetry facade, and enables the iot feature in the application manifest. Unsafe identifiers/path traversal and existing target files are rejected. It does not install an MQTT/CoAP transport, HAL, firmware, or claim broker connectivity.

cargo rullst make:k8s

Scaffolds cloud-native Kubernetes manifest files in the k8s/ directory (deployment.yaml, service.yaml, configmap.yaml, hpa.yaml, ingress.yaml, and all-in-one.yaml) pre-configured with liveness (/health) and readiness (/ready) HTTP probes.

cargo rullst make:scalar

Scaffolds a Scalar API Documentation controller at src/controllers/docs_controller.rs. The interactive view loads a pinned CDN asset; its local fallback is status-only and final CSP/network policy belongs to the application.

cargo rullst make:live <ComponentName>

Scaffolds a LiveView-style server component at src/live/<name>.rs using a WebSocket and HTMX out-of-band swaps. Application JavaScript may be unnecessary, but HTMX remains client-side JavaScript and the generated transport requires origin, reconnect, and backpressure review.

cargo rullst make:grpc <ServiceName>

Scaffolds a new gRPC service implementation in src/grpc/<name>.rs and Protobuf schema definition in proto/<name>.proto powered by tonic.

cargo rullst deploy [--platform <fly|railway|render|vps>]

Guided deployment helper that generates cloud manifests (fly.toml, railway.json, render.yaml, or docker-compose.prod.yml) and invokes the selected provider CLI where supported. Credentials, migrations, availability, DNS/TLS and rollback remain operator responsibilities.

cargo rullst auth

Creates an authentication starting point in your codebase, including:

  • User model and migration with asynchronous Argon2 password hashing.
  • Auth Controllers (Login, Registration, Logout).
  • Session or Token Middleware.
  • Complete HTML Views for Login and Signup (unless --api is used).

cargo rullst make:mfa

Scaffolds a 2FA TOTP Multi-Factor Authentication controller at src/controllers/mfa.rs providing RFC 6238 Base32 secret generation, 6-digit TOTP code validation, and otpauth:// QR URI generation.


🗄️ 3. Database and Migrations (db:*)

cargo rullst db:migrate

Analyzes the internal _rullst_migrations table in your database and executes all SQL files in the migrations/ directory that haven’t been run yet.

cargo rullst db:rollback

Reverts the last applied migration batch. It looks at the latest executed batch, extracts the “Down” section of the SQL file, and executes it to undo changes and remove tables/columns.

cargo rullst db:status

Checks the database connection and prints a table in the terminal comparing the local migrations/ folder with the database status, detailing exactly what has been run and what is pending.

cargo rullst db:seed

Populates the database using seeder files created in src/db/seeds.rs, ideal for injecting an initial administrator or dummy testing data.

cargo rullst studio

Launches the local developer Studio on port :5555. Treat it as a privileged development tool; do not expose it publicly without an independently reviewed authentication, authorization, and TLS boundary.


🧠 4. Analyzers and Code Generators (generate:*)

cargo rullst generate:openapi

Reads recognizable route and Rustdoc patterns and generates an OpenAPI V3 draft. Dynamic routes, custom extractors, and semantic constraints may require manual edits; validate the result with an OpenAPI validator before publishing it.

cargo rullst generate:ts

Scans supported models and DTOs and emits a TypeScript file (sdk.ts). Generated types reduce duplication but do not replace compatibility tests for serialization and API behavior.

cargo rullst generate:diagram

Analyzes primary and foreign keys defined in your Models and exports a diagram.md file containing Mermaid.js code, visually generating an Entity-Relationship (ER) diagram.

cargo rullst generate:models / cargo rullst make:models-from-db

Connects to an existing database and generates reviewable starter structs from the tables and columns visible in SQLite or the current PostgreSQL/MySQL schema. Table lookups are parameterized and SQL identifiers are allowlisted. Table module names are normalized, while collisions and database columns that would require an unsupported ORM field remapping fail before the output directory is written. The bounded type mapping falls back to String; review keys, relations, custom types, schema selection and generated files before compiling or replacing application models.

  • Required Flags:
    • --driver: postgres, mysql, or sqlite.
    • --url: The complete connection string.
  • Optional Flags:
    • --output: Where to save the generated structs (Default: src/models).

cargo rullst generate:ai-context

Creates .llms.txt, a compact summary of project structure, conventions, and dependencies for coding assistants. It is context, not a guarantee that a model will understand or modify the project correctly.

cargo rullst audit [--ai] [--compliance] [--idor]

Runs bounded source/configuration checks and can invoke installed dependency scanners. Static findings require human review and are not a penetration test or compliance certification.

  • Flags:
    • --ai: Enables AI Sentinel suggestions for threat mitigation.
    • --compliance: Generates an evidence-oriented control report with PASS, FAIL, SKIPPED, or NOT_EVALUATED; it does not confer SOC 2 or ISO 27001 certification.
    • --idor: Fails on parameterized routes without an adjacent // rullst-access: public|owner|role|admin — reason classification and the recognized guard required by non-public classifications. public is accepted only for recognized GET routes. This bounded heuristic cannot prove domain authorization correctness.

cargo rullst eject [--force] [--output <path>]

Generates an inspectable Axum/Tokio entry-point snapshot (src/ejected_main.rs) for the supported abstractions. Review it and run cargo check; optional subsystems may still depend on Rullst crates.

  • Flags:
    • --force: Overwrites src/main.rs directly instead of creating src/ejected_main.rs.
    • --output <path>: Specifies a custom output path for the ejected file.

cargo rullst inspect [target]

Statically expands and inspects macro code or structural definitions directly in the terminal without starting a server. Useful for debugging proc-macro output, reviewing route tables, and validating database schemas.

  • Arguments:
    • [target]: The item or file to inspect:
      • route or routes: Renders the active route table (methods, paths, and handlers).
      • model or models: Renders ORM struct models and field attributes.
      • schema: Outputs the project’s structural JSON schema (rullst-schema.json).
      • <path/to/file.rs>: Displays the first 40 lines of any target Rust file with line numbers.

🚀 5. Development, Infrastructure, and Build

cargo rullst dash

Opens the Ratatui development control surface in an interactive terminal. The dashboard reports the probed application port, supervised auto-reload state, the child process exit state, and the configured database profile; it does not label a database as connected merely because a URL exists. Logs and input queues are bounded, ANSI control sequences are removed, terminal state is restored on error, and the owned application process is stopped and reaped when the dashboard exits.

The layout adapts to narrower terminals and provides these keyboard controls:

  • o: open the application.
  • s: probe the loopback Studio endpoint and open it only when reachable.
  • d: open existing Scalar docs. Missing files produce explicit cargo rullst make:scalar guidance rather than silently modifying the project.
  • m: run db:migrate asynchronously and report its real exit result.
  • /: search both log panes; f cycles all/warning+error/error filtering.
  • Tab: switch the focused log pane; arrows and Page Up/Page Down scroll it.
  • c: clear dashboard logs; q or Esc: exit.

The animated neon palette is enabled only for an interactive terminal. Set RULLST_REDUCED_MOTION=1 to keep colors with static rendering, or NO_COLOR=1 for a color-free, static interface. Non-interactive automation should use cargo rullst dev; dash fails clearly when no terminal is attached.

cargo rullst dev

Builds and starts a directly linked application. Saving source, static assets, templates, Cargo.toml, Cargo.lock, Rullst.toml or .env schedules a coalesced rebuild. A failed build leaves the current application running. A successful build creates an owned executable snapshot, stops the previous process and starts its replacement. The snapshot avoids locking Cargo’s build output on Windows. Initial migrations run before startup; later migrations remain an explicit command.

The same-origin browser client polls an opaque process-generation marker and refreshes only when a different generation serves successfully. This is enabled only in debug/development. Readiness verifies that marker, not just an open port. Changing the configured port requires restarting the CLI. In-memory state and unsaved browser state reset during reload. The process receives a bounded shutdown interval before forced termination; this is a development facility.

No scaffold question is required: dev and dash enable auto-reload, while cargo run runs the application normally. The legacy --hot-reload scaffold flag is rejected in v12 because DLLs can split ORM/Tokio globals. Existing legacy scaffolds can use their directly linked router; the supervisor removes HOT_RELOAD from its child’s environment.

See Supervised Development Auto-Reload for limitations, failure recovery and the v13 architecture decision.

  • Optional Flags:
    • --ts-sync: Automatically watches controller and model file changes and syncs the TypeScript client SDK (sdk.ts) live during development.

cargo rullst build:client

Builds the library for wasm32-unknown-unknown, runs wasm-bindgen, and writes a separate static/rullst-islands.js hydrator that awaits binding initialization. It parses Cargo.toml, merges the required cdylib crate type without replacing existing library crate types, and honors an explicit lib.name. The command checks/installs the Rust target and wasm-bindgen-cli; any failed tool step aborts. Bundle size and browser performance depend on the generated application and must be measured.

  • Flags: --debug (Avoids extreme minification so you can inspect and debug Wasm sourcemaps).

cargo rullst build

Creates the monolithic final Production binary of the backend and executes pre-compression tools (GZIP and Brotli) on your static assets.

  • Flags: --debug (Compiles with debug information, generating a larger binary).

cargo rullst dockerize / cargo rullst nixify

Injects infrastructure files (Dockerfile or Nix Flake) directly into a pre-existing project (similar to the flags used in new).

cargo rullst foundry:init

Generates the Foundry.toml deployment manifest at the project root containing SSH access settings and environment variables for a compatible systemd-based Linux VPS. It adds Foundry.toml to .gitignore; operators must still verify that secrets were never committed.

cargo rullst foundry:deploy

Executes an SSH deployment pipeline: local release build, remote directory and systemd provisioning, scp transfer, environment/Caddy configuration, service restart, and a bounded remote-local /health probe. It requires a preinstalled, reviewed curl, systemd, and Caddy installation plus root or passwordless non-interactive sudo. Candidate files are staged under an application-specific /opt/rullst/<app> root, the Caddy configuration is validated, and .previous copies of replaced files are retained. The current command replaces the global /etc/caddy/Caddyfile; it does not perform a separate remote checksum, migrations, data backup, external reachability check, or automatic rollback. It does not guarantee zero downtime and does not support IPv6 SCP targets.

cargo rullst omni

Runs the generated Tauri development client after make:omni. Android/iOS require their official SDK/toolchain and a reachable backend.

  • Optional Arguments: <target> specifies where to run (e.g., desktop, android, ios).

🛡️ 4. Security, Compliance & System Diagnostics

cargo rullst audit

Executes bounded automated checks across recognized source, configuration, route, dependency, and local network patterns.

  • Optional Flags:
    • --ai: Enables autonomous AI Sentinel analysis with risk assessment and proactive remediation advice.
    • --compliance: Generates an evidence-oriented control report; it is not a SOC 2, ISO 27001, or transport certification.
    • --idor: Fails on parameterized routes without an explicit adjacent access classification. owner requires RbacGuard::authorize_owner_or_role; role requires a recognized role guard; admin requires RequireRoleLayer or NexusAuthPolicy::protect_router; public is restricted to recognized GET routes. Manual review and runtime negative tests remain required.
    • --geiger: Inventories unsafe in the dependency tree. Unsafe may be justified and requires review; the command does not prove a zero-unsafe invariant.
    • --sbom: Generates a standardized CycloneDX 1.5 JSON Software Bill of Materials (sbom-cyclonedx.json) with package SHA-256 checksums and license metadata.
    • --audit-ignore RUSTSEC-YYYY-NNNN: Passes one explicit, repeatable advisory exception to cargo audit. A successful run is reported as NO FINDINGS OUTSIDE EXCEPTIONS, not “no findings”; the caller must separately version, own, review, and expire every exception.
    • --network: Checks a bounded list of local ports/bindings for potentially exposed services; it is not a comprehensive network scan.

cargo rullst hook:install

Installs managed pre-commit and commit-msg wrappers. The first runs cargo fmt --all -- --check, strict workspace Clippy, and cargo rullst audit --idor; the second enforces Conventional Commits. Existing active hooks are moved to explicit .rullst-original backups and invoked first, while reinstalling the managed wrappers is idempotent. The command supports linked worktrees, fails clearly outside a Git worktree, and refuses a backup collision instead of overwriting it. These local hooks are bypassable by design; protected CI remains authoritative.

cargo rullst doctor

Runs bounded system and toolchain diagnostics for Rust MSRV (>= 1.96.0), linters, cargo-llvm-cov, cargo-audit, cargo-geiger, cargo-deny, cargo-mutants, kani-verifier, and Docker Engine, and reports detected or missing components.

cargo rullst inspect [target]

Expands macros and displays structural insights in the terminal:

  • cargo rullst inspect route: Lists all registered HTTP, WebSocket, and gRPC endpoints.
  • cargo rullst inspect model: Inspects ORM model columns, primary keys, and relationships.
  • cargo rullst inspect schema: Displays the synchronized database schema.

🛠️ Quick CLI Cheat Sheet

# Create a new project with fast-linker scaffolding
cargo rullst new my_app

# Reverse-engineer ORM models from an existing database
cargo rullst make:models-from-db --driver postgres --url "postgres://user:pass@localhost:5432/mydb"

# Statically inspect routes, models, or schemas in the terminal
cargo rullst inspect route
cargo rullst inspect model

# Launch the visual Studio Dashboard (Data Browser, ER Diagram, Feature Flags)
cargo rullst studio

# Run the reviewed Foundry pipeline on a compatible, prepared VPS
cargo rullst foundry:deploy

GitHub CLI installation and safe login

The GitHub CLI (gh) lets a maintainer inspect workflow runs, pull requests, issues, Dependabot state, and Code Scanning alerts from a terminal. It does not gain access merely by being installed: access begins only after the maintainer completes GitHub’s browser authorization flow.

Official references:

Current Rullst workstation

On the Linux workstation used for the v12 release work, gh was installed for the current user at ~/.local/bin/gh. The downloaded GitHub release archive was verified against its official SHA-256 checksum. Confirm that the command is on the shell path:

command -v gh
gh --version

If the first command prints nothing, start a new terminal. For the current terminal only, this adds the user-local directory without changing system files:

export PATH="${PATH}:$HOME/.local/bin"

For another machine, use GitHub’s current official installation instructions. Common package-manager entry points are:

# macOS with Homebrew
brew install gh

# Windows with WinGet
winget install --id GitHub.cli

Linux repository commands vary by distribution and can change; copy them from the official Linux installation guide instead of an old blog post.

Browser login for Rullst maintenance

Run this command yourself in the terminal:

gh auth login --hostname github.com --git-protocol https --web --scopes security_events

GitHub will show a one-time code and open its authorization page. Verify that the browser is on github.com, sign in as the intended Rullst maintainer, read the requested permissions, and approve only if they match the task. The additional security_events scope allows the CLI to query Code Scanning data; GitHub CLI’s web flow also maintains its documented baseline scopes.

Do not paste an access token into chat, a repository file, shell history, an issue, or a commit. Do not use --insecure-storage. The browser flow asks the system credential store to keep the credential; if gh reports that no secure credential store is available, stop and configure one before continuing.

Verify the active account without printing its token:

gh auth status --hostname github.com --active

Never add --show-token to a command whose output may be shared. Once the status is healthy, a read-only check of Rullst’s open Code Scanning alerts is:

gh api 'repos/Rullst/Rullst/code-scanning/alerts?state=open&per_page=100' \
  --jq '.[] | {number, rule: .rule.id, severity: .rule.security_severity_level, url: .html_url}'

Authentication makes inspection possible; it does not authorize dismissing an alert, merging a pull request, changing repository settings, publishing a release, or modifying secrets. Those actions still require an explicit task and an evidence-based review.

Logout and revocation

Remove the local GitHub CLI session with:

gh auth logout --hostname github.com

The logout command removes the locally stored authentication entry but does not revoke the OAuth grant. To revoke it, open GitHub authorized applications, select GitHub CLI, review the impact on other machines, and choose Revoke Access.

After logout, confirm that this workstation no longer has an active session:

gh auth status --hostname github.com

Rullst CI/CD and Verification Contract

This document describes what the repository’s automation currently executes. It is not evidence that a workflow has passed for a particular commit. A green claim must always point to the GitHub Actions run, commit SHA, logs, and produced artifacts.

Last source-level review: 2026-09-11.

Status language

StatusMeaning
BlockingA failing command fails that workflow run. Branch protection still determines whether the check is required for merging.
Automated evidenceThe workflow runs automatically, but part of its result is external, uploaded, or deliberately non-blocking.
InformationalThe workflow is explicitly advisory and must not be described as a release gate. A manual trigger alone does not make a strict candidate check informational.
RoadmapThe idea is preserved, but this repository does not yet provide reproducible evidence for it.

The distinction matters: Kani, Miri, mutation testing, or a scanner can be very valuable without proving that the entire framework is panic-free, race-free, memory-safe, or compliant with a regulation.

Mainline execution model

The v12 dashboard and its automatic status badges are pinned to main. The continuous workflows accept pushes to main and pull requests targeting it, and expose workflow_dispatch where a safe rerun is useful. Superseded runs of these workflows are cancelled per workflow and ref so rapid development does not spend runner capacity proving an obsolete commit.

ci.yml deliberately treats the expensive operating-system matrix differently. Format and Clippy continue to give feedback on draft pull requests. The complete Linux/macOS/Windows test matrix, blocking line coverage, SemVer fan-out and CodeQL analysis start for a pull request only when it is ready for review, and it can always be requested manually. Each operating system executes eight parallel shards: the non-CLI workspace, ordinary CLI targets, the LMS contract, three public-profile groups and two generated-blueprint groups. The basic/relational/polyglot and foundation/product partitions retain every original case while bounding the longest Windows and macOS jobs. No test is omitted; this changes wall-clock scheduling rather than the assertions being executed. Hosted CI permits two nested compiler jobs for the otherwise serial generated profile/blueprint builds; constrained local runs retain their one-job default. Each CLI shard fetches the locked registry inventory before its generated applications prove that they compile without network access. After that reviewed commit is merged, the automatic main push repeats Linux rather than paying for the same macOS and Windows proof twice. A direct push to main therefore has Linux evidence only until a maintainer explicitly runs ci.yml; release candidates must use the manual full matrix when no successful ready-PR run points to the exact candidate tree.

The SHA-bound quality scorecard is generated only by a ready pull request or a manual full-matrix run. It is deliberately skipped on the Linux-only automatic main run, because that execution cannot honestly award cross-platform verification credit. Run ci.yml manually on a final main candidate to produce the exact-SHA release scorecard. Manual diagnostic runs may select one operating system and one test shard; those deliberately do not produce a full-matrix scorecard and do not replace final-candidate evidence.

GitHub executes schedule events from the repository’s default branch, so scheduled and continuous v12 evidence now share the active main source line. Tag publication remains deliberately unavailable through a manual button.

Manual and periodic execution map

Every verification workflow except the PR-context-only ai-sentinel-pr.yml and tag-only release.yml can now be started from Actions → select workflow → Run workflow. A manual run checks the selected branch’s current SHA; record that SHA and the run URL before treating it as release evidence. The release workflow intentionally has no button because its publication authority begins only with an exact version tag.

The workflows below run only when requested manually:

WorkflowEvidenceRC interpretation
dast-zap.ymlOWASP ZAP baseline against a release blog showcase plus fresh generated REST API and complete LMS applicationsREST/LMS warnings and failures block unless an exact rule ID is versioned as INFO with a local explanation in .zap/; those configs are passed explicitly to the pinned scanner and unlisted warnings remain live. The showcase is informational because it deliberately uses third-party presentation assets; reports and application logs are retained. This remains representative, not universal deployment coverage.
fuzzing.ymlAll 40 declared libFuzzer targets from the validated shared inventoryRequired v12 RC evidence: release mode first validates the ten package lockfiles and compiles every declared target in ten package-level preflight jobs, then every target must finish without a crash for the 5.5-hour budget; target-specific corpora are restored and saved, while failure reproducers are retained. Dependency-lock drift fails preflight, campaign and corpus jobs. The proc-macro parser uses strict processes of at most 30 minutes sharing one corpus, which bounds sanitizer RSS without weakening the total budget. A strict five-minute single-target diagnostic accelerates correction feedback but is explicitly ineligible as RC evidence. This is bounded evidence, not proof for every input.
kani.ymlTwenty named bounded formal harnesses in ten supported runtime/library packagesRequired v12 RC evidence for the declared harnesses: every proof has an isolated strict matrix job. Rullst itself stays on stable Rust 1.98.1 with a Rust 1.96 MSRV; only the separately built Kani verifier uses its pinned nightly-2026-08-01 compiler (rustc 1.99.0-nightly) because the latest stable Kani bundle’s Rust 1.93 compiler cannot compile the framework. The proc-macro-only rullst-macros target remains unsupported by Kani and is covered by compile-pass/fail and generated-project evidence instead.
miri.ymlRandomized-layout Miri execution over 15 named pure-Rust/default-feature scopesRequired v12 RC evidence for the declared scopes: every selected scope is strict. This nightly-only interpreter uses pinned nightly-2026-08-21 (rustc 1.100.0-nightly); it does not change the project’s stable toolchain or MSRV. Native FFI, OS syscall, network/provider, umbrella re-export, and example-application boundaries are excluded explicitly rather than emitted as tolerated errors.
mutants.ymlA fail-fast reviewed inventory, eighty lossless shards over the measured 14,380-mutant workspace scope, their artifacts and a strict aggregateInformational: a cheap all-feature --list --json preflight rejects inventory drift before runners start; every shard then uses that release surface, and aggregation requires every reviewed candidate to receive exactly one classification before reporting the conservative caught percentage. A targeted mode retests one validated production Rust file after a correction; it does not replace the complete campaign. Missed/time-out exit codes remain findings, while a broken baseline, incomplete artifact set, preflight/classification mismatch, invalid invocation or cargo-mutants internal failure fails the workflow. The 80-way split replaces an invalid 16-way attempt whose default-feature baseline omitted optional tests and whose slowest CLI/ORM jobs could not fit the 5h30 bound. “Pass” does not honestly mean every possible mutant was killed.

These workflows are periodic and manually runnable:

CadenceWorkflowsMode
Dailyaudit.yml, sanitizers.ymlCargo Audit is blocking; TSan/ASan are blocking when executed.
Weeklybench.yml, cargo-deny.yml, codeql.yml, corpus-sync.yml, coverage.yml, documentation.yml, pqc-compliance.yml, proptest.yml, scorecards.yml, security-audit.yml, trufflehog.yml, udeps.ymlThe inventory below identifies which results are blocking, automated evidence, or informational. Corpus sync warms and minimizes the same 40 validated target corpora with bounded parallelism.

All remaining test/build workflows run on the documented push, pull-request or path filters and also expose a manual rerun. For an RC checkpoint, first use the automatic mainline suite, then manually run the five manual-only workflows and any periodic/platform matrix whose latest successful run does not point to the same candidate SHA. Physical devices, store approval, live provider accounts, external security review and human release approval remain outside GitHub Actions.

Required local and release baseline

The contributor baseline from AGENTS.md is:

cargo test --workspace --all-features
cargo clippy --workspace --all-features -- -D warnings
cargo fmt --all

The main CI uses the stricter all-target Clippy form and checks formatting without modifying files:

cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features

Rust CI disables Cargo incremental compilation and uses the pinned sccache Action and binary to store content-addressed compiler outputs in GitHub Actions cache. The current cc build dependency also honors the same Rust compiler wrapper, so compatible bundled DuckDB C++ objects can be reused. Same-repository pull requests can populate only their GitHub-isolated refs/pull/.../merge cache scope, making a failed-job rerun useful without modifying the trusted default-branch namespace. Fork pull requests remain read-only. Pushes and explicit manual runs on main populate entries reusable by later pull requests. Cache contents never substitute for a test result. The tag-only verifier uses the same namespace strictly read-only, so it can reuse an exact trusted compiler output but cannot alter the cache while creating release artifacts; every release command and assertion still runs.

The setup action’s job-scoped Cargo archive is disabled in these compiler-cache jobs, so neither the raw workspace target tree nor duplicate registry bundles compete with compiler objects. CLI integration tests create and remove nested Cargo targets, and archiving whole mutable trees previously produced false missing-directory annotations, duplicated roughly 9.56 GiB across twenty active main caches, and caused eviction churn at GitHub’s default 10 GiB repository limit. Cargo may redownload registry sources on a fresh runner; this small network cost is preferable to storing the same registry/target archive under many job-specific keys. A first run on a new cache namespace is still a cold build; evaluate acceleration using the reported cache hit ratio and a later compatible run, never by weakening or omitting assertions.

The same content-addressed approach accelerates LLVM coverage, benchmark, mutation/fuzz compilation and the scheduled release-mode regression suite. The weekly corpus workflow warms the same fuzz compiler namespace used by the manual campaign. Benchmarks remain sequential on one runner so comparisons do not mix host variance. Coverage deliberately remains one report job because splitting it without a reviewed profile-data merge could change the repository percentage. Its second default-feature pass is limited to ORM, Studio and the public facade: those are the packages with default-SQLite tests excluded by the mutually exclusive all-feature graph, so unrelated workspace tests are not repeated. The release-mode workspace is safe to split because every shard returns an ordinary test result, while the two source locations that actually use proptest! still receive their configured 10,000-case runs. SemVer checks fan out from the machine-readable release order and validate one published API per job, so adding or removing a release package cannot silently drift from the matrix.

The all-feature coverage pass uses pinned cargo-nextest to schedule the same discovered unit and integration tests concurrently. It performs no retry and a flaky retry could not be normalized into success. A repository profile fixes four global slots and permits at most two integration tests that launch nested generated-application Cargo builds, preventing compiler fan-out from trading latency for memory or disk exhaustion. The exact per-test JUnit result and duration record is retained for 30 days. This runner is a coverage scheduler, not a replacement for Rust CI: the required multi-platform shards continue to execute the complete inventory with ordinary cargo test, preserving both traditional libtest process semantics and the materialized-project gates.

CodeQL also remains one analysis job. Its Cargo target cache is disabled so the extractor observes compilation for the exact SHA instead of inheriting a fresh artifact from another run; the analysis database itself is not interchangeable with ordinary test shards. Rust CodeQL’s faster buildless mode is intentionally not used because manual compilation gives the extractor the stronger generated code boundary needed by this release.

ci.yml also compiles and exercises each ORM strict database feature in isolation (PostgreSQL, MySQL, and SQLite), exercises the runtime-only Core and all 45 public umbrella features in isolated additive graphs with automatic manifest-drift detection, runs the portable database matrix on Linux, and tests the complete all-feature workspace in eight parallel shards on Linux, macOS, and Windows. Feature-boundary rows and threat-model negative tests also fan out into four deterministic strict shards each; their matrix job remains a single blocking dependency for the quality scorecard. Each threat-model shard primes the reviewed lockfile before its deliberately offline generated-project checks, so it does not inherit a hidden source-cache dependency from another job. The umbrella’s cfg(doctest) aggregation reads all 52 public tutorial files directly, so that same command discovers the versioned Rust blocks, compiles or executes complete examples, and records explicitly contextual fragments as ignored instead of pretending they are standalone programs. Its pinned live Redis job also proves that scheduled Core jobs are not claimed early, that Core cache inspection returns bounded metadata without values, plus ORM cache hit/TTL/recovery, tenant/table invalidation, rollback preservation, process-local post-commit observers and Scout commit ordering. A separate SQLite outbox contract runs on all three operating systems and covers atomicity, conflicting idempotency keys, claim races, lease expiry, retry and dead-letter; the relational matrix repeats the core outbox lifecycle against PostgreSQL, MySQL, MariaDB and strict SQLite. A dedicated job checks the declared MSRV, Rust 1.96.0. The Linux provider matrix also runs the feature-gated Scout adapter against a digest-pinned Meilisearch image; Algolia and Elasticsearch use bounded local protocol fixtures because no hosted provider account is part of CI. The same matrix runs typed, parameterized L2 and cosine queries against a digest-pinned PostgreSQL + pgvector image. It also runs Nexus’s default Any/SQLite HTTP contract explicitly, because the global all-feature graph intentionally selects a strict database profile and excludes that materialized tenant/audit target. Coverage separately merges the default workspace pass, so those routes contribute real executed-line evidence.

After a ready-PR or manual full-matrix Rust CI run finishes, an observational job emits a SHA-bound per-crate quality scorecard into the workflow summary and a 90-day artifact. The score combines versioned expert-audit ceilings with the actual gate results; a failed/skipped/cancelled gate can remove the dimensions it was meant to prove, while a green gate cannot inflate a crate beyond its audited ceiling. This is engineering-evidence reporting, not capability completion or certification. See the scorecard methodology.

Rows with no feature selected compile every package target. Feature-selected rows compile the isolated library graph; feature-enabled tests, examples, and benchmarks remain covered by the workspace and specialist jobs. This avoids pulling unrelated development dependencies into every boundary while retaining real integration coverage.

The tag-only packaged-distribution gate reads the complete feature set from the extracted rullst package manifest and compiles that crates-only consumer with defaults disabled and every public feature enabled. A partial hand-maintained feature allowlist therefore cannot make a monorepo-only integration appear release-ready.

Require every job emitted by the following workflows before merging into main: Rust CI, GitHub Actions Lint, Documentation, End-to-End Smoke Tests, Cargo Audit, Security Audit, Cargo Deny, CodeQL, Test Coverage, Cargo Machete, SemVer Checks, Spellcheck, Crate Architecture Policy, TruffleHog, Unsafe Policy, WebAssembly Matrix, Zero Panics, no-std Build, IoT Integration, and PR Security Evidence.

Do not configure a path-filtered, scheduled, manual, deployment, or tag-only workflow as a universal required check: an intentionally skipped workflow may never create the check context. In particular, IoT Cryptography Containment is blocking when relevant paths change, and the Omni desktop, Android and iOS compile workflows are blocking only when the Omni generator boundary changes. Pages, benchmarks, fuzzing, sanitizers, Kani, Miri, mutation testing, udeps, ZAP, Scorecard, and release provenance belong to deeper evidence or release policy. GitHub repository rulesets remain the enforcement source; this document records the recommended profile and does not claim that the hosted setting is already enabled.

Phase 4 release-engineering status

Goal from gpt.mdCurrent statusAssessment
Trifecta with all featuresImplementedCI and tag release both run format, all-target/all-feature Clippy, and all-feature tests.
Strict DB features in isolationImplemented in workflowstrict-postgres, strict-mysql, and strict-sqlite compile independently and each runs a backend-specific CRUD test with only the selected strict feature enabled.
Honest blocking/informational labelsImplementedUnsafe and Wasm checks are blocking in continuous CI; the declared Kani, Miri and fuzzing scopes are strict v12 RC gates; mutation testing and udeps explicitly remain informational.
Cover every fuzz targetImplemented in workflow.github/fuzz-targets.json is the shared inventory for the manual campaign and corpus maintenance. A blocking validator compares it with all ten fuzz manifests and their 40 source files. This records configuration, not a successful six-hour run.
Package all crates before publishingImplemented in workflowThe tag-only release validates versions, packages all publishable workspace crates, hashes and attests the archives, then publishes in dependency order.
Unified evidence bundle per tagImplemented in workflowThe tag-scoped bundle contains Cargo.lock, Cargo metadata, CycloneDX 1.5 SBOM, Cargo Audit JSON, deny.toml, bounded compliance evidence, governed advisory exceptions, commit/tag context, and checksums. The bundle and .crate archives are included in build-provenance attestation.
Align manifest, changelog, tag, registry, and notesPartialThe release validates vMAJOR.MINOR.PATCH against publishable manifest versions. Changelog state and registry/release-note consistency are not automatically verified. Worth implementing before calling 12.0.0 released.

Important evidence boundaries

Zero-panics and unsafe Rust

zero-panics.yml denies Clippy’s unwrap, expect, panic, todo, and unimplemented lints for published runtime libraries, procedural-macro engines, CLI production targets, generated runtime templates, and the Wasm Core path. Tests are excluded where assertion panics are test semantics.

unsafe-policy.yml compiles production libraries and binaries with -Dunsafe-code. The only reviewed file-level exceptions are the Radar OS probe and dynamic-library loader, and the workflow fails if that allowlist changes. This is an enforced boundary, not a claim that all dependencies contain no unsafe Rust.

Coverage

coverage.yml runs LLVM coverage over workspace all-features and default profiles plus the live database matrix, then uploads LCOV to Codecov using GitHub OIDC rather than a long-lived upload secret. It also retains exact JSON and text line summaries for 30 days so a passing upload cannot be confused with the coverage percentage. The all-feature pass uses pinned cargo-nextest only to run the same discovered tests concurrently, with zero retries, bounded nested-Cargo concurrency and a retained JUnit execution record. Rust CI still runs ordinary cargo test across Linux, macOS and Windows. The default-profile pass explicitly includes ORM, Studio, Nexus and the umbrella facade so their real SQLite contracts are not hidden by mutually exclusive all-feature database profiles. Before upload, the workflow independently rejects an LLVM summary below 90% for either the whole repository or the governed framework-library paths. codecov.yml also requires at least 90%, with zero tolerance, for those views and changed lines; failure to upload LCOV also fails the workflow. The report filters examples, benchmarks, auxiliary test support, and separate test files. CLI and proc-macro code therefore remains part of the blocking repository aggregate and is additionally visible as informational components. Their stronger semantic evidence still comes from materialized scaffolds and compile-pass/compile-fail contracts. The README exposes both the public overall badge and the separate framework_libraries badge rather than substituting the higher component result for the repository total.

Formal, dynamic, and stress analysis

  • Kani and Miri are manual research evidence scoped to the harnesses/packages that actually execute. Rullst itself remains pinned to stable Rust 1.98.1 and keeps Rust 1.96 as its declared MSRV. Kani builds reviewed upstream revision 8fcd6d90ed07b559e553ca8a92b95f2db69b2c78 with the verifier’s own pinned nightly-2026-08-01 compiler (rustc 1.99.0-nightly) into a bundle and installer, then treats proof failures in twenty isolated harness jobs across ten supported packages as real matrix failures. That revision intentionally predates a compare_bytes compiler crash reproduced with Kani’s first rustc 1.100.0-nightly snapshot. The workflow does not patch manifests, bypass MSRV data, or change Rullst’s stable toolchain. Kani cannot verify the proc-macro-only rullst-macros target. Miri, which only runs on nightly Rust, pins nightly-2026-08-21 (rustc 1.100.0-nightly) and strictly executes 15 named pure-Rust/default-feature scopes. Its matrix excludes native ring, AWS-LC, SQLite, OS-syscall and network/provider execution that Miri cannot interpret; native CI, integration tests and sanitizers remain the applicable evidence for those paths. The rullst umbrella re-export facade and Blog example add no separate interpreter scope. A selected-scope failure fails the run. The Kani Security harnesses prove pure production decisions such as Vault key-ID character policy, DLP buffer admission, ASCII-folded RASP matching, SRI asset limits and bounded Login Guard delay; the IoT matrix also proves the complete CoAP option-component classification. They do not claim that Kani verifies zeroize’s unsupported inline assembly, cryptographic implementations or the entire concurrent middleware implementations.
  • Mutation testing is manual, split into 80 lossless shards over the measured 14,380-mutant inventory, and intentionally informational while results are uploaded. Before the expensive matrix starts, a fail-fast --list --json preflight verifies the exact unique candidate set; the final aggregate must match that reviewed list, not merely its count. The hosted command makes --all-features explicit and .cargo/mutants.toml applies the same feature policy locally; the ignored legacy root configuration and its exclusions were not silently activated. Targeted mode accepts exactly one tracked production .rs path so a correction can be retested without restarting the complete workspace campaign. Exit statuses for missed and timed-out mutants remain findings; baseline, usage and internal failures do not get normalized into green jobs. The aggregate also fails closed when an artifact is absent, a shard is incomplete or the reviewed full inventory drifts; its conservative percentage never treats a timeout as caught.
  • Fuzzing and corpus maintenance pin nightly-2026-08-21 instead of following a moving nightly alias. This verifier-only toolchain does not change the framework’s stable Rust 1.98.1 toolchain or its Rust 1.96 MSRV. Before a release campaign starts, ten strict preflight jobs compile every target so a stale import or broken fuzz manifest fails in minutes rather than alongside hours of valid campaigns. A five-minute one-target diagnostic is correction feedback only; the evidence-boundary job refuses to call it release evidence. Each of the ten fuzz packages has a checked-in dependency lock; inventory validation requires it, and locked metadata plus a post-command drift check prevents preflight, campaign or corpus maintenance from silently resolving a different dependency graph. The parser campaign restarts its ASan process every 30 minutes while retaining one corpus and the full 5.5-hour target budget, preventing instrumentation RSS accumulation from masquerading as a parser crash.
  • Branch coverage, cargo-udeps, TSan and ASan share the reviewed nightly-2026-08-21 analysis snapshot instead of following a moving nightly alias. The first two remain observational/informational; sanitizer failures remain blocking whenever their daily/manual matrix executes.
  • cargo-udeps is weekly/manual and explicitly non-blocking.
  • TSan and ASan run daily/manual across twelve runtime/domain packages; Messaging runs its integration contract so its concurrent state is actually exercised rather than reporting a zero-test library pass. There is no MSan job in the current sanitizer workflow.
  • The manual ZAP workflow materializes, release-builds and migrates a fresh REST API and complete LMS through the real CLI. Both baselines fail on any warning/failure, preserve INFO observations and use no ignored rules. The release blog showcase is scanned separately but remains informational because its documented presentation boundary deliberately uses a relaxed CSP and third-party assets. Its rules retain those external-asset findings for review and reduce only evidenced token/state signals or escaped showcase reflections to INFO; they do not hide findings with IGNORE. These three targets are representative evidence, not coverage of every blueprint, authenticated role, browser, proxy or deployment.
  • Property tests and benchmarks are scheduled/manual evidence. The property workflow preserves the complete all-feature release-mode regression suite in eight parallel shards and separately runs the ORM and Connect property contracts with 10,000 generated cases. The eight published benchmark groups, backed by nine Criterion binaries, emit non-blocking alerts at a 20% regression and feed the public benchmark hub; they are not a promise against every nanosecond-level regression.

Fuzzing and OSS-Fuzz

The manual fuzzing.yml matrix covers all 40 declared libFuzzer targets: Core 12, ORM 5, Security 7, Connect 3, Mail 4, AI 3, IoT 3, Capital 1, Nexus 1, and Studio 1. The checked-in .github/fuzz-targets.json is validated against every */fuzz/Cargo.toml, corresponding lockfile and source file before either the manual campaign or weekly corpus job can fan out. Release mode then compiles all targets in ten package-level preflight jobs before starting any long campaign. Both jobs use versioned per-target corpora and one content-addressed compiler-cache namespace; campaign failures retain their exact reproducer, and the weekly job performs a bounded warm-up before minimizing and uploading each actual corpus. A clean run remains evidence only for its exact SHA, target, corpus, toolchain and time budget. Diagnostic mode is limited to one exact inventory target for five minutes and is never counted as the forty-target RC gate.

The oss-fuzz/projects/rullst directory is a local integration draft. It is not proof of upstream acceptance, continuous ClusterFuzz execution, or coverage of all 40 targets; its helper build must be completed and validated against the official OSS-Fuzz repository before submission. The integration is worth finishing, but a “100% first-pass acceptance” promise is not meaningful and should not be made.

Supply chain and release provenance

All direct third-party GitHub Actions references in this repository’s workflow files are pinned to full commit SHAs. A pinned composite action can still carry its own transitive downloads or references, so blocking integrations must also be reviewed for that behavior. RustSec exceptions are limited by deny.toml and documented with owners, controls, and expiry dates in docs/src/security-advisory-exceptions.md.

scorecards.yml runs the pinned OpenSSF Scorecard action on main pushes and weekly, uploads SARIF to GitHub code scanning, and publishes OIDC-authenticated results to the public Scorecard API so the README badge follows the latest completed analysis. The numeric score is supply-chain evidence, not a security certification.

release.yml is tag-only. It verifies source, validates the exact semantic tag against every publishable crate, packages before the first publish, and creates a tag-bound evidence bundle containing the lockfile, Cargo metadata, CycloneDX 1.5, Cargo Audit JSON, dependency policy, bounded compliance evidence, advisory exceptions, commit context, and checksums. The .crate archives and evidence receive a GitHub build-provenance attestation, while the official generic SLSA generator produces release provenance. This does not by itself establish project-wide SLSA Level 3 certification, Sigstore Cosign binary signing, or regulatory compliance.

workflow-lint.yml validates all workflow syntax, GitHub expressions, and embedded shell with Actionlint 1.7.7. Its container is pinned to an immutable linux/amd64 digest, just like third-party GitHub Actions are pinned to full commit SHAs.

architecture.yml is repository-owned and deterministic. It rejects any internal dependency edge or optionality change that is not reflected in the reviewed crate-architecture-policy.json. The earlier TangleGuard integration was removed because its composite action downloaded an unversioned latest binary without a repository-pinned checksum, which was unsuitable for a blocking supply-chain gate.

Workflow inventory (37 definitions)

Durations are intentionally omitted because runner load, cache state, and the dependency graph make static estimates unreliable.

WorkflowTriggerModeActual scope
ai-sentinel-pr.ymlpull requestsAutomated evidenceGenerates bounded CLI audit, compliance report, and CycloneDX SBOM artifacts; no certification claim.
architecture.ymlmain push and PR, manualBlockingCompares Cargo’s publishable non-dev internal dependency graph with the reviewed crate-architecture-policy.json; unreviewed normal/build edges, removals, or optionality changes fail, while test-only dev-dependencies do not masquerade as production coupling.
audit.ymlmain push and PR, daily, manualBlockingCargo Audit over the production lock and all ten fuzz-package locks with one advisory-database fetch. The v12 candidate applies no advisory exceptions; future exceptions must pass the separate owner/expiry governance check.
bench.ymlmain push, weekly, manualAutomated evidenceEight published groups backed by nine Criterion binaries, with non-blocking 20% regression alerts and gh-pages data consumed by the benchmark hub. Scheduled runs use the repository default branch.
cargo-deny.ymlmain push and PR, weekly, manualBlockingAdvisory, license, ban, and source policy from deny.toml.
ci.ymlmain push and PR, manualBlocking plus observational reportFormat, all-target/all-feature Clippy, eight-shard multi-OS tests including Cargo-aware doctests sourced from all 52 tutorials, four-way feature/threat partitions, the SQLite transactional outbox contract and Messaging concurrency suite, relational/polyglot live matrices, isolated strict-DB/feature boundaries, MSRV, and a ready-PR/manual full-matrix SHA-bound per-crate quality scorecard artifact. A targeted manual OS/shard run is diagnostic and cannot emit the full scorecard.
codeql.ymlmain push and PR, weekly, manualBlocking runRust CodeQL after an all-target/all-feature workspace check.
corpus-sync.ymlweekly, manualInformationalValidates the shared 40-target inventory and ten package lockfiles, restores each real target corpus, performs a bounded warm-up, minimizes it, uploads the result and warms the campaign’s content-addressed compiler cache; individual target failures are retained but tolerated, while dependency-lock drift remains a hard failure.
coverage.ymlmain push and PR, weekly, manualBlocking plus observational jobLLVM LCOV generation with a pinned, zero-retry, bounded-concurrency nextest scheduler and retained JUnit inventory; a focused default-SQLite pass for ORM/Studio/Nexus/the facade; exact local 90% floors; and blocking OIDC-authenticated Codecov upload. Scheduled/manual branch instrumentation is non-blocking and uses the pinned verifier-only nightly.
dast-zap.ymlmanualBlocking generated targets plus informational showcasePins the ZAP image by digest, scans fresh release/migrated REST API and complete LMS surfaces as blocking gates, scans the CDN-backed blog showcase informationally, and uploads separate reports plus application logs.
documentation.ymlmain push and PR, weekly, manualBlocking plus informational external scanBuilds the mdBook; validates landing/benchmark templates, project identity, the README workflow count, local assets, pinned external chart scripts and all requested social links. Real Chromium checks desktop/390px/320px layout, keyboard/mobile navigation, clipboard success/denial, privacy disclosure, reduced motion, no-JS navigation, and absence of external landing requests/browser storage. This is a bounded browser contract, not WCAG certification. Also validates the 190-claim historical roadmap denominator and repository-local links. Scheduled/manual runs preserve an informational external-link report.
e2e-smoke.ymlmain push and PR, manualBlockingBoots the release Blog application and checks HTTP, headers, CSRF form flow, SQLite persistence, and the persisted page parsed by real headless Chromium.
fuzzing.ymlmanualBlocking v12 RC campaign or diagnosticRelease mode validates ten package lockfiles and compiles all targets in ten package preflights before forty 5.5-hour libFuzzer jobs, with per-target corpus caching and failure reproducers. Lock drift is always rejected. The parser restarts its ASan process at most every 30 minutes while preserving the budget. Single-target diagnostic mode runs for five minutes and the evidence boundary marks it ineligible for release.
iot-integration.ymlmain push and PR, manualBlockingHost IoT tests, signed OTA invariants, and one Cortex-M no-std build; no hardware claim.
kani.ymlmanualBlocking v12 RC scope; bounded evidenceBuilds an immutable reviewed Kani snapshot with the verifier-only nightly-2026-08-01 compiler, while Rullst stays on stable Rust 1.98.1 with a Rust 1.96 MSRV. It verifies twenty named bounded harnesses in isolated jobs across ten supported packages. Proof failures fail their matrix jobs; the proc-macro-only crate remains outside Kani’s supported targets.
machete.ymlmain push and PR, manualBlockingUnused dependency scan with configured exceptions.
miri.ymlmanualBlocking v12 RC scope; bounded evidencePinned nightly-only Miri executes 15 named pure-Rust/default-feature scopes with randomized layouts without changing Rullst’s stable toolchain or MSRV. Native FFI/syscall/network paths, the umbrella re-export facade, and the Blog example are explicit boundaries; selected-scope failures fail the workflow.
mutants.ymlmanualInformationalA fail-fast exact-inventory preflight followed by eighty lossless pinned cargo-mutants 27.1.0 shards over the measured 14,380-mutant all-feature workspace scope, or one validated production-file diagnostic, with uploaded results, compiler caching and a strict completeness aggregate bound to the preflight list. Findings stay informational, but baseline/tool/invocation failures, missing artifacts, incomplete classification and reviewed-inventory drift fail the run.
no_std-build.ymlmain push and PR, manualBlockingBuilds rullst-iot for three bare-metal targets; this is compile evidence, not hardware execution.
omni-android.ymlrelevant main changes and PRs, manualBlocking when triggeredGenerates a fresh deterministic Omni shell, initializes Android and compiles an unsigned aarch64 debug APK. It does not test a physical device, Play testing, signing, privacy declarations or store acceptance.
omni-desktop.ymlrelevant main changes and PRs, manualBlocking when triggeredGenerates a fresh deterministic HTTPS-backed shell and checks its Tauri crate on Linux, macOS and Windows. It does not build/sign every installer or exercise a GUI/WebView session.
omni-ios.ymlrelevant main changes, manualBlockingGenerates a fresh deterministic Omni iOS shell on macOS and compiles it for the runner’s simulator architecture. It does not test a physical device, signing, privacy declarations, TestFlight or App Store acceptance.
pages.ymlmain push, manualDeployValidates and deploys the unreleased v12 landing page, local visual assets, mdBook and benchmark hub/dashboards to GitHub Pages while preserving history data fetched from gh-pages.
pqc-compliance.ymlrelevant main changes, weekly, manualBlockingSigned OTA and Vault tests, RustSec audit, and simulator-boundary checks; explicitly no PQC/HSM certification.
proptest.ymlweekly, manualBlocking runEight parallel release-mode workspace shards plus dedicated ORM and Connect property contracts with configured case counts.
release.ymlexact-looking version tagsReleaseTag validation, full verification, package-all, evidence bundle, checksums, attestations, dependency-order publish, and release provenance.
sanitizers.ymldaily, manualBlocking runTSan and ASan library matrices on pinned nightly-2026-08-21; this verifier toolchain does not change Rullst’s stable compiler or MSRV.
scorecards.ymlmain push, weekly, manualAutomated evidenceOpenSSF Scorecard analysis and SARIF/artifact upload; not SLSA certification.
security-audit.ymlmain push and PR, weekly, manualBlockingCross-checks active advisory IDs and expiry metadata across the ledger, Cargo Deny, and scanner workflows, then independently reruns Cargo Audit.
semver.ymlmain push and PR, manualBlockingFans out one job per machine-readable release-order entry and compares each supported, already-published library API with its exact latest non-yanked crates.io baseline. Never-published packages and proc-macro/binary API surfaces unsupported by cargo-semver-checks are reported explicitly.
spellcheck.ymlmain push and PR, manualBlockingRepository typo scan.
trufflehog.ymlmain push and PR, weekly, manualBlockingVerified-secret scan over the configured Git history range.
udeps.ymlweekly, manualInformationalcargo-udeps signal on pinned nightly-2026-08-21; command failures are tolerated.
unsafe-policy.ymlmain push and PR, manualBlockingDenies new production unsafe code and validates the reviewed exception allowlist.
wasm-matrix.ymlmain push and PR, manualBlockingCompiles Core, the public rullst facade and macros for wasm32-unknown-unknown and wasm32-wasip1.
workflow-lint.ymlmain push and PR, manualBlockingValidates the shared fuzz inventory, then Actionlint checks workflow syntax, GitHub expressions and embedded shell using an immutable container digest.
zero-panics.ymlmain push and PR, manualBlockingPanic-family Clippy lints plus generated-code regression checks for published runtime targets.

Preserved next-generation roadmap

These ideas remain valuable, but are not current guarantees:

IdeaStatus and recommendation
Loom and Shuttle concurrency explorationNot implemented — worth implementing for the small shared-state primitives that have explicit concurrency invariants. Do not apply them indiscriminately to the whole workspace.
cargo-vet dependency reviewNot implemented — worth implementing once review ownership and audit criteria are defined; an empty policy file would add ceremony without assurance.
cargo-careful and zero-allocation assertionsNot implemented — worth targeted experiments. Allocation claims need stable benchmarks and explicit hot paths before becoming gates.
PGO and BOLTNot implemented — defer until production profiles exist. Fixed throughput-gain percentages must not be promised in advance.
Chaos testing with fail-rsNot implemented — worth implementing around queues, database retries, and provider timeouts after deterministic failure contracts exist.
AFL.rs/honggfuzz differential fuzzingNot implemented — valuable after the 40 libFuzzer targets have healthy corpora and triage ownership.
Sigstore Cosign signingNot implemented. Consider it for separately distributed binaries/containers; current .crate provenance and checksums should remain the immediate priority.
Absolute “100% pure Rustls” mandateNot established and not recommended as a marketing absolute. Enforce an audited TLS dependency policy based on supported platforms and threat model instead.
Complete upstream OSS-Fuzz integrationPartial draft — worth finishing. Validate every intended target with helper.py build_fuzzers and check_build, then submit upstream; do not imply acceptance before merge.

The goal of this roadmap is stronger, reproducible evidence—not a larger number of badges or absolute claims that no finite test suite can establish.

rullst-orm

Rullst ORM 🌟

A beautiful, type-safe, Active Record ORM for Rust.

Crates.io Downloads Docs.rs Build Status License: MIT

Important

This page documents the unreleased v12 source. Use a path dependency from this checkout until the planned 12.0.0-rc.1 is published.

🚀 Visit the Official Website & Documentation Hub 🚀

Built on top of sqlx and procedural macros, Rullst ORM brings the delightful, fluent syntax of Active Record frameworks directly to the high-performance Rust ecosystem.

🛡️ Security Engineering

Rullst ORM uses SQLx bindings, validated identifiers, typed errors, and layered CI checks. Workflow badges are scoped test results, not a guarantee for an application or deployment.

Security AuditStatusDescription
OpenSSF ScorecardOpenSSF ScorecardCurrent public supply-chain practice score; not a security certification
Release ProvenanceRelease provenanceProvenance attestations for release artifacts; no SLSA level is claimed here
CodecovFramework library coverageBlocking 90% target for the measured framework-library scope; the complete repository aggregate now also has its own 90% gate
Matrix DB TestsTestcontainersLive PostgreSQL, MySQL, MariaDB, MongoDB, SurrealDB and libSQL contracts, plus in-process DuckDB tests
OpenSSFOpenSSF Best PracticesOpen source security standards
Property testsProptestScheduled/manual bounded invariant evidence
Miri research matrixMiriManual bounded evidence; the selected pure-Rust privacy scope is strict, while native database FFI remains outside Miri
Kani research harnessesKaniManual, bounded formal evidence; not whole-ORM proof
CodeQL SASTCodeQL SASTAdvanced semantic code analysis
Cargo DenyCargo DenyBanning unmaintained/vulnerable crates
Cargo AuditAuto-AuditContinuous scanning for crate vulnerabilities
Cargo SemVercargo-semver-checksStrict SemVer API breakage checks
Cargo MacheteCargo MacheteDetecting unused and bloated dependencies
On-demand fuzzingFuzzingManual time-bounded targets; no continuous OSS-Fuzz claim
Mutation TestingMutantsMutation testing for test suite robustness
Continuous BenchmarksBenchmarks CIContinuous performance regression testing & live dashboard
Unsafe PolicyUnsafe PolicyAudits unsafe usage within the workflow’s declared scope
Panic PolicyPanic PolicyGraceful error handling across the framework

🚀 Why Rullst ORM?

Rullst ORM generates Active Record operations and a fluent query builder from #[derive(Orm)]. SQLx remains available for queries that do not fit the generated API.

Key Features:

  • Generated CRUD: Insert, update, delete, restore, and find operations for supported model shapes.
  • Fluent Query Builder: Chain methods such as .where_eq(), .limit(), and .order_by(); values are bound and structural identifiers are validated.
  • Relationships and eager loading: has_many, has_one, belongs_to, and polymorphic relationship helpers, with explicit eager-load methods.
  • Opt-in tenant scope: #[orm(tenant_column = "account_id")] adds the configured task-local tenant to generated model queries. Applications must establish the tenant context at their authenticated boundary.
  • Actor-bound audit revisions: #[orm(auditable)] requires a validated user/service/system AuditContext; the active tenant and optional correlation ID are recorded with recursively redacted bounded changes. Generated instance saves/deletes and their audit entry share a savepoint and fail together. Eligible v2 updates expose guarded revision restoration, which rejects stale, cross-tenant, redacted, malformed, legacy, create/delete, and oversized revisions and records a compensating audit entry. The host still derives authenticated principal/tenant authority, while bulk per-row history and durable export remain explicit. See Auditable Revisions.
  • Field privacy: #[orm(encrypted)] transparently encrypts supported String fields with a versioned AES-256-GCM envelope. Randomized ciphertext cannot be filtered or sorted; use a separate keyed blind index where needed.
  • Native relational enums: #[derive(Enum)] owns one closed label mapping for SQLx, Serde and ORM values. Blueprint::native_enum emits a named, drift-checked PostgreSQL type with strict-postgres, inline MySQL/MariaDB ENUM, or a SQLite TEXT CHECK constraint.
  • Scout hooks and providers: #[orm(searchable)] calls a configured SearchEngine after generated writes/deletes. scout-http supplies bounded Meilisearch, Elasticsearch and Algolia adapters; the generated effect is process-local unless the application composes the transactional outbox. See Scout Search Providers.
  • Typed pgvector queries: pgvector re-exports Vector with SQLx support; vector/distance values in L2, cosine and inner-product helpers are bound, not interpolated. The strict PostgreSQL matrix creates the extension and runs a typed live lifecycle. See RAG Systems & Vector Search.
  • Bounded Qdrant vectors: qdrant keeps specialized dense-cosine collection/upsert/delete/query semantics separate from SQL Active Record, with resource/transport bounds, deterministic fallback, authenticated protocol fixtures and a pinned live lifecycle.
  • Native Redis structures: redis adds an immutable namespace and bounded Hash, Set and Sorted Set operations in addition to .remember; remote endpoints require TLS and live evidence covers isolation and native commands.
  • Portable document recovery: MongoDB, SurrealDB and the deterministic store expose identifier-preserving inventory. An application-operated, AES-256-GCM snapshot binds application/collection scope, compares two bounded source observations, resumes only into an exact destination subset and verifies the final inventory. Writers, schema provisioning, key custody and durable backup storage remain explicit operator responsibilities. See Polyglot Persistence.
  • Structured telemetry: generated/raw query and stream spans expose only static model/table/operation metadata, managed transactions record bounded outcomes, and Rullst-created pools emit checkout timing. Core’s opt-in OpenTelemetry layer can export the standard tracing signals; subscriber, sampling, collector and separately configured SQLx logs remain host policy.
  • Comparative SQLite evidence: a lockfile-pinned Criterion harness gives Rullst, Diesel and SeaORM one typed connection, the same indexed schema, 100-row seed, SQLite policy and five logical operations. The CI history is scoped comparison evidence; it does not claim universal or negligible overhead, networked-database throughput or complete-application performance.
  • Durable opt-in outbox: Outbox::enqueue commits a stream-scoped, idempotent event with relational domain state. Exact lease tokens, bounded retry and dead-letter are shared by SQLite, PostgreSQL, MySQL and MariaDB. Delivery is at least once, so the application dispatcher and consumer remain idempotent; generated observers are not silently converted into events. See the transactional outbox tutorial.
  • Database-first introspection: cargo rullst generate:models reads SQLite, PostgreSQL, or MySQL metadata using bound schema/table parameters, normalizes table module identifiers, and rejects unsafe SQL identifiers, collisions, or columns requiring unsupported ORM remapping before writing files.
  • Additive migration generation: make:migration:auto compares supported model definitions and emits a migration for review.
  • Cascading soft deletes: Opt-in relationship metadata can cascade through generated delete methods; transaction-aware variants use the supplied transaction.
  • Partial updates: .update_partial() binds only the selected supported fields.
  • Model policies: #[orm(policy = "MyPolicy")] invokes the configured policy on generated create/update/delete/restore operations.
  • Strict lazy-loading prevention: the global toggle makes generated lazy relationship methods return a validation error instead of performing the query.
  • Explicit Capability Boundaries: Unsupported replication paths fail closed instead of reporting simulated success.

🛠️ Quick Start

Installation

After the RC is published, install its exact train with:

cargo add rullst-orm@12.0.0-rc.1
cargo add tokio -F full

Zero-to-Hero Example

use rullst_orm::{Orm, FromRow};

// 1. Just add the Orm macro to your struct!
#[derive(Debug, Clone, FromRow, Orm)]
pub struct User {
    pub id: i32, // ID = 0 means it hasn't been saved yet
    pub name: String,
    pub email: String,
    #[orm(hidden)] // Won't be exposed in JSON responses
    pub password: String,
}

#[tokio::main]
async fn main() -> Result<(), rullst_orm::Error> {
    // 2. Initialize the connection pool (Supports SQLite, Postgres, MySQL)
    Orm::init("sqlite::memory:").await?;

    // 3. Create a new user
    let mut user = User {
        id: 0,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
        password: "secret_password".to_string(),
    };
    
    user.save().await?; // Runs INSERT and hydrates the generated ID.

    // 4. Fluent Queries
    let active_users = User::query()
        .where_like("email", "%@example.com")
        .order_by_desc("id")
        .limit(10)
        .get()
        .await?;

    println!("Found users: {:?}", active_users);

    Ok(())
}

Native database enums

Generated applications should select a strict primary feature. PostgreSQL native enums specifically require strict-postgres, because SQLx’s dynamic Any driver cannot decode custom PostgreSQL types:

rullst-orm = { version = "12.0.0-rc.1", features = ["strict-postgres"] }

Derive one label contract and use it in schema code:

#![allow(unused)]
fn main() {
use rullst_orm::schema::{Blueprint, Schema};
use rullst_orm::{Enum, Orm};

#[derive(Enum, Debug, Clone, Copy, PartialEq, Eq)]
#[rullst_enum(type_name = "account_status", rename_all = "snake_case")]
enum AccountStatus {
    AwaitingReview,
    Active,
}

async fn create_schema() -> Result<(), rullst_orm::Error> {
Orm::init("postgres://user:password@localhost/application").await?;
Schema::create("accounts", |table: &mut Blueprint| {
    table.id();
    table.native_enum::<AccountStatus>("status").not_null();
}).await?;
Ok(())
}
}

The derive accepts 1–64 unique labels of at most 63 bytes using ASCII letters, digits, spaces, underscores or hyphens. An existing PostgreSQL type must have the exact same ordered labels or schema creation fails. MySQL/MariaDB store the labels in the table’s inline ENUM; SQLite enforces them through TEXT CHECK. Adding, removing or reordering labels is an explicit reviewed migration. Drop every dependent table before calling Schema::drop_native_enum::<T>() on PostgreSQL; the method is a validated no-op on the other backends.


📚 Documentation

We recently launched a brand-new Interactive Documentation Hub!

👉 Explore the Full Documentation in the Rullst Book


🛡️ Security

Rullst ORM uses SQLx prepared-statement bindings for values accepted by its query builders. Structural identifiers are restricted to a bounded ASCII identifier grammar before interpolation. Raw SQL and application authorization remain the caller’s responsibility; these controls reduce injection risk but are not an absolute safety guarantee.

📄 License

This project is licensed under the MIT License.

Rullst Connect 🦀

Important

This page documents the unreleased v12 source. Use a path dependency from this checkout until the planned 12.0.0-rc.1 is published.

Vision preserved: message brokers, additional queue transports, remote storage, and media work are retained with explicit status and recommendations in the capability ledger.

Crates.io Downloads Documentation Build License

Rullst Connect is an async OAuth2/OIDC client layer with a shared Provider interface and normalized ConnectUser output. Provider-specific protocols, scopes, claims, and application account-linking rules still require explicit review.

🛡️ Security Engineering

Rullst Connect uses layered tests and repository security checks. CI badges report the state of those checks for the referenced commit; they are not an absolute security guarantee.

Security AuditStatusDescription
OpenSSF ScorecardOpenSSF ScorecardCurrent public supply-chain practice score; not a security certification
CodecovFramework library coverageBlocking 90% target for the measured framework-library scope; the complete repository aggregate now also has its own 90% gate
OpenSSF Best PracticesOpenSSF Best PracticesCurrent project badge from the official programme
Release ProvenanceReleaseProvenance attestations for release artifacts; no SLSA level is claimed here
On-demand fuzzingFuzzingManual time-bounded targets; no continuous OSS-Fuzz claim
Property testsProptestScheduled/manual bounded invariant evidence
Miri research matrixMiriManual bounded evidence; the selected pure-Rust client scope is strict, while network/provider execution remains outside Miri
Kani research harnessesKaniManual bounded formal evidence, not proof of the complete protocol surface
CodeQL SASTCodeQLAdvanced semantic code analysis
Cargo DenyCargo DenyBanning unmaintained/vulnerable crates
Cargo AuditCargo AuditContinuous scanning for crate vulnerabilities
Benchmark CIBenchmarkContinuous performance regression testing
Cargo SemVerSemver ChecksStrict SemVer API breakage checks
Cargo MacheteMacheteDetecting unused and bloated dependencies
Spellcheck CISpellcheckAutomated typo detection across docs and code
Mutation TestingMutantsMutation testing for test suite robustness
Secret ScanningTrufflehogAutomated CI prevention of leaked credentials
Unsafe PolicyUnsafeAudits unsafe usage within the workflow’s declared scope
Panic PolicyPanicsGraceful error handling across the framework

✨ Features

  • 🚀 Async HTTP: Built on Tokio-compatible request paths and reqwest.
  • 🧩 Standardized: All providers return a unified ConnectUser struct.
  • 🛡️ Type-Safe: Robust error handling using thiserror (ConnectError).
  • 🔌 Framework adapters: Core provider APIs are framework-independent; optional extractor features cover the integrations declared in the crate.
  • 🔐 Managed callback transaction: The optional Axum/tower-sessions path generates, stores, expires, validates, and consumes state + PKCE + OIDC nonce.
  • 🔏 Encrypted token snapshots: Versioned AES-256-GCM envelopes bind a refresh generation to one trusted provider/account pair and an explicit key rotation ID before application-owned persistence.
  • 🔐 OIDC Security: Strict discovery validation plus isolated JWKS caches with TTL, refresh on unknown kid, and bounded stale-if-error behavior.
  • 🚪 Typed remote revocation: Access and refresh tokens are distinct API operations. Google, GitHub, Discord, Apple, Auth0 and Cognito have bounded protocol adapters; unsupported providers fail explicitly and offline credentials remain network-free.
  • 🏢 Explicit Corporate Proxy: First-class HTTP(S) proxy clients, including bounded Basic proxy authentication without credentials in the endpoint URL.
  • 📺 Device Flow: Native RFC 8628 support for headless CLI and Smart TV auth.
  • 🛠️ Testing: Empty or mock_* credentials select a deterministic offline transport, while mock_idp supplies a loopback-only signed OIDC fixture with one-shot codes, PKCE, nonce, EdDSA ID tokens and JWKS.

📚 Important Documents:

  • CHANGELOG.md: See what’s new.
  • ISSUES: Any issue? Please report.
  • AUDIT.md: Repository audit record; current workflow evidence remains authoritative.

📦 Supported Providers

Official support for 11 core providers:

  1. Google
  2. GitHub
  3. Microsoft / Azure AD
  4. Apple (Sign in with Apple)
  5. Auth0
  6. AWS Cognito
  7. Facebook
  8. X (Twitter) (Strict PKCE requirement)
  9. Discord
  10. LinkedIn
  11. OIDC (OpenID Connect Custom Provider)

Remote token revocation is deliberately narrower than login support. Use Provider::revoke_token for an access token and Provider::revoke_refresh_token for a refresh token; Auth0/Cognito accept only the latter through these adapters, while GitHub accepts only the former. A successful provider response does not clear application cookies, sessions, cached identity, or durable token records—the host must commit that local logout lifecycle. Provider revocation endpoints are intentionally idempotent in several ecosystems, so HTTP success is protocol acceptance, not proof that the token was valid immediately before the call.

🛠️ Installation

Inside a checkout of the unreleased v12 workspace, use its path dependency. After the RC is published, install that exact release train instead.

Published RC command:

cargo add rullst-connect@12.0.0-rc.1
cargo add secrecy

For the recommended Axum session transaction, enable axum-session and add a tower-sessions store:

rullst-connect = { path = "../Rullst/rullst-connect", features = ["axum-session"] }
tower-sessions = "0.15"

Or manually add it to your Cargo.toml:

[dependencies]
rullst-connect = { path = "../Rullst/rullst-connect" }
secrecy = "0.10"
tokio = { version = "1.52", features = ["full"] }

🚀 Quick Start

1. Initialize the Provider

Choose your provider and pass your credentials and callback URL:

use rullst_connect::prelude::*;

fn main() -> Result<(), ConnectError> {
let github = GithubProvider::try_new(
    "YOUR_CLIENT_ID",
    "YOUR_CLIENT_SECRET".to_string().into(),
    "http://localhost:3000/auth/github/callback",
)?;
let _ = github;
Ok(())
}

Signed local OIDC fixture

Enable axum and mount mock_idp::mock_router_with_config only on an exact loopback listener. MockIdpConfig::try_new also rejects non-loopback issuer and callback URLs. The resulting fixture exercises discovery, an exact registered client/callback, expiring one-shot authorization codes, optional nonce, S256 PKCE, EdDSA ID-token validation through JWKS and bearer-protected userinfo.

Its signing seed and credentials are deterministic public test material. It is not a production identity provider, interactive login/consent UI, refresh-token service, federation implementation or OIDC conformance suite. Follow the local OIDC testing tutorial for a complete loopback setup.

Explicit corporate proxy

Live providers can receive a first-class proxy-aware transport without relying on ambient HTTP_PROXY state:

#![allow(unused)]
fn main() {
use rullst_connect::client::ReqwestClient;
use rullst_connect::prelude::{ConnectError, GithubProvider};
use std::sync::Arc;

fn github_through_corporate_proxy(
    proxy_password: impl Into<String>,
) -> Result<GithubProvider, ConnectError> {
    let proxy = ReqwestClient::try_with_proxy_basic_auth(
        "https://proxy.corp.example:8443",
        "proxy-user",
        proxy_password,
    )?;
    let github = GithubProvider::try_new(
        "YOUR_CLIENT_ID",
        "YOUR_CLIENT_SECRET".to_string().into(),
        "http://localhost:3000/auth/github/callback",
    )?;
    Ok(github.with_http_client(Arc::new(proxy)))
}
}

Proxy URLs are limited to an HTTP(S) scheme and authority, with no embedded credentials, path, query, or fragment. Authenticated non-loopback proxies must use HTTPS. The configured client uses only that explicit proxy; PAC/WPAD, SOCKS, proxy mTLS identity and deployment certification remain outside this bounded transport.

2. Start a Server-Bound Authorization

The recommended Axum path generates state and PKCE, stores their private counterparts in tower-sessions for ten minutes, and returns only the redirect URL:

#![allow(unused)]
fn main() {
use axum::response::Redirect;
use rullst_connect::prelude::{ConnectError, GithubProvider};
use rullst_connect::extractors::begin_oauth_session;
use tower_sessions::Session;

async fn start_github_authorization(
    session: &Session,
    github: &GithubProvider,
) -> Result<Redirect, ConnectError> {
    let authorization = begin_oauth_session(session, github).await?;
    Ok(Redirect::temporary(authorization.url()))
}
}

Use begin_oidc_session instead for Google, Apple, or a custom OIDC provider. It adds and stores an OIDC nonce as well.

3. Consume the Callback and Get the User

AuthSession consumes the challenge before checking its expiry and state. It then supplies the exact PKCE verifier and optional OIDC nonce to the provider:

#![allow(unused)]
fn main() {
use rullst_connect::extractors::AuthSession;
use rullst_connect::prelude::{
    ConnectError, GithubProvider, Provider as _, UniversalProfile,
};

async fn finish_github_callback(
    auth_session: AuthSession,
    github: &GithubProvider,
) -> Result<UniversalProfile, ConnectError> {
    let params = auth_session.exchange_params()?;
    let user = github.get_user(params).await?;
    Ok(user.universal_profile())
}
}

Only one managed challenge is active per browser session. Starting another login deliberately invalidates the earlier tab. The application must configure a durable production session store, Secure/HttpOnly/SameSite cookies, TLS, registered redirect URLs, account-linking policy, and post-login session rotation. See Server-Bound OAuth/OIDC Sessions.

🛡️ Manual State Handling

Non-Axum hosts may use the framework-neutral primitives directly. The host must atomically take the expected value from a short-lived server-side store before comparison; a reusable cookie value is not equivalent.

The following fragment is host pseudocode because the durable one-time store and callback extractor are deliberately application-provided:

use rullst_connect::pkce::generate_oauth_state;

let state = generate_oauth_state();
store_one_time_state(&state).await?; // application-provided durable operation
let url = github.redirect_url_with_state(&state);

let expected = take_one_time_state().await?; // atomically removes it
callback.verify_state(&expected)?;

🔄 Refreshing Tokens

If the provider returned expires_in plus a refresh token, bind the result to a statically dispatched, process-local coordinator when the callback receives it:

#![allow(unused)]
fn main() {
use rullst_connect::{AutoRefreshingSession, ConnectError, ConnectUser};
use rullst_connect::prelude::ExposeSecret as _;

async fn send_token_to_the_authorized_api(_: &str) -> Result<(), ConnectError> {
    Ok(())
}

async fn call_provider_api(
    github: &rullst_connect::providers::GithubProvider,
    user: &ConnectUser,
    token_received_at: u64,
) -> Result<(), ConnectError> {
    let session = AutoRefreshingSession::from_user_at(
        github,
        user,
        token_received_at,
    )?;
    let lease = session.access_token().await?;
    send_token_to_the_authorized_api(lease.access_token().expose_secret()).await?;
    Ok(())
}
}

The default checks 60 seconds before expiration. Refresh calls cannot overlap; callers waiting behind a successful refresh reuse its state. A response can replace the refresh token only after the lifetime and original provider user ID validate. Use access_token_at in deterministic workers/tests. Seal state_snapshot() with EncryptedTokenSnapshot before writing it to a dedicated application store:

#![allow(unused)]
fn main() {
use rullst_connect::{
    EncryptedTokenSnapshot, RefreshableTokenState, TokenSnapshotBinding,
    TokenSnapshotError, TokenSnapshotKey,
};

fn seal_for_storage(
    state: &RefreshableTokenState,
    key_bytes: [u8; 32],
    local_account_id: &str,
) -> Result<EncryptedTokenSnapshot, TokenSnapshotError> {
    let binding = TokenSnapshotBinding::try_new("github", local_account_id)?;
    let key = TokenSnapshotKey::try_new("oauth-primary-2026", key_bytes)?;
    EncryptedTokenSnapshot::seal(state, &key, &binding)
}
}

Persist only snapshot.as_str(). On restart, validate it with try_from_envelope, select the secret-manager key named by key_id(), and call open with the same trusted binding. Keys must be 32 bytes from a secret manager or CSPRNG; human passwords require an application-selected KDF. The envelope authenticates confidentiality, ownership binding and generation but does not provide a database transaction by itself.

Durable shared-local SQLite state

Enable rullst-connect/sqlite (or umbrella rullst/oauth-sqlite) to store the encrypted generations in one local SQLite file:

#![allow(unused)]
fn main() {
use rullst_connect::{
    RefreshableTokenState, SqliteTokenSnapshotStore, TokenSnapshotBinding,
    TokenSnapshotKey, TokenStoreError,
};

async fn persist_initial_generation(
    state: &RefreshableTokenState,
    key: &TokenSnapshotKey,
    account_id: &str,
) -> Result<(), TokenStoreError> {
    let store = SqliteTokenSnapshotStore::connect(
        "sqlite:///var/lib/my-app/oauth-tokens.sqlite",
        50_000,
    )
    .await?;
    let binding = TokenSnapshotBinding::try_new("github", account_id)?;
    store.insert_initial(&binding, state, key).await?;
    store.close().await;
    Ok(())
}
}

After refreshing from generation n, call compare_and_swap(&binding, n, &replacement, &key). BEGIN IMMEDIATE serializes local writers and only generation n + 1 can replace the observed row. The fixed schema persists its configured 1–1,000,000 row ceiling and restart/key metadata; malformed rows, configuration drift, quota exhaustion and stale replacement/deletion fail with typed redacted errors. The account locator is a SHA-256 pseudonymous digest, not an anonymity guarantee; the provider/account binding is also authenticated inside the ciphertext.

This local CAS does not serialize the earlier call to a remote OAuth provider. Applications must still authorize the account, lease that remote operation, reconcile a provider rotation lost to CAS, configure retry/backoff and local logout, keep keys in a secret manager, protect/backup the directory and provide multi-host replication when needed. A provider that does not support refresh continues to return a typed error.

🔒 Manual PKCE Support

Provider adapters expose PKCE (Proof Key for Code Exchange) where supported by the provider protocol. Some providers such as X (Twitter) v2 require it; applications must preserve and validate the verifier/state for the complete authorization transaction. The generic provider boundary accepts both values explicitly:

#![allow(unused)]
fn main() {
use rullst_connect::{
    ConnectError, ConnectUser,
    pkce::{generate_oauth_state, generate_pkce},
    provider::{ExchangeParams, Provider},
};

async fn exchange_with_pkce<P: Provider>(
    provider: &P,
    authorization_code: &str,
) -> Result<ConnectUser, ConnectError> {
    let state = generate_oauth_state();
    let (code_verifier, code_challenge) = generate_pkce();

    // Persist state + verifier in a short-lived server-side record before redirecting.
    let authorization_url =
        provider.redirect_url_with_pkce_and_state(&code_challenge, &state);
    let _ = authorization_url;

    // After atomically consuming and validating that state in the callback:
    provider
        .get_user(ExchangeParams {
            auth_code: authorization_code,
            code_verifier: Some(&code_verifier),
            ..Default::default()
        })
        .await
}
}

🧑‍💻 Full Example with Axum

You can find a complete working server using the Axum framework in the examples directory. Just run:

cargo run --example axum_server

📦 Releasing a New Version

Connect is released only through the repository-wide, topologically ordered release workflow. Do not publish this crate independently from a working tree. Follow the v12 release guide and require all candidate-SHA gates before creating a release tag.

🤝 Contributing

Feel free to open Issues and submit Pull Requests! Want to add a new provider? It’s easy! Just implement the Provider trait.

📄 License

This project is licensed under the MIT License.

rullst-messaging

rullst-messaging is the broker-neutral event boundary for Rullst. It is kept separate from rullst-connect because OAuth/OIDC identity federation and message-broker delivery have different security, availability, and retry semantics.

Current status

The crate has an implemented, bounded broker foundation:

  • a versioned immutable envelope;
  • bounded identifiers, headers, payloads, batches, leases, and retention;
  • topic-scoped idempotent publication;
  • consumer-group fan-out and competing-consumer claims;
  • expiring single-use acknowledgement tokens;
  • bounded retry, dead-letter views, and explicit purge;
  • deterministic time injection and reusable contract tests;
  • a canonical bounded v1 envelope wire codec with a deterministic byte fixture;
  • allowlisted W3C traceparent/tracestate propagation without baggage;
  • a feature-gated SQLite adapter that transactionally retains publications, subscriptions, claims, ACK/retry/DLQ state and idempotency across restart.
  • an explicit SQLite AES-256-GCM profile for header values and payloads, with immutable profile selection and bounded primary/prior-key rotation.
  • an opt-in static relational ORM outbox relay with exact stream/topic binding and publish-before-ACK crash/replay evidence.

The InMemoryBroker is suitable for offline tests, deterministic development, and explicitly process-local workloads. SqliteBroker uses a fixed schema and serialized SQLite write transactions; restart, two-instance contention, configuration drift and corrupt-row repair are tested. It is a durable local adapter, not a remote transport. Kafka, RabbitMQ, Redis Streams, NATS/JetStream, SQS/SNS, Google Pub/Sub, and Pulsar adapters remain roadmap work.

The wire codec is not a remote adapter: it neither opens broker connections nor maps a provider’s publish/ACK/retention semantics. Trace sampling, exporting, retention and tenant-aware correlation also remain host policy.

Security and correctness boundary

Payloads, idempotency values, acknowledgement tokens, and header values are not included in debug output. Tracing emits only bounded routing and decision metadata. Applications must still authorize who may publish or consume each topic and must make external effects idempotent.

Delivery is at least once. A valid acknowledgement consumes its lease exactly once, but no local ACK can make an arbitrary remote side effect atomic. Use the stable envelope ID at the side-effect boundary.

SqliteBroker::connect keeps the compatible plaintext profile. connect_encrypted uses randomized AES-256-GCM and authenticates immutable row metadata. Raw-storage, restart, wrong-key, tamper, row-swap, rotation, symlink and two-instance regressions are executable. It protects header values and payloads, not routing/idempotency/delivery metadata or the complete database. The deployment still owns keys, database-file permissions, protected backups, rollback detection, retention, disk monitoring and topic/tenant authorization. Reopening a namespace with different limits or a different storage profile fails closed instead of silently changing retained semantics.

The outbox relay does not make ORM and broker state one atomic transaction. It uses the committed event key for exact broker replay, publishes first and ACKs the exact ORM lease second. The application still supervises workers, retries, dead letters, cleanup, tenant/topic authorization and destination idempotency.

Continue with the brokered messaging tutorial or inspect the crate roadmap.

Rullst Mail 📬

Important

This page documents the unreleased v12 source. Use a path dependency from this checkout until the planned 12.0.0-rc.1 is published.

Vision preserved: additional providers and air-gapped/zero-leak ambitions were not silently removed; see their status and recommendation in the capability ledger.

rullst-mail is Rullst’s transactional email and mailables engine. Official dispatch paths pass through a pre-flight pipeline for CRLF protection, recipient checks, content security scanning, and DLP sanitization before queueing or transport delivery.


✨ Features

  • 🛡️ Typed failures: production delivery paths return MailError; malformed messages and provider configuration fail closed. CI and formal checks remain scoped evidence, not an absolute guarantee.
  • ⚡ Delivery and Test Drivers:
    • Resend (ResendDriver) — Native REST API with scheduled delivery & RFC 8058.
    • SendGrid (SendGridDriver) — Native v3 REST API with personalization & attachments.
    • Postmark (PostmarkDriver) — High-deliverability transactional REST API with Message Streams.
    • AWS SES v2 (AwsSesDriver, aws-ses) — official AWS SDK/SigV4 native transport with temporary/rotating credential support, plus offline fixture and an explicit legacy proxy boundary.
    • Native SMTP (SmtpDriver) — Pure async Lettre transport with TLS.
    • Memory & MailTrap (MemoryDriver, MailTrap) — Zero-I/O in-memory harness with fluent assertions.
    • Log (LogDriver) — Terminal and disk file logging (storage/logs/mail.log).
  • 🔀 Typed Circuit Breaker & Automatic Failover (FailoverDriver): Fails over only for transport, HTTP 5xx, provider rate-limit, or transient SMTP failures; permanent message/configuration/provider rejection stays on the original error path. Structured tracing exposes bounded decision fields without provider bodies.
  • 🏢 Auth-bound Multi-Tenancy Resolver (TenantMailResolver): Select isolated in-process drivers directly from a trusted Core TenantContext; registry failures and invalid IDs fail closed.
  • 📎 Bounded Attachments & Inline CID Assets: The shared pre-flight contract caps count and byte size, validates safe basenames/MIME/CID metadata and requires every unique inline CID to be referenced by HTML. Resend, SendGrid, Postmark, native SES and SMTP serialize the same owned-byte model; transports copy or Base64-encode as required.
  • 🔬 Opt-in Attachment Inspection (AttachmentInspectionGuard): A strict bounded local policy rejects executable magic, spoofed known types, active PDF/SVG, secrets and unsafe text links before transport. A static AttachmentInspector adapter boundary supports an independently operated production scanner.
  • 🚫 Durable Recipient Suppression (sqlite): SuppressionGuard checks manual, hard-bounce and spam-complaint state before transport. The SQLite store binds verified provider/event identities, detects conflicting replay, enforces immutable quotas transactionally and survives restart or multiple local processes.
  • 📊 Secret-Minimized Delivery Observability: ObservedMailDriver records only a bounded provider label, terminal outcome, latency, attachment count and scheduling/tenant booleans through a non-failing static observer.
  • ⏰ Durable Scheduling (.send_at(), .send_in()): SQLite and Redis queues persist schedules for up to 366 days and never claim early; direct Resend/SendGrid delivery uses provider scheduling. Real SMTP, Postmark, Log and SES paths reject future direct delivery and must use a durable queue; offline fixtures may retain the timestamp for assertions.
  • 🕵️ Outbound Phishing & Homograph URL Interceptor (.validate_security()): Pre-flight detection of mixed-script Unicode IDN spoofed domains (pаypal.com with Cyrillic characters) and dangerous URI schemes (javascript:, data:text/html).
  • 📜 RFC 8058 One-Click List-Unsubscribe: Automatic compliant header injection (List-Unsubscribe and List-Unsubscribe-Post: List-Unsubscribe=One-Click).
  • 🔤 Automatic Plain-Text Fallback: Automatic HTML-to-plain-text conversion without manual duplication.
  • 🔒 Outbound DLP Secret Scanner: Proactive credential masking (AWS keys, passwords, API tokens, bearer tokens) before emails leave your server.
  • 📦 Async Background Worker Queues: Native non-blocking dispatch via rullst-core::queue.
  • 🧪 Explicit offline provider mode: empty or mock_* credentials select DeliveryMode::OfflineMock, never perform network I/O, and are inspectable through OfflineMailMock.
  • 🛠️ Safe CLI Scaffolding: Generates registered facade-based Welcome, Password Reset, OTP, Invoice, custom, evidence-aware NFS-e/international receipt, and explicit D+1/D+3/D+7 dunning mailables, refusing unsafe names/collisions and escaping dynamic HTML.
  • 🧾 Payment-Bound PDF Delivery: The opt-in capital-invoice bridge accepts only Capital’s final evidence-bound PaidInvoice, attaches bounded HTML/PDF, applies pre-flight and preserves a stable key for the application outbox.

🚀 Quickstart

1. Composing and Sending an Email

use rullst_mail::{Mail, Message};
use chrono::{Utc, Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let logo_bytes = b"replace with application-owned PNG bytes";

    let message = Message::new()
        .to("alice@example.com")
        .from("noreply@rullst.dev")
        .subject("Welcome to Rullst!")
        .html(r#"
            <h1>Welcome, Alice!</h1>
            <p>Thanks for joining our platform.</p>
            <img src="cid:app_logo" alt="Logo" />
        "#)
        .attach_cid("app_logo", "logo.png", logo_bytes.to_vec(), "image/png")
        .attach_bytes("welcome_guide.pdf", b"%PDF-1.4...".to_vec(), "application/pdf")
        .send_in(std::time::Duration::from_secs(60)) // Deliver in 1 minute
        .unsubscribe_url("https://rullst.dev/unsub/alice");

    // The mandatory pipeline validates and sanitizes before queueing or delivery.
Mail::send(message).await?;

    Ok(())
}

For a schedule that survives process restarts, operate a built-in queue and keep its worker handle alive:

#![allow(unused)]
fn main() {
use rullst_core::queue::{Queue, Worker};
use rullst_mail::{register_mail_handler, Mail, Message};

async fn schedule() -> Result<(), Box<dyn std::error::Error>> {
let queue = Queue::sqlite("sqlite://storage/jobs.db").await?;
let mut worker = Worker::new(&queue).poll_interval(100);
register_mail_handler(&mut worker);
let worker_handle = worker.run()?;

let message = Message::new()
    .to("alice@example.com")
    .subject("Scheduled update")
    .text("Delivered after the durable due time")
    .send_in(std::time::Duration::from_secs(60));
Mail::enqueue(&queue, message).await?;

// Keep `worker_handle` in application state; shut it down during graceful exit.
worker_handle.shutdown().await?;
Ok(())
}
}

Execution begins on the first worker poll after the UTC timestamp and remains at-least-once. Queue scheduling does not promise exact wall-clock execution, exactly-once provider delivery, or provider acceptance.


2. Resilient Multi-Driver Failover (Circuit Breaker)

#![allow(unused)]
fn main() {
use rullst_mail::drivers::{FailoverDriver, PostmarkDriver, ResendDriver};
use std::time::Duration;

fn build_failover() -> Result<FailoverDriver, rullst_mail::MailError> {
let primary = ResendDriver::try_new("re_...")?;
let fallback_1 = PostmarkDriver::try_new("pm_token_...")?;

let failover_driver = FailoverDriver::new(primary)
    .with_fallback(fallback_1)
    .with_threshold(3) // Trip circuit after 3 consecutive failures
    .with_cooldown(Duration::from_secs(60)); // Cooldown for 60s
Ok(failover_driver)
}
}

3. Dynamic B2B Multi-Tenancy Routing

#![allow(unused)]
fn main() {
use rullst_core::security::TenantMembership;
use rullst_mail::{Message, ResendDriver, TenantMailResolver};

async fn send_tenant_message(
    message: &Message,
) -> Result<(), Box<dyn std::error::Error>> {
let resolver = TenantMailResolver::new();
let membership = TenantMembership::try_new(["tenant_globex"])?;
let context = membership.select("tenant_globex")?;

// Register tenant-specific API credentials during application configuration.
resolver.register_for_context(
    &context,
    ResendDriver::try_new("re_globex...")?,
)?;

// The context must be derived from trusted authentication/membership state.
resolver.send_for_context(&context, &message).await?;
Ok(())
}
}

The registry is intentionally process-local. Durable encrypted credential storage, rotation, and distribution between instances remain application/deployment concerns.


4. Fast Unit & Integration Testing with MailTrap

#![allow(unused)]
fn main() {
use rullst_mail::{Mail, MailTrap, Message};

#[tokio::test]
async fn test_user_registration_email() {
    Mail::set_driver(Box::new(MailTrap::driver()));
    MailTrap::clear();

    let msg = Message::new()
        .to("alice@example.com")
        .subject("Welcome to Rullst!")
        .html("<p>Please verify your email address.</p>")
        .attach_bytes("terms.pdf", b"%PDF...".to_vec(), "application/pdf")
        .unsubscribe_url("https://example.com/unsub/alice");

    Mail::send_now(msg).await.unwrap();

    // Fluent assertions
    MailTrap::assert_sent_to("alice@example.com")
        .with_subject("Welcome to Rullst!")
        .with_body_contains("Please verify your email")
        .with_attachment_count(1)
        .with_attachment_named("terms.pdf")
        .with_unsubscribe_url("https://example.com/unsub/alice");
}
}

5. Scaffolding Mailables with CLI

# Generate Welcome & Onboarding email
cargo rullst make:mail WelcomeEmail --welcome

# Generate Time-limited Password Reset email
cargo rullst make:mail PasswordReset --reset

# Generate Two-Factor OTP code email
cargo rullst make:mail OtpVerification --otp

# Generate SaaS Invoice receipt email
cargo rullst make:mail InvoiceReceipt --invoice

# Generate an evidence-aware NFS-e/international receipt
cargo rullst make:mail-invoice

# Generate explicit D+1/D+3/D+7 payment-recovery stages
cargo rullst make:mail-dunning

The fiscal template enables the umbrella capital feature and accepts a typed FiscalResponse: an OfflineMock always renders as [PREVIEW — NOT AUTHORIZED]. The dunning template does not infer due dates, schedule itself, or mutate access. Both generated build paths run the mandatory mail pre-flight and reject unsafe links; tax provenance, billing state, scheduling, and policy remain application-owned.

For native payment-bound PDF delivery, enable rullst-mail/capital-invoice (or umbrella rullst/capital-mail) and use PaidInvoiceDelivery::prepare. It rejects non-final/mock evidence and recipient/amount/currency substitution. Applications still reconcile webhooks and atomically claim the stable delivery key; provider acceptance and exactly-once delivery are not promised.


6. Pre-Flight Deliverability & Disposable Email Filtering

Prevent sender quota waste and fake user signups with built-in deliverability checks and blocked temporary domains:

#![allow(unused)]
fn main() {
use rullst_mail::{is_disposable_email, validate_email_deliverability, Message};

// 1. Direct email address validation
assert!(validate_email_deliverability("user@company.com").is_ok());
assert!(is_disposable_email("spammer@mailinator.com"));

// 2. Pre-flight check before dispatching
let msg = Message::new()
    .to("user@mailinator.com")
    .subject("Welcome!");

if msg.is_disposable() {
    eprintln!("Blocked disposable email address!");
}
}

7. Composing inspection, suppression, and observability

The wrappers use static dispatch and may be composed around any MailDriver. Enable rullst-mail/sqlite (or umbrella rullst/mail-sqlite) for the durable local suppression store:

#![allow(unused)]
fn main() {
use rullst_mail::{
    AttachmentInspectionGuard, BoundedMailObserver, LocalAttachmentInspector,
    MailDriver, MemoryDriver, Message, MutableSuppressionStore,
    ObservedMailDriver, SqliteSuppressionStore, SuppressionEvent,
    SuppressionGuard, SuppressionReason,
};
use std::time::{SystemTime, UNIX_EPOCH};

async fn deliver() -> Result<(), Box<dyn std::error::Error>> {
let store = SqliteSuppressionStore::connect(
    "sqlite://storage/mail-suppressions.sqlite",
    100_000,
    500_000,
).await?;

// Only record events after the provider-specific signature was authenticated.
let observed_at = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
store.record(SuppressionEvent::try_new(
    "postmark",
    "verified-event-id",
    "blocked@example.com",
    SuppressionReason::HardBounce,
    observed_at,
)?).await?;

let (transport, _) = MemoryDriver::isolated();
let inspected = AttachmentInspectionGuard::new(
    transport,
    LocalAttachmentInspector::strict(),
);
let suppressed = SuppressionGuard::new(inspected, store);
let observer = BoundedMailObserver::new(10_000)?;
let driver = ObservedMailDriver::try_new("memory", suppressed, observer)?;
driver.send(&Message::new()
    .to("recipient@example.com")
    .subject("Bounded delivery")
    .text("Hello"))
    .await?;
Ok(())
}
}

SuppressionEvent does not verify a webhook signature: a provider-specific adapter must authenticate the exact event first. SQLite is shared durable local state, not multi-host replication or encrypted storage. Keep replay IDs at least as long as every provider’s redelivery window. The local attachment inspector is a bounded heuristic, not antivirus, sandbox execution, recursive archive inspection or content disarm. The default observer is bounded and process-local; the host owns any external metrics/tracing sink, retention and alerts.


8. Authenticated open/click tracking primitives

Generate versioned, purpose-bound HMAC-SHA256 tracking tokens with a mandatory 32-byte secret and bounded validity. HMAC authenticates but does not encrypt: recipient and target URL remain base64-readable in the current token. The application owns consent, minimization, retention, redirects and applicable privacy-law decisions.

#![allow(unused)]
fn main() {
use rullst_mail::{TrackingEngine, TrackingVerifier, PIXEL_1X1_GIF, Message};
use std::time::Duration;

fn tracking_example() -> Result<(), Box<dyn std::error::Error>> {
let secret = b"replace-with-32-or-more-random-key-bytes";
let now_unix_seconds = 1_800_000_000;

// Fluent open & click tracking injection
let tracked_msg = Message::new()
    .to("user@example.com")
    .subject("Monthly Newsletter")
    .html("<p>Check out our <a href=\"https://rullst.dev/pricing\">pricing</a>.</p>")
    .try_with_open_tracking("https://app.com", secret, "campaign_2026")?
    .try_with_click_tracking("https://app.com", secret)?;

let token = TrackingEngine::try_generate_open_token(
    secret,
    "user@example.com",
    "campaign_2026",
    now_unix_seconds,
)?;

// Default verification enforces a 30-day TTL.
let event = TrackingEngine::verify_open_token(secret, &token)?;
println!("Email opened by {} for campaign {}", event.email, event.campaign_id);

// Endpoints needing single-consumption semantics can reject replay explicitly.
let verifier = TrackingVerifier::new(Duration::from_secs(24 * 60 * 60), 100_000)?;
let event = verifier.verify_open_once(secret, &token, now_unix_seconds)?;
let _ = (tracked_msg, event, PIXEL_1X1_GIF);
Ok(())
}
}

9. Transactional Test Fixtures with MailFactory

Quickly generate standard transactional emails for local preview and testing:

#![allow(unused)]
fn main() {
use rullst_mail::MailFactory;

let welcome_msg = MailFactory::fake_welcome("alice@example.com", "Alice", "My SaaS App");
let reset_msg = MailFactory::fake_password_reset("bob@example.com", "https://app.com/reset?token=xyz", 15);
let otp_msg = MailFactory::fake_otp("carol@example.com", "492015", 5);
let invoice_msg = MailFactory::fake_invoice("david@example.com", "INV-2026-001", 9900, "USD");
let alert_msg = MailFactory::fake_security_alert("eve@example.com", "Unrecognized Login", "198.51.100.1", "Chrome / macOS");
}

10. Native AWS SES v2 with SigV4

Enable the opt-in official SDK transport:

[dependencies]
rullst-mail = { version = "12.0.0-rc.1", features = ["aws-ses"] }
aws-config = "1.11"

MAIL_DRIVER=ses selects native mode when both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY exist. AWS_SESSION_TOKEN is accepted for temporary credentials. Without those variables, the existing empty/mock_* token rule selects the offline fixture; a real AWS_SES_BEARER_TOKEN is usable only with an explicit trusted proxy URL.

Long-running services should inject a refreshing credential provider or a caller-built SDK config instead of freezing credentials:

This snippet intentionally uses the application’s direct aws-config dependency shown above, which is not re-exported by Rullst; it is therefore checked in the AWS integration workflow rather than the umbrella doctest:

use rullst_mail::{AwsSesDriver, MailDriver, Message, aws_ses_sdk};

async fn deliver() -> Result<(), Box<dyn std::error::Error>> {
let shared = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
let config = aws_ses_sdk::Config::new(&shared);
let driver = AwsSesDriver::from_native_config(config)?;
driver.send(&Message::new()
    .to("recipient@example.com")
    .from("verified@example.com")
    .subject("Signed by AWS SigV4")
    .text("Hello from Rullst"))
    .await?;
Ok(())
}

The application still owns AWS identity/domain verification, sandbox exit, IAM least privilege, quotas, reputation, bounce/complaint handling and monitoring. A successful MessageId is provider acceptance, not proof of inbox delivery. The native adapter rejects SES field limits and an encoded message estimate over 40 MiB before network I/O; provider 429 responses preserve a bounded delta-seconds Retry-After for failover/retry policy.


⚙️ Configuration (Rullst.toml or Environment Variables)

[mail]
driver = "resend" # "log" | "memory" | "smtp" | "resend" | "sendgrid" | "postmark" | "ses"

Environment variables:

  • MAIL_DRIVER: Select active driver (log, memory, smtp, resend, sendgrid, postmark, ses).
  • RESEND_API_KEY: API key for Resend.
  • SENDGRID_API_KEY: API key for SendGrid.
  • POSTMARK_SERVER_TOKEN: Server API token for Postmark.
  • AWS_REGION: Region used by native SigV4 signing or SES proxy/mock metadata.
  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: Select native SES when the aws-ses feature is enabled; both must be present.
  • AWS_SESSION_TOKEN: Optional temporary-credential session token.
  • AWS_SES_BEARER_TOKEN: Bearer token for an explicit trusted proxy; it is never sent to AWS as a substitute for SigV4.
  • AWS_SES_ENDPOINT: Native SDK base endpoint or complete proxy send URL; HTTPS is required except for loopback integration tests.
  • MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD: SMTP credentials.
  • MAIL_LOG_PATH: Path for log file (default: storage/logs/mail.log).

For Resend, SendGrid, Postmark, the SES fixture/proxy, and authenticated SMTP, an empty credential or one beginning with mock_ selects the deterministic offline fallback. Use driver.delivery_mode() and OfflineMailMock::deliveries() to assert this explicitly in tests.


Scope and product boundaries

rullst-mail is a transactional-delivery library, not a marketing CRM or a claim that third-party delivery infrastructure is unnecessary.

Implemented building blocks include:

  • typed message construction and escaped generated templates;
  • explicit SMTP, Resend, SendGrid, Postmark, log and memory drivers, plus the opt-in official-SDK SES v2 transport and bounded SES proxy/mock adapter;
  • deterministic offline mode for empty or mock_* provider credentials;
  • an in-memory MailTrap and MailFactory fixtures;
  • bounded retry/failover helpers, tenant-driver resolution, attachments, provider-specific scheduling fields, and durable SQLite/Redis due times;
  • opt-in attachment inspection, process-local or shared-local suppression, and content-minimized local delivery observations;
  • HMAC-authenticated (not encrypted) tracking tokens with expiry/replay helpers, URL checks, and bounded secret-redaction heuristics.

These components do not provide deliverability, sender-domain reputation, legal consent, unsubscribe policy, durable campaign orchestration, a visual marketing editor, or a production inbox. Provider acceptance is not proof of delivery. Tracking pixels/links have privacy and consent implications that the application must evaluate for each jurisdiction and use case.

Choose a delivery provider and operational policy based on measured volume, region, data processing terms, bounce/complaint handling, retention, cost, and failover tests. Rullst publishes no universal latency, price, or feature comparison against commercial platforms.

Attachment limits are 32 items, 20 MiB per item and 25 MiB of raw bytes in aggregate before transport encoding. Provider/account limits can be lower. The base pipeline validates metadata but treats bytes as opaque. The opt-in local inspector recognizes only its documented bounded formats and heuristics; use a production scanner adapter when malware, archive, sandbox or CDR policy is required.

Rullst Auth

rullst-auth provides Argon2id password hashing, versioned AES-GCM cookie sessions, role-based authorization middleware, WebAuthn/passkey ceremony verification, and an opt-in application JWT policy.

Application JWTs are enabled with jwt. The sqlite feature also enables JWT and adds durable shared JWT revocation plus passkey device state. OAuth2/OIDC providers remain a separate trust boundary enabled with oauth, which re-exports rullst-connect.

Passwords

Use the asynchronous functions inside HTTP handlers so Argon2 work runs on Tokio’s blocking pool:

#![allow(unused)]
fn main() {
use rullst_auth::{AuthError, hash_password_async, verify_password_async};

async fn verify_login(password: String) -> Result<bool, AuthError> {
    let hash = hash_password_async(password.clone()).await?;
    Ok(verify_password_async(password, hash).await)
}
}

Passwords longer than 72 bytes are rejected. needs_rehash compares the algorithm, version, memory, iteration, and parallelism parameters.

Encrypted sessions

make_login_cookie and decrypt_session use a versioned AES-256-GCM envelope with authenticated metadata and an operating-system nonce. APP_KEY must contain at least 32 bytes, must not be a documented placeholder, and must satisfy the entropy check.

#![allow(unused)]
fn main() {
use rullst_auth::{AuthError, decrypt_session, get_app_key, make_login_cookie};

fn round_trip(user_id: i32) -> Result<i32, AuthError> {
    let cookie = make_login_cookie(user_id)?;
    let token = cookie
        .split(';')
        .next()
        .and_then(|part| part.split_once('='))
        .map(|(_, value)| value)
        .ok_or_else(|| AuthError::General("session cookie is malformed".to_string()))?;
    decrypt_session(token, &get_app_key()?)
}
}

WebAuthn/passkeys

PasskeyAuth validates exact RP origin and ID binding, one-time expiring challenges, client-data ceremony type, user-presence/user-verification flags, ES256 COSE keys, P-256 points, credential IDs, signatures, and monotonic counters. Only none attestation is advertised and accepted. With sqlite, SqlitePasskeyStore supplies bounded file-backed registration, listing, renaming, revocation and optimistic counter CAS shared by processes on the same SQLite file. finish_authenticate verifies the ES256 ceremony and atomically advances the stored counter; a stale concurrent update fails. Revoked records remain in inventory and continue to consume quota.

Challenge state remains process-local inside PasskeyAuth, so a multi-instance deployment needs sticky ceremony routing or a custom shared challenge layer. The adapter does not establish normative WebAuthn conformance, encrypt or replicate the database, or replace application device-ownership policy.

Application JWTs

The jwt feature provides ApplicationJwtPolicy, versioned HS256 claims, strong key validation, required issuer/audience/subject/time/JTI claims, bounded TTL and scope policy, and kid-based key rotation. Every verification receives a JwtRevocationStore. Production policies reject the bundled bounded in-memory store because it is process-local. With sqlite, SqliteJwtRevocationStore persists token IDs and monotonic subject session versions behind a stored quota. Its BEGIN IMMEDIATE mutations are visible to local processes, expired token rows are pruned before capacity checks, and ApplicationJwtPolicy::verify_async checks that shared state.

The SQLite boundary is durable across restarts but not replicated across hosts. The deployment owns trusted paths, file permissions/encryption, backup, availability and disaster recovery. This API does not verify third-party OAuth/OIDC tokens or provide refresh tokens.

RBAC

Implement HasRole for the authenticated user type and install RequireRoleLayer::<User>::new("Admin"). Authentication middleware must insert that user into Axum request extensions before the role layer executes.

OAuth2/OIDC

Enable oauth for the rullst_auth::connect re-export. Provider configuration, discovery, JWKS rotation, and deterministic offline fixtures are implemented by rullst-connect.

Security-sensitive functions return typed errors or a false verification result. The repository’s zero-panic CI checks production library paths; this policy is not an absolute guarantee about all dependencies or host failures.

Rullst Core ⚙️

rullst-core contains Rullst’s runtime primitives, Axum-compatible routing, state management, health probes, process telemetry, queues, and configuration helpers.

Core is runtime-only by default. Enable orm for ORM bootstrap/artisan and database-backed feature flags, and queue-sqlite for the SQLite queue driver. The umbrella rullst crate enables both by default, while domain crates opt in only when they actually use them.

Queue monitoring capabilities are driver-specific. The trait defaults for listing all jobs, retrying failures and purging failures return QueueError::Unsupported; they never fabricate an empty snapshot or successful mutation. purge_failed_jobs is the canonical facade method. The deprecated purge_completed_jobs name is retained only as a source-compatibility alias for the historical operation, which actually removed failed jobs.

Cache diagnostics are driver-specific too. Cache::inspect(limit) accepts 1–200 and returns sorted logical-key, UTF-8 value-length and remaining-TTL metadata for Memory and Redis without the value. Exact keys are still application data and CacheEntryMetadata::logical_key() belongs only inside an authorized diagnostic boundary; its Debug output redacts the key. Custom drivers return CacheError::InspectionUnsupported unless they implement the bounded method. The live Redis CI/release contract checks metadata, TTL and non-disclosure; it does not prove cluster/failover or operator authorization.

SQLite deletes successful jobs by default. Applications that need a real Studio/operations history can opt in with Queue::sqlite_with_completed_history(database_url, retained_jobs). The validated limit is 1–100,000 records; status transition and pruning commit in one transaction, and purge_completed_history removes the retained successes. Rows still contain the original payload, so access control and retention policy belong to the host. Redis/custom drivers do not inherit this policy implicitly.

Queue::dispatch_at persists a due timestamp for at most 366 days through the built-in SQLite and Redis drivers. SQLite filters claims by local wall-clock milliseconds; Redis atomically promotes bounded batches using Redis server time. Neither backend claims a scheduled job early. Execution starts on the first worker poll after it becomes due and retains the queue’s at-least-once semantics. Custom drivers return QueueError::Unsupported for future timestamps unless they explicitly implement durable scheduling.

ApplicationLifecycle supplies an opt-in process-local startup/readiness/drain contract. Up to 32 immutable validated component labels can gate readiness and application admission; /ready publishes only aggregate counts, not labels or dependency errors. Server::with_lifecycle marks the phase ready after binding, begins draining before Axum’s graceful wait, and marks it stopped on completion or startup failure. run_with_shutdown accepts a caller-owned trigger for embedded supervisors and deterministic tests. The lifecycle does not run dependency probes, coordinate replicas, authorize users, or guarantee load balancer propagation.

✨ Core Features & Subsystems

  • Axum-compatible routing: rullst::Router wraps and converts to/from axum::Router; application latency depends on handlers, middleware, build profile, and deployment.
  • Typed server functions: concrete async #[server_function] items share owned Serde arguments/results through the versioned rullst.client v1 envelope. The generated native router and Wasm caller enforce same-origin paths, bounded bodies, correlation and redacted errors; hosts still own identity, tenant, authorization, idempotency and rate-limit policy.
  • Rullst Radar (rullst::radar): Collects process RSS/CPU where an OS probe is supported, Tokio task/yield observations when a runtime is available, and process uptime. Unsupported probes return None.
  • Prometheus /metrics Exporter: Text-format metrics served at GET /metrics; formatting and collection have bounded runtime cost.
  • Kubernetes probe routes (rullst::health): the simple health_router reports process availability and uptime. The opt-in health_router_with_lifecycle returns readiness from the same bounded state that gates Server request admission; the application still performs and times out its own dependency checks.
  • Interactive Scalar API Docs (rullst::scalar): OpenAPI documentation UI mounted at /docs, with a pinned CDN asset and a status-only fallback. A missing or malformed openapi.json returns 503.
  • Typed framework errors: startup, queues, validation, scheduling, storage, and other subsystems expose their own typed errors. Applications may compose those into an application-owned AppError; Core does not define one global application error type.
  • Durable scheduled queues: SQLite and Redis persist bounded due timestamps; the live Redis CI contract proves that an immediate job remains claimable while a future job stays unavailable.
  • Opt-in completed-job monitoring: SQLite can retain and atomically prune a configured number of successful jobs; the privacy-safe default remains immediate deletion.
  • Bounded cache metadata: Memory and Redis expose value length and TTL for at most 200 sorted entries, never cached values. Rullst Studio renders keyed opaque identifiers and one-entry invalidation rather than exact keys or bulk flush.

🚀 Usage

Most applications can use the re-exports provided by the umbrella rullst crate instead of depending on rullst-core directly.

Mounting lifecycle-aware Health Probes & Prometheus Metrics

These optional surfaces are application-owned and must be mounted explicitly. Protect or isolate /metrics when its operational data should not be public.

use rullst_core::{
    ApplicationLifecycle, Router, Server,
    health::health_router_with_lifecycle,
    radar::radar_metrics_router,
    scalar::scalar_docs_router,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let lifecycle = ApplicationLifecycle::new();
    let app = Router::new()
        .merge_axum(health_router_with_lifecycle(lifecycle.clone()))
        .merge_axum(radar_metrics_router())   // GET /metrics (Prometheus)
        .merge_axum(scalar_docs_router("/openapi.json")); // GET /docs
    Server::new(app)
        .with_lifecycle(lifecycle)
        .run(3000)
        .await?;
    Ok(())
}

Axum First-Class Escape Hatches & Tower Interoperability

rullst::Router provides bidirectional conversion with axum::Router and accepts compatible tower::Layer values:

#![allow(unused)]
fn main() {
use rullst::Router;
use axum::routing::get;
use tower_http::cors::CorsLayer;

async fn handler() -> &'static str { "ok" }

let mut router = Router::new()
    .route("/hello", get(handler))
    .fallback(|| async { (axum::http::StatusCode::NOT_FOUND, "not found") })
    .layer(CorsLayer::permissive());

// Direct conversion to raw axum::Router
let axum_app: axum::Router = router.into();

// Or wrap an existing Axum router
let rullst_app: Router = axum_app.into();
}

🔐 Security Audit & Reliability

Repository workflows exercise Core with unit, integration, fuzz, and Miri jobs within their declared scopes. Consult the exact workflow run and commit for evidence; these tools do not prove the absence of every panic, leak, or vulnerability.

Rullst Capital 💰

“Enterprise Multi-Gateway Billing, SaaS Analytics & Fiscal Engine”

rullst-capital provides a unified financial foundation for SaaS, digital commerce, and marketplace platforms written in Rust. It includes multi-provider adapter surfaces, recurring-subscription models, international payout helpers, and a bounded Brazilian National NFS-e preparation pipeline. Live provider and fiscal production readiness must be established per adapter and environment.


⚡ Capability & Lifecycle Matrix

SubsystemLifecycle StatusDescription
Direct Gateways🟠 [Partial]11 payment/payout adapter surfaces with pooled HTTP clients and deterministic mocks. Live method coverage, provider acceptance tests, retry semantics, and reconciliation are not uniform yet.
Outbound Failure Boundary🟢 [Implemented / Bounded]Reviewed live methods share finite timeouts, disabled redirects/ambient proxies, one-MiB JSON parsing, HTTPS checkout-location validation, and redacted permanent/transient/rate-limited failures. Rullst performs no automatic mutation retry.
Subscription Lifecycle🟠 [Partial]Checkout, portal, cancellation, pause, usage, coupon, trial, status, and webhook APIs exist, but not every provider implements and verifies every method end-to-end.
Webhook Processing🟢 [Implemented / Bounded]Axum and opt-in Actix middleware call one canonical bounded verifier; named adapters implement signature verification and freshness checks. The opt-in webhook-sql ledger shares bounded payload or semantic-event claims across SQLite, PostgreSQL, MySQL, and MariaDB processes. Relational handlers can claim a stable provider event ID with one domain mutation in a caller transaction. Cross-system exactly-once and reconciliation remain application work; Alipay RSA2 remains fail-closed.
Metered Billing🟢 [Implemented / Bounded]Current Stripe Meter Events and Lemon Squeezy Usage Records shapes with provider-specific identity/action, bounded response binding and deterministic non-live mocks. Durable application-outbox claiming and provider-account evidence remain explicit.
Paid Invoice Rendering🟢 [Implemented / Feature-gated]Exact validated minor units, escaped HTML, bounded paginated A4 PDF and a final-success e-mail/amount/currency binding. The downstream Mail bridge sends the attachment but durable outbox claiming and exactly-once delivery remain application work.
SaaS MRR/ARR Analytics🟢 [Implemented / Bounded]In-memory revenue metrics and churn calculations for supplied records; this is not an accounting ledger or provider reconciliation engine.
NFS-e 1.01 Local Pipeline🟢 [Implemented / Bounded]Strict ordinary-service DPS builder, checksum-pinned closed-catalog validation of official XSD sources with one exact documented production regex-anchor compatibility normalization, protected PKCS#12 RSA-SHA256/inclusive-C14N XMLDSig, signed-tpAmb binding, independent local signature verification, deterministic dpsXmlGZipB64 request JSON, bounded signed-authorization and structured-rejection parsing, and bounded rustls mTLS client construction.
NFS-e Local Command Journal🟢 [Implemented / Bounded]Single-active-writer HMAC-chained prepared/terminal evidence, exact replay/conflict handling, restart recovery of minimized pending descriptors, hard record/byte quotas and externally retainable exact-tip checkpoints. It stores no XML, access key, response messages or certificate data and does not transmit or retry.
NFS-e Offline Sandbox🟡 [Offline Mock]Deterministic offline mock fixtures (NfseEnvironment::Mock) for local development and CI testing.
SEFIN Live NFS-e Homologation🔵 [Roadmap / External Evidence]Full emitter/ICP-Brasil certificate policy, deployment-owned request/outbox and reconciliation storage, retained official protocol fixtures, real A1 restricted-environment tests, independent review, and official homologation. Transmission is disabled.

📦 Supported Payment & Payout Providers

rullst-capital includes decoupled adapter surfaces for 11 global and regional gateways. The list preserves the intended product reach; it does not mean every provider product, fee, payment method, tax promise, or live API path has been independently homologated by Rullst:

  1. 💳 Stripe: Global card checkouts, Customer Portal, and recurring subscriptions.
  2. 🍋 Lemon Squeezy: Merchant of Record (MoR) with automated global tax compliance.
  3. 🌎 Mercado Pago: LATAM subscriptions, Pix, and credit card checkouts.
  4. InfinitePay: Ultra-low-fee domestic Brazilian Pix and installment credit cards.
  5. 📱 PicPay: Brazilian digital wallet and QR-code checkout flows.
  6. 🐻‍❄️ Polar: Developer-first MoR for monetizing GitHub repositories and SaaS software.
  7. 🛶 Paddle: Global B2B SaaS quote-to-cash with EU VAT handling.
  8. 🇮🇳 Razorpay: Recurring UPI Autopay and credit card orders in India & APAC.
  9. 💸 Wise: High-speed, multi-currency international contractor payouts (40+ currencies).
  10. 🪙 Coinbase Commerce: On-chain cryptocurrency payments (Bitcoin, Ethereum, Solana, USDC).
  11. 🌏 Alipay: Cross-border Chinese digital wallet checkouts (支付宝).

🚀 Usage Examples

Shared outbound failure contract

CapitalError::Provider carries a redacted ProviderFailure for request construction, transport, non-success HTTP status, oversized/malformed JSON, or semantic response mismatch. Its provider and operation labels are static and safe for low-cardinality telemetry; the value deliberately omits URLs, credentials, bodies, and raw transport errors.

#![allow(unused)]
fn main() {
use rullst_capital::{CapitalError, ProviderFailureClass};

fn record_disposition(error: &CapitalError) -> &'static str {
    match error {
        CapitalError::Provider(failure) => match failure.class() {
            ProviderFailureClass::Permanent => "permanent",
            ProviderFailureClass::Transient => "transient",
            ProviderFailureClass::RateLimited => "rate_limited",
            _ => "unknown",
        },
        _ => "not_provider_transport",
    }
}
}

HTTP 429 is rate-limited; transport failures and HTTP 408, 425, and 5xx are transient; request-build, response-shape, and other HTTP failures are permanent. Only numeric Retry-After delta seconds are retained and they are capped at 24 hours. These are scheduling hints, not a generic retry engine: non-idempotent operations must not be repeated without a durable, provider-forwarded idempotency key and reconciliation.

1. Initializing a Provider and Creating a Checkout Session

use rullst_capital::providers::stripe::StripeProvider;
use rullst_capital::BillingProvider;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let stripe = StripeProvider::new(
        "sk_live_your_stripe_api_key",
        "whsec_your_webhook_signing_secret",
    );

    let session = stripe
        .create_checkout_session(
            "customer@example.com",
            "price_pro_monthly",
            "https://example.com/billing/complete",
        )
        .await?;

    println!("Checkout URL: {session}");
    Ok(())
}

2. Provider-Specific Metered Usage

MeteredBillingProvider uses an associated request type so the framework does not confuse Stripe customer/meter identity with Lemon Squeezy subscription-item identity. StripeMeterEvent implements the current form-encoded Meter Events contract and forwards its identifier as both event identity and idempotency header. LemonSqueezyUsageRecord implements the current JSON:API relationship and requires Increment or Set to match provider aggregation.

Both paths validate positive bounded quantities, bind accepted responses, cap response JSON to one MiB and return visibly non-live deterministic mocks. A Stripe identifier has rolling provider deduplication. Lemon’s application event key is not accepted by the provider request, so claim it in a durable outbox before sending. Live-account acceptance, retry/reconciliation and entitlements remain application/release evidence.

3. Payment-Bound Invoice PDF and Mail

Enable rullst/capital-mail or the separate rullst-capital/invoice-pdf and rullst-mail/capital-invoice features. A PaidInvoice can be constructed only from final Succeeded evidence matching the invoice recipient, exact minor-unit total and currency. PaidInvoiceDelivery::prepare generates escaped HTML and a bounded PDF attachment and runs Mail’s mandatory pre-flight.

The stable delivery key is an application outbox identity, not a distributed lock. The application must reconcile webhooks, claim that key atomically and own at-least-once retries/provider attachment policy.

4. Verified Webhook Signature Handling

The low-level provider contract below illustrates exact-byte verification. HTTP applications should normally mount verify_webhook on Axum or verify_webhook_actix_with_state on Actix so body limits, normalized event insertion, and replay rejection are applied before the handler. The default store is process-local; the opt-in webhook-sql feature accepts an Arc<SqlWebhookReplayStore> in WebhookMiddlewareState for cross-process admission. Active claims are never evicted to admit new work. Webhooks use constant-time cryptographic verification where applicable:

#![allow(unused)]
fn main() {
use axum::{body::Bytes, http::HeaderMap, response::IntoResponse};
use rullst_capital::providers::stripe::StripeProvider;
use rullst_capital::BillingProvider;
use std::collections::HashMap;

pub async fn handle_stripe_webhook(
    headers: HeaderMap,
    body: Bytes,
) -> Result<impl IntoResponse, axum::http::StatusCode> {
    let stripe = StripeProvider::new(
        "sk_live_api_key",
        "whsec_your_webhook_signing_secret",
    );

    let signature = headers
        .get("Stripe-Signature")
        .and_then(|v| v.to_str().ok())
        .ok_or(axum::http::StatusCode::BAD_REQUEST)?;

    let provider_headers = HashMap::from([(
        "stripe-signature".to_string(),
        signature.to_string(),
    )]);

    // Verifies the provider signature and timestamp before parsing the event.
    let event = stripe
        .handle_webhook(&body, &provider_headers)
        .map_err(|_| axum::http::StatusCode::UNAUTHORIZED)?;

    println!(
        "Verified subscription {} with status {:?}",
        event.subscription_id,
        event.status,
    );
    Ok(axum::http::StatusCode::OK)
}
}

SQL-backed middleware claims the payload before dispatch. Treat it as a fail-closed replay firewall, not an exactly-once delivery guarantee. For an atomic relational state change, verify the exact payload through the selected provider, obtain its stable event identifier, then call check_and_record_event_key_with_transaction inside the same transaction as the domain mutation. Provider API calls, e-mail, queues, and other systems still need an outbox, idempotent consumers, and reconciliation.


🏛️ Brazilian Digital Invoicing (NFS-e Nacional)

rullst-capital includes a dedicated fiscal module (rullst_capital::fiscal) shaped around the National NFS-e domain. Its local schema, signature, bounded issuance-codec, and mTLS preparation contracts are implemented and tested; it also supplies a bounded authenticated local command journal, but it is not yet an officially homologated issuer.

Enable rullst-capital/nfse (or umbrella rullst/capital-nfse) for the pinned XSD, XMLDSig, GZip/Base64 protocol codec, and mTLS preparation dependencies. Selecting the feature does not enable SEFIN transmission.

Architecture & Pipeline

[SaaS Sale] ─► [NfseDpsV101] ─► [Pinned XSD] ─► [PKCS#12 XMLDSig] ─► [Bounded JSON codec]
                    │                                                    │
                    ▼                                                    ▼
          [Offline deterministic fixture]       [HMAC journal; mTLS prepared; transmission disabled]

Emitting an Invoicing Document (DPS)

use rullst_capital::fiscal::{
    build_dps_xml_v1_01, FiscalCustomer, FiscalEmitter, IssRetention,
    IssTaxation, NfseDpsV101, NfseEnvironment, TaxRegime,
};
use chrono::{NaiveDate, Utc};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let emitter = FiscalEmitter {
    cnpj: "12.345.678/0001-90".to_string(),
    inscricao_municipal: "1234567".to_string(),
    legal_name: "Rullst SaaS & Software Ltda".to_string(),
    trade_name: Some("Rullst".to_string()),
    ibge_code: "3550308".to_string(), // São Paulo
    tax_regime: TaxRegime::SimplesNacional,
};

let customer = FiscalCustomer {
    doc_number: "123.456.789-00".to_string(),
    name: "João Silva".to_string(),
    email: "joao@example.com".to_string(),
    zip_code: Some("01310-100".to_string()),
    address: Some("Av Paulista, 1000".to_string()),
    ibge_code: Some("3550308".to_string()),
};

let dps = NfseDpsV101 {
    id: "DPS355030821122233300018100001000000000000101".to_string(),
    series: "1".to_string(),
    number: 101,
    issued_at: Utc::now(),
    competence_date: NaiveDate::from_ymd_opt(2026, 8, 30).ok_or("invalid date")?,
    service_code: "010301".to_string(),
    description: "Assinatura Mensal SaaS Rullst Pro".to_string(),
    amount_cents: 9_900,
    iss_rate_basis_points: Some(200),
    iss_taxation: IssTaxation::Taxable,
    iss_retention: IssRetention::NotRetained,
    service_city_ibge: "3550308".to_string(),
};

let unsigned_xml = build_dps_xml_v1_01(
    &emitter,
    &customer,
    &dps,
    NfseEnvironment::Homologation,
)?;
let _ = unsigned_xml;
Ok(())
}

See Preparing a National NFS-e 1.01 homologation candidate for pinned artifact validation, local signing, and the external gates that still prevent live transmission.

The opt-in journal records a caller-owned opaque command before transport and then one parsed terminal result. Exact replays are read-only; a reused command ID with different request/result material fails closed. pending() returns only the command ID, environment, signed-request digest, local observation time, and sequence needed to reconcile application-owned request storage after a restart. The host must keep the 32-byte HMAC key in a secret manager, use one active writer in a trusted directory, persist checkpoint() independently, and own retention, backup, request/outbox storage, retries, and authority reconciliation.


🔒 Security Invariants

  1. Constant-Time Verification: Webhook signatures use subtle::ConstantTimeEq to prevent side-channel timing attacks.
  2. Fail-Closed Live Modes: Local XMLDSig/XSD/codec/mTLS preparation and command evidence do not enable a request. Homologation and Production return a typed FiscalError::Unsupported without network I/O until the external trust and homologation gates pass.
  3. Bounded Egress: Reviewed live provider methods use a pooled client with finite connect/request timeouts, disabled redirects and ambient proxy discovery, bounded JSON, and redacted typed failure evidence. Returned checkout URLs must be absolute credential-free HTTPS without fragments.

Rullst Studio 📊

Important

This page documents the unreleased v12 source. Use a path dependency from this checkout until the planned 12.0.0-rc.1 is published.

rullst-studio is the built-in, local-first administration and monitoring dashboard for Rullst. It exposes bounded database, queue, cache and telemetry views from the sources explicitly supplied by the application.

✨ Features

  • Database inspector: Read and filter configured SQLx tables, edit bounded primitive non-key values, delete one complete-primary-key-selected row with exact confirmation, and inspect a live ER diagram. SQLite, PostgreSQL, MySQL and MariaDB run executable mutation contracts.
  • API playground: Mount interactive Swagger UI from an OpenApi document explicitly supplied by the application; Studio does not infer arbitrary Axum routes.
  • Worker queue monitoring: Inspect up to 50 records exposed by a supplied Rullst queue and request retries. SQLite removes successful jobs, so the view is not durable completion history.
  • Safe configuration view: Environment values are deny-by-default redacted; typed runtime configuration is projected without URLs, paths, or secrets.
  • Feature flags manager: Toggle database-backed flags and immediately invalidate already-warm DbFeatureDriver caches in the same process.
  • Distributed diagnostics: Visualize in-process sources plus bounded, attribute-free v1 spans from a separately mounted HMAC-authenticated push endpoint. Slow-query and repeated-label findings are heuristics; no SQL text, bindings, attributes, headers, bodies or error details are accepted.
  • Cache inspector: An explicitly supplied memory or Redis Cache exposes bounded metadata and individual invalidation through opaque process-bound tokens. Values, exact keys and bulk flush remain unavailable in the UI.
  • Local-first security: The supported launcher binds to loopback, verifies the direct peer and local Host authority on every request, and requires a same-origin Origin header for mutations.

🚀 Quickstart

After the RC is published, add its exact train with cargo add rullst-studio@12.0.0-rc.1.

Launching the Studio

The supported v12 mode is a standalone debug server. run_studio and Studio::into_router(LocalStudioAccess::loopback_only()) reject release builds and requests whose direct peer is not verified as loopback. Servers composing the router manually must preserve Axum ConnectInfo<SocketAddr>. The access capability also rejects DNS-rebinding-style non-local Host values, cross-origin requests, and unsafe requests without an Origin header.

The earlier StudioLayer embedded-production idea was never implemented. Keeping an authenticated shared Studio is worthwhile, but it needs its own explicit identity/RBAC/TLS policy before it can become a supported mode.

CLI Launch:

If you don’t want to embed it, you can launch it statelessly via the Rullst CLI:

cargo rullst studio

Authenticated trace producers

#![allow(unused)]
fn main() {
use rullst_studio::distributed_traces::{
    DistributedTraceStore, TraceIngestionKey, TraceIngestor,
};
use rullst_studio::{LocalStudioAccess, Studio};

fn build() -> Result<(), Box<dyn std::error::Error>> {
let store = DistributedTraceStore::new(2_048)?;
let key = TraceIngestionKey::new(std::env::var("RULLST_TRACE_INGESTION_KEY")?)?;
let ingestion = TraceIngestor::new(store.clone(), "api-1", key)?;
let _push_only_application_router = ingestion.router();
let _local_viewer = Studio::new()
    .with_distributed_traces(store)
    .into_router(LocalStudioAccess::loopback_only())?;
Ok(())
}
}

Each ingestor binds one exact producer name to one key; mount separate producer endpoints over the same store when needed. TraceBatchSigner::new binds the same pair and produces the byte-identical JSON body plus source, timestamp, nonce and signature headers. Mount the push-only router under an application path; it exposes no Studio reads or administrative mutations. The application/deployment still owns TLS, network policy, key distribution and rotation, clock synchronization, availability and label redaction. The store is bounded process memory, not OTLP or durable trace storage.

🔐 Security Audit

rullst-studio currently supports verified-loopback development access. It does not provide a built-in shared-intranet or production authentication mode. Do not expose raw subrouters publicly; a future shared mode must fail closed behind application-owned authentication, administrator authorization, TLS, and network policy.

The built-in migration page intentionally links to cargo rullst db:* commands. The compatibility HTTP mutation handlers return 501 Not Implemented because the standalone Studio has no configured migration or seeder registry. Queue and revenue panels likewise show only data supplied by the selected driver or application; unsupported operations return errors instead of simulated success.

The SSE request view records method, URI, status and latency only. It does not capture bodies or headers by default because those can contain authentication, session, payment, and personal data. A successful Studio toggle invalidates all already-warm DbFeatureDriver entries in the same process. Other processes and direct database writers remain visible through the configured TTL unless the application supplies distributed invalidation.

Studio::with_cache opts one Cache into metadata-only inspection. The page shows a keyed opaque identifier, UTF-8 value byte length and remaining TTL for at most 100 entries. It never returns the value or logical key, offers no bulk flush, and requires the verified local mutation marker to invalidate one entry. Memory and Redis implement this contract; custom drivers return an explicit unsupported state unless they implement bounded inspection.

📚 Documentation

For supported usage and security boundaries, see this book and the capability ledger.

Rullst Nexus

rullst-nexus is a server-rendered administrative panel for models that implement NexusModel, either manually or through #[derive(Nexus)]. The derive infers primitive widgets and accepts explicit semantic metadata such as kind = "textarea" and kind = "enum", options = "draft, published". Nexus provides registered-model CRUD, search, pagination, semantically validated form widgets, bounded selected-record delete/deactivate actions, telemetry, a security view and an optional AI query page. The current interface uses server-side HTML and HTMX; Wasm islands, drag-and-drop media management and automatic relationship discovery described by older documentation were not implemented. They remain worthwhile separate features, but must not be presented as current behavior.

Build a protected panel

Nexus fails closed: try_build() requires an explicit validated access policy. The generated-app helper permits credential-free access only in debug builds and only for a loopback peer proven by Axum ConnectInfo. Release builds require NEXUS_ADMIN_USERNAME and a unique NEXUS_ADMIN_PASSWORD of at least 16 characters.

#![allow(unused)]
fn main() {
use rullst_nexus::{Nexus, NexusAuthPolicy};

fn build() -> Result<axum::Router, Box<dyn std::error::Error>> {
let access = NexusAuthPolicy::local_development_or_basic_from_env()?;
let nexus = Nexus::new()
    .with_auth_policy(access)
    .with_brand("Application Admin")
    // .register::<User>()
    .try_build()?;

let app = axum::Router::new().nest("/nexus", nexus);
Ok(app)
}
}

The serving boundary must preserve the socket address, for example with Axum’s into_make_service_with_connect_info::<SocketAddr>(). Basic Auth additionally requires direct HTTPS or the application-owned NexusVerifiedTls capability inserted only after validating a trusted TLS terminator. Never derive that capability from an untrusted forwarded header.

NexusAuthPolicy::protect_router can apply the same administrator boundary to application-owned operational routes, as the ERP blueprint does for inventory mutations.

Capability boundary

  • Implemented: explicit model registration and a compile-tested derive; server-rendered tables/forms; bounded/validated registry metadata; parameterized and allowlisted SQL identifiers; bound record values; server-side pair/byte limits and semantic validation for Boolean, enum, JSON, number, date, e-mail and HTTP(S) URL fields; CRUD, search, pagination, sort and batch operations; CSRF middleware; fail-closed loopback/Basic access; bounded Basic Auth failure throttling; opt-in exact text-column tenant scope across every built-in read and mutation, derived only from a trusted Core TenantContext; and opt-in transaction-coupled minimized mutation audit with a fixed cross-database schema and bounded export API.
  • Batch boundary: at most 1,000 explicitly selected IDs; deactivation is available only for a writable Boolean is_active or active field.
  • Application responsibility: identity, tenant membership and domain policy; model/field authorization for global models and custom routes; database privileges; trusted proxy and TLS configuration; secret rotation; immutable external audit delivery, retention and backup; schema/type compatibility; and ownership rules beyond the explicit tenant column. The built-in audit table is transaction-coupled in the same database, not append-only or tamper-evident, and records committed mutations rather than rejected attempts. Multiline intent and variants from an unrelated Rust enum require explicit #[nexus] metadata; the struct derive does not invent them.
  • Not implemented: a generic NexusLayer, automatic ORM schema reflection, automatic HasMany/BelongsTo widgets, full rich-media management and a shared-production authentication service. These ideas may be implemented when they have typed contracts and proportional tests.

For a complete model example and the local/release access flow, see Rullst Nexus: Explicit Admin CMS.

Rullst AI

Vision preserved: capability-typed schema support, local-model boundaries, and autonomous-agent ideas remain itemized with an implementation opinion in the capability ledger.

rullst-ai is a provider-agnostic LLM client with mandatory outbound prompt-injection checks, PII masking, deterministic offline fixtures, JSON mode, explicit JSON Schema output, and a bounded tenant-aware RAG pipeline.

Provider capabilities

ProviderChatVisionEmbeddingsJSON modeNative JSON Schema
OpenAIyesyesyesyesyes
Geminiyesyesyesyesyes
Anthropicyesyesnoprompt-constrainedno
DeepSeekyesnonoyesdeepseek-v4-flash
Ollamayesmodel-dependentyesyeslocal API only
OpenAI-compatible local/cloudyesdeclareddeclareddeclareddeclared

Unsupported capabilities return AiError::UnsupportedCapability; the client does not silently switch to an unrelated endpoint or represent a fixture as a live-provider result.

OpenAiCompatibleProvider covers servers implementing the named OpenAI /chat/completions and optional /embeddings shapes. It defaults to chat-only; vision, embeddings, JSON mode, and JSON Schema must be declared for the exact endpoint/model pair. try_local permits unauthenticated HTTP only on a literal loopback IP, try_local_with_bearer adds explicit local authentication, and try_cloud requires HTTPS plus a Bearer credential. All three disable redirects and environment proxies and bound response bodies. Different protocols use a custom public AiProvider, not an arbitrary-HTTP mode.

Guarded client

#![allow(unused)]
fn main() {
use rullst_ai::{AiClient, AiError, providers::openai::OpenAiProvider};

async fn answer(api_key: String, user_text: &str) -> Result<String, AiError> {
    let client = AiClient::new(OpenAiProvider::new(api_key));
    client
        .chat()
        .system("Answer concisely.")
        .user(user_text)
        .send()
        .await
}
}

AiClient::prompt, chat, vision, embedding, JSON, and structured-output calls run the same guardrail stage before provider dispatch. Built-in providers repeat the check on direct trait calls. Call custom AiProvider implementations through AiClient when the application needs the same mandatory boundary.

The current guardrail blocks deterministic injection patterns, provider delimiter tokens, external Markdown beacons, and selected invisible Unicode controls. Supported PII classes are masked before outbound transmission. This is a bounded heuristic control, not proof that arbitrary input or model output is safe; authorization, tool permissions, output encoding, and domain validation remain application responsibilities.

Adaptive evaluation runner

AdaptiveAiEvaluator<P> runs application-defined multi-turn strategies over static provider dispatch and the mandatory prompt guardrail. One scenario is limited to 32 turns, 16 KiB per generated prompt and 2 MiB per response, with an independent deadline and explicit cancellation. Each observation exposes a bounded response only while the synchronous strategy chooses pass, fail, inconclusive or its next prompt.

The versioned JSON report keeps the exact caller-supplied suite/subject labels, provider name, status, terminal code and per-turn byte counts/outcomes. It does not retain prompts, responses or provider error bodies and can itself be sent through AuditDeliveryClient. The subject label is not automatic model discovery, and the deterministic offline runner test is not live-model evidence. Operators must version their scenario code/corpus and execute it against each exact model/configuration they intend to approve; no passing suite proves universal safety, groundedness or jailbreak resistance.

Authenticated audit delivery

AuditDeliveryClient can export an application-minimized RAG, tool or provider event to one exact endpoint. Cloud configuration requires HTTPS; local development allows HTTP(S) only on a literal loopback IP. Each JSON envelope is limited to 16 KiB and HMAC-SHA256 authenticates the exact bytes together with the key ID and Unix-millisecond timestamp. A caller-generated event ID remains stable across at most five attempts, and success requires a closed JSON acknowledgement that repeats that ID. Cancellation covers request, response and retry waits; empty or mock_* keys select a deterministic offline fixture.

Only transport/deadline failures, HTTP 429 and HTTP 5xx are retryable. Because a timeout can happen after remote acceptance, the receiver must enforce idempotency by event ID as well as signature/freshness validation. The client does not minimize arbitrary serialized data, retain a durable outbox, rotate keys, authorize operators or provide a SIEM receiver. Those remain explicit application/deployment responsibilities.

Bounded streaming and cancellation

StreamingAiClient<P> is a static-dispatch extension for genuinely incremental providers. The OpenAI-compatible adapter implements the strict SSE path only when the exact endpoint/model configuration opts into with_streaming(). It checks the prompt before I/O, requires text/event-stream and [DONE], bounds the raw response, chunk count, each chunk and aggregate output, and rejects malformed or truncated events.

AiCancellation is cloneable and aborts a supported request while it is waiting for headers or another body chunk. That drops the local request future; it cannot prove that an upstream server stopped work or billing. The other built-in transports and ordinary non-streaming calls retain deadline/drop semantics until their different wire protocols have equivalent tests.

Tenant-aware chat memory

StatefulChat<M> is a static-dispatch orchestration boundary over ChatMemory. It binds every conversation to trusted TenantContext, loads a bounded even history, calls the guarded client, and atomically appends the user and assistant halves after successful generation. InMemoryChatMemory is a bounded deterministic offline store.

With the opt-in umbrella ai-sql-memory feature, SqlChatMemory supplies a dedicated SQLx Any pool and fixed schema for SQLite, PostgreSQL, MySQL, and MariaDB. Its revision compare-and-swap rejects stale cross-process writers. It does not retry the provider call, because doing so could duplicate cost or side effects. The AI integration tutorial shows the complete setup and application-owned security/retention boundary.

Tenant-aware RAG pipeline

RagPipeline::answer performs guarded embedding, calls a static-dispatch RagRetriever, applies per-document and total Unicode-safe budgets, guards and masks every selected passage, generates a grounded response, returns source metadata, and records one terminal audit event. It requires a trusted TenantContext, rejects differently tagged documents, and fails with RagError::NoContext instead of asking the model to answer without retrieved evidence.

The bundled InMemoryRagRetriever is a bounded tenant-partitioned cosine index for offline tests, development, and small ephemeral datasets. It is neither durable nor distributed. A production application can implement RagRetriever over ORM pgvector or Qdrant, but that adapter must bind the trusted tenant and ownership predicates in the authoritative datastore. The pipeline’s tag check is defense in depth, not a replacement for datastore authorization.

The mandatory audit event stores the tenant, a SHA-256 correlation digest, counts, character budget, and outcome. It deliberately omits raw questions, documents, embeddings, provider bodies, and model answers. The digest is not encryption and can be guessed for low-entropy questions. DurableRagAuditTrail and DurableToolAuditTrail provide bounded synchronous local files with distinct version headers, SHA-256 frame integrity and restart/quota/corruption validation. They are single-process writers and do not supply authenticity, rotation, retention, backup or external delivery; multi-instance deployments can implement the same audit traits over their destination.

Follow the tenant-bound RAG tutorial for the complete offline flow and production integration boundary.

Versioned offline evals

The packaged evals/guardrails-v1.json corpus freezes deterministic injection, jailbreak, and PII regressions. The repository gate validates unique IDs and required categories, then runs every case across all six built-in transports in offline mode. It is deliberately not presented as a safety benchmark: adaptive attacks, tool selection, hallucination, and live provider/model versions require separate eval suites.

Strict egress policy

EgressPolicy::strict() starts with no permitted destination. After an exact host allowlist is configured, EgressFetcher permits HTTPS and explicit ports, blocks credentials/local/private/metadata/reserved addresses, validates every DNS answer, pins those answers in a proxy-free reqwest client, verifies the connected peer, follows redirects only after repeating policy, and bounds time and streamed bytes. The fetcher is opt-in: it cannot protect arbitrary application/provider HTTP clients, and tenant authorization, content schema and data minimization remain caller contracts.

Offline mode

OpenAI, Gemini, Anthropic, DeepSeek, and compatible cloud endpoints use deterministic offline mode when their API key is empty or begins with mock_. Ollama uses an empty or mock_* host; the compatible adapter also exposes an explicit mock constructor. Plain try_local is deliberately live because no credential is its valid loopback configuration. Offline branches return before HTTP dispatch and cover each capability the provider declares. Unsupported capabilities remain typed errors in offline mode.

AiClient::auto() checks OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, DEEPSEEK_API_KEY, and OLLAMA_HOST. If none is configured, it selects an offline OpenAI fixture; it does not probe localhost implicitly.

JSON mode and structured output

JSON mode requests a parseable JSON value and deserializes it in Rust:

#![allow(unused)]
fn main() {
use rullst_ai::{AiClient, AiError, providers::openai::OpenAiProvider};
async fn example() -> Result<(), AiError> {
let client = AiClient::new(OpenAiProvider::new("mock_local"));
let value: serde_json::Value = client.json_prompt("Summarize this record").await?;
let _ = value;
Ok(())
}
}

Native structured output requires an explicit schema and fails when the provider cannot enforce it:

#![allow(unused)]
fn main() {
use rullst_ai::{AiClient, AiError, StructuredOutputSchema, providers::openai::OpenAiProvider};
async fn example() -> Result<(), AiError> {
let client = AiClient::new(OpenAiProvider::new("mock_local"));
let schema = StructuredOutputSchema::new("answer", serde_json::json!({
    "type": "object",
    "properties": {"ok": {"type": "boolean"}},
    "required": ["ok"],
    "additionalProperties": false
}))?;
let value: serde_json::Value = client
    .structured_prompt_with_schema("Evaluate the input", &schema)
    .await?;
let _ = value;
Ok(())
}
}

Provider-side schema enforcement and Rust deserialization do not replace application-specific semantic validation.

Current boundaries

Streaming for non-compatible provider protocols, provider-native tool execution loops, first-party external vector-store RagRetriever adapters, maintained domain-specific evaluation corpora, and compile-time schema derivation remain roadmap work. The SQL memory does not supply raw-text encryption, ownership within a tenant, retention or provider auditing; the in-memory vector utilities and tool registry do not create an authorization boundary by themselves. Authenticated audit delivery does not replace a durable outbox or certify a receiver’s retention, availability or security operations.

Rullst Security 🛡️

“Defense-in-Depth RASP, Cryptographic Vault & Runtime Protection for Rust”

rullst-security provides authenticated field encryption, bounded defensive middleware, local abuse controls, and security telemetry. Its RASP/DLP rules are defense-in-depth heuristics: they reduce specific risks but do not establish complete OWASP coverage, replace parameterized SQL/authorization, or certify the application that mounts them.


⚡ Capability & Lifecycle Matrix

SubsystemLifecycle StatusDescription
Rullst Vault🟢 [Implemented]Authenticated AES-256-GCM encryption with 96-bit nonces, AAD, versioned envelopes, and keyring rotation. Key custody remains external.
Bounded RASP🟢 [Implemented: defense in depth]ASCII signature matching plus one decoding pass for URI, headers, and bounded textual/JSON bodies. Decoding and body inspection may allocate.
Login Guard Tarpit🟢 [Implemented: local]Progressive delay decisions and bounded, expiring in-memory jails keyed by a hashed identity. The caller performs the returned delay.
Sliding-Window Rate Limiter🟢 [Implemented: local]In-memory limiter keyed from the verified socket peer. It does not coordinate multiple processes.
Redis Rate Limiter🟢 [Implemented: feature-gated foundation]redis-rate-limit uses an atomic fixed-window Lua script, namespace validation, hashed client keys and TTL-derived retry metadata. Empty/mock_* URLs select an explicit process-local test mode; call require_distributed() at production startup. A live contract proves independent clients share one Redis budget; cluster/failover remains application evidence.
DLP & Secret Masking🟢 [Implemented: bounded]Masks complete private-key envelopes, AWS access-key patterns, and credentials in supported textual database URLs. Binary, compressed, streaming, unknown-size, and oversized bodies are not rewritten.
Local SIEM Journals🟢 [Implemented: bounded local]DurableSiemSpool preserves synchronized unsigned SHA-256 frames. The opt-in AuthenticatedSiemSpool adds HMAC-SHA256 sequence/predecessor integrity, one active plus seven historical zeroized keys, byte/record quotas and fail-closed restart/forgery/ordering/external-change behavior. Whole-tail checkpoints, multi-writer coordination, retention, delivery, retry, acknowledgement and external adapters remain operator work.
TOTP Multi-Factor Auth🟢 [Implemented: foundation]Six-digit SHA-1 TOTP generation/verification with a ±1 time-step window, percent-encoded otpauth URI builder, and subject-bound single-use recovery-code verifiers. Enrollment, transactional persistence, rate limits, and account policy remain application concerns.
CSWSH Guard🟢 [Implemented]Exact normalized scheme/host/port validation for WebSocket origins, with a fail-closed default for missing origins.
Canonical Server security stack🟡 [Partial]CSP nonce identity is shared across Core and extended layers, but Core still owns the default Server CSRF/WAF/header/PII stack. Explicit composition is required.
Distributed Rate Limiting Evidence🟡 [Partial]The Redis adapter is implemented, but real cross-instance, eviction/failover and trusted-proxy deployment tests remain required. The legacy no-argument distributed selector still returns Unsupported rather than guessing configuration.

🔐 1. Rullst Vault (Authenticated AES-256-GCM)

FieldEncryptor provides authenticated encryption at rest (AEAD) with a versioned envelope and keyring-assisted rotation. Applications can keep prior keys readable while writing with a new key; deployment coordination, key custody, data re-encryption, and retirement remain operator responsibilities.

Usage Example

use rullst_security::vault::FieldEncryptor;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let master_key = [0x42u8; 32]; // 256-bit cryptographic key
    let sensitive_data = "user_ssn_123-45-6789";

    // Encrypt with key ID and Additional Authenticated Data (AAD)
    let encrypted = FieldEncryptor::encrypt_with_key_id(
        sensitive_data,
        master_key,
        "key-2026-v1",
        b"tenant-organization-id-42",
    )?;
    println!("Ciphertext Envelope: {}", encrypted);

    // Decrypt and verify authentication tag and AAD
    let decrypted = FieldEncryptor::decrypt_with_aad(
        &encrypted,
        master_key,
        b"tenant-organization-id-42",
    )?;
    assert_eq!(decrypted, sensitive_data);

    Ok(())
}

🛡️ 2. Runtime Application Self-Protection (RASP)

The RASP request inspector scrutinizes incoming requests before they reach your controllers:

#![allow(unused)]
fn main() {
use rullst_security::RaspInspector;

let is_sqli = RaspInspector::inspect_text("admin' OR '1'='1");
assert!(is_sqli);

let is_traversal = RaspInspector::inspect_uri("/files/../../../etc/passwd");
assert!(is_traversal);
}

Mount RaspSecurityLayer explicitly when the extended inspector is desired. The default rullst-core::Server currently mounts the smaller Core WAF rather than this layer; consolidation remains roadmap work.


🚫 3. Anti-Bruteforce Login Guard & Tarpit

#![allow(unused)]
fn main() {
use rullst_security::LoginGuard;

async fn apply_login_delay() {
let guard = LoginGuard::new(); // defaults: 5 failures, 15-minute local jail

// Record failure and apply the returned progressive delay in the async handler.
let delay = guard.record_login_failure("account:alice");
tokio::time::sleep(delay).await;

if guard.is_jailed("account:alice") {
    println!("Identity is in the local Login Jail");
}
}
}

📱 4. Multi-Factor Authentication (TOTP MFA)

#![allow(unused)]
fn main() {
use rullst_security::{
    build_otpauth_uri, generate_mfa_secret, generate_totp_code, verify_totp_code,
};

let secret = generate_mfa_secret();
let otp_uri = build_otpauth_uri("Rullst SaaS", "alice@example.com", &secret);
let current_code = generate_totp_code(&secret);

// Validate 6-digit code submitted by user
let is_valid = verify_totp_code(&secret, "123456");
}

The secret must be encrypted at rest. Recovery helpers return plaintext codes only at enrollment and salted HMAC verifiers for storage; consume/delete must be one durable transaction. The application still owns replay/attempt limiting, enrollment confirmation, recovery UX, audit and clock-monitoring policy.

Rullst IoT 📡

“Embedded Sensor Protocols, Ed25519 OTA Gate & Edge Computing for Rust”

Important

The dependency example uses 12.0.0-rc.1, the planned first v12 RC. Do not request it from crates.io before it is published; use a path dependency from this source checkout during development.

rullst-iot provides high-assurance telemetry models, bare-metal #![no_std] data structures and packet encoders, and a cryptographically verified Over-The-Air (OTA) firmware update state machine.


⚡ Capability & Lifecycle Matrix

SubsystemLifecycle StatusDescription
Ed25519 OTA Manifest Gate🟢 [Implemented / Bounded]Verifies a domain-separated signed manifest, target, firmware length/hash, and monotonic counter. A no_std store trait adds durable compare-and-set coordination; its concrete persistence, flashing, bootloader handoff, and hardware validation remain external.
no_std Telemetry Models🟢 [Implemented / Bounded]Allocation-conscious telemetry, digital-twin, and sensor models are available without std; board- and toolchain-specific builds must still be validated in the release matrix.
Protocol Frame Helpers🟢 [Implemented / Bounded]MQTT 5 PUBLISH, RFC 7252 CoAP base requests, Modbus CRC, I2C frame packing, BLE GATT data models, and power-policy abstractions; these are bounded packet/state helpers, not network, bus, or radio drivers.
Experimental Fixtures🟡 [Simulador Dev]The opt-in feature exposes explicitly named deterministic MQTT formatting, HSM-byte, and PQC-byte fixtures. GPIO/I2C/BLE types are always-available state/frame helpers, not hardware simulators.
Native MQTT/CoAP Transport🔵 [Roadmap]Connections, TLS/DTLS, broker negotiation, acknowledgement/retransmission state, subscriptions, block-wise transfer, and interoperability.
Hardware Security Module (HSM)🔵 [Roadmap]Native secure-element driver interfaces (ATECC608A, TPM 2.0, SE050).

🛡️ Over-The-Air (OTA) Firmware Verification

The OtaManager enforces a fail-closed eligibility gate: its state machine does not produce an OtaCommit receipt before strict Ed25519 verification of the signed manifest. The RollbackCounterStore path also requires an exact, strictly increasing compare-and-set. The receipt selects the intended inactive partition; platform code must still flash, verify, implement durable storage, configure the bootloader, and recover safely from power loss.

The Cryptographic Invariant

[Signed Firmware Manifest]
├── Target Hardware ID: "stm32-sensor-node-v1"
├── Version String:     "2.4.0"
├── Rollback Counter:   12  (Must be strictly > current committed counter)
├── Firmware Length:    131072 bytes
└── Firmware SHA-256:   [32 bytes hash]
                     │
                     ▼
       [Ed25519 Strict Signature Check]
                     │
           ┌─────────┴─────────┐
        Passed               Failed
           │                   │
  [Ready to Commit]     [Revert & Reject]

Usage Example

#![allow(unused)]
fn main() {
use rullst_iot::{
    OtaCommit, OtaError, OtaManager, OtaManifest, RollbackCounterStore,
};

fn process_incoming_ota<S: RollbackCounterStore>(
    firmware_bytes: &[u8],
    signature_bytes: &[u8],
    provisioned_public_key: [u8; 32],
    counter_store: &mut S,
) -> Result<OtaCommit, OtaError> {
    // 1. Construct the expected manifest from the payload
    let manifest = OtaManifest::from_firmware(
        "esp32-sensor-node", 
        "2.0.0", 
        15, // Proposed monotonic counter
        firmware_bytes
    )?;

    // 2. Load the last committed counter from the platform adapter
    let mut manager = OtaManager::new_with_counter_store(
        "esp32-sensor-node",
        "1.9.0",
        provisioned_public_key,
        counter_store,
    )?;

    // 3. Cryptographically verify signature, target, and anti-rollback state
    manager.verify_update(&manifest, firmware_bytes, signature_bytes)?;

    // 4. Flash and read back this bank using platform code before commit.
    let target_partition = manager.verified_target_partition()?;

    // 5. Durable CAS succeeds before local state changes. Coordinate the
    // receipt with the platform bootloader after this call.
    let receipt = manager.commit_verified_update_with_store(counter_store)?;
    debug_assert_eq!(receipt.target_partition(), target_partition);
    Ok(receipt)
}
}

The store contract requires power-loss-safe persistence before returning success. The framework tests restart/replay, transient retry, corruption and stale-writer conflict at the adapter boundary, but those tests do not certify a particular flash, secure element or board. A failure after the durable counter advances can require platform recovery and a newer signed update.


🔌 Embedded Bare-Metal Telemetry (#![no_std])

rullst-iot exposes a no_std model layer intended for constrained microcontrollers. Compatibility is feature-, target-, allocator-, and toolchain-dependent and must be confirmed for the actual board:

#![allow(unused)]
fn main() {
use rullst_iot::SensorTelemetry;

fn sample_reading() -> SensorTelemetry {
    SensorTelemetry::new(
        "node-1",
        "temperature_celsius",
        24.5,
        1_724_500_000,
    )
}
}

MQTT and CoAP packet boundaries

#![allow(unused)]
fn main() {
use rullst_iot::{
    CoapMessageType, CoapMethod, CoapRequest, MqttPublish, MqttQos,
};

let publish = MqttPublish::reliable(
    "nodes/node-1/temperature",
    b"24.5".to_vec(),
    MqttQos::AtLeastOnce,
    7,
)?
.encode()?;

let request = CoapRequest::new(
    CoapMessageType::Confirmable,
    CoapMethod::Post,
    42,
    [0x01, 0x02],
)?
.path_segment("telemetry")?
.content_format(50)
.payload(br#"{"temperature":24.5}"#.to_vec())?
.encode()?;

Ok::<(), Box<dyn std::error::Error>>(())
}

These helpers follow the local OASIS MQTT 5 PUBLISH and RFC 7252 CoAP packet shapes. They deliberately stop before any socket or session state; the transport owns security, identity, timing, correlation, retry and peer interoperability.


🔬 Experimental Simulators (experimental-simulators)

For local integration tests without physical hardware attached, enable the simulator feature:

[dependencies]
rullst-iot = { version = "12.0.0-rc.1", features = ["experimental-simulators"] }

This exposes SimulatedMqttPayloadFormatter, SimulatedHsmDevice, and SimulatedPqcFixture for deterministic sandbox execution.