rullst-core 12.3.0

Core runtime, routing engine, and telemetry for the Rullst framework suite.
Documentation
# Rullst Core ⚙️

`rullst-core` encapsulates the foundational primitives, routing engines, state management, and configuration layers of the Rullst Framework. It acts as the beating heart that orchestrates HTTP handlers, middleware, and backend worker systems.

## ✨ Features

- **Typed Axum Integration:** Wraps Axum routing while retaining access to the underlying router and middleware ecosystem.
- **Bounded Server Functions:** `#[server_function]` shares the versioned
  `rullst.client` v1 envelope between native Axum routes and Wasm callers, with
  a 256 KiB body ceiling, correlation checks, same-origin paths, CSRF header
  forwarding, and message-free typed failures. Identity and domain policy stay
  server-owned.
- **Runtime Telemetry:** Exposes process/runtime snapshots and tracing-span collection for consumers such as Rullst Studio.
- **Lifecycle-aware Readiness:** An opt-in process lifecycle gates new requests
  during startup, dependency unavailability, and graceful drain. It accepts at
  most 32 immutable component labels and exposes counts—not labels or errors—on
  `/ready`; dependency checks and multi-replica coordination remain host work.
- **Bounded Rate Limiting:** `RateLimiter` keys IPv4 peers per address and
  IPv6 peers per /64, and bounds its process-local bucket map to 100,000 keys
  by dropping refilled buckets and evicting the least recently used ones.
  `Server`'s limiter and Traffic Shield exempt exact `GET`/`HEAD /health` and
  `/ready` probes.
- **Typed Failures:** Server, scheduler, queue, storage, and resilience APIs expose structured errors for fallible paths. The repository's zero-panic policy is CI-scoped, not an absolute runtime guarantee.
- **Dependency Injection:** Type-safe, intuitive global state management across routes and background workers.
- **Environment Management:** Native `dotenv` and TOML configuration loaders for different deployment targets (Staging, Production, Local).
  `Server` and the Artisan `db:*`/`studio` commands resolve the database the
  same way: process `DATABASE_URL`, then `./.env` (never overriding the
  process), then `[database].url`. Database commands without a configured
  database fail instead of creating a SQLite file.
- **Local Edge Emulator:** `edge::EdgeServer::run` binds `127.0.0.1` unless
  `HOST`/`RULLST_HOST` is set, serves every path including `/`, and rejects a
  body over 2 MiB with `413` (or an unreadable one with `400`) instead of
  passing the handler an empty body. It applies no security baseline.
- **Durable Scheduled Queues:** SQLite and Redis persist bounded `dispatch_at`
  timestamps and never claim a job before its millisecond due time. Delivery is
  poll-dependent and at-least-once.
- **Fenced Queue Leases:** SQLite and Redis complete, fail or requeue a claimed
  job only under the attempt number `pop` returned, so a stale worker whose
  lease was recovered and claimed again cannot finish the newer claim.
- **Queue-wide Stalled-lease Recovery:** each worker periodically requeues
  every processing lease older than its `stalled_after`, including other
  workers' leases, so every worker sharing a queue needs a `stalled_after`
  longer than the longest `job_timeout` among them. SQLite and Redis fail a
  job whose fifth lease stalls (for example because it keeps crashing its
  worker) instead of requeuing it forever.
- **Rolling-deploy Safe Dispatch:** A worker without a handler for a job's
  name hands the claim back with a five-second delay (SQLite and Redis) instead
  of failing it, so a worker that registered that name can run it.
- **Bounded Redis Failure State:** Failed jobs and dead letters are each
  retained up to 10,000 entries (configurable with
  `RedisDriver::try_with_failure_retention`), evicting the oldest atomically.
- **Swappable HTMX Validation Errors:** `ValidatedForm`/`ValidatedJson` send
  HTMX requests their error fragment with `200 OK` plus
  `X-Rullst-Validation-Status: 400|422`; other clients keep `400`/`422` JSON.
- **Cron Semantics:** `Scheduler::task` evaluates five-field expressions in
  UTC with the `cron` crate's rules: weekdays 1 (Sunday) to 7 (Saturday), 0
  rejected, and restricted day fields must both match. Prefer weekday names.
- **Bounded Background Errors:** `WorkerHandle` and `SchedulerHandle` buffer at
  most 256 undrained errors; overflow is dropped, counted by `dropped_errors()`
  and logged as a `tracing` warning. Drain `next_error` to observe every failure.
  `Server::schedule` drains its own handle, logging each task failure; only a
  failed scheduler loop makes `Server::run` return an error.
- **Explicit Completion History:** SQLite deletes successful payloads by
  default. `Queue::sqlite_with_completed_history` opts into a bounded retained
  history for Studio/operations, with atomic pruning and an explicit purge API.
- **Atomic Local Writes:** `Storage::local`/`LocalDriver::put` writes a unique
  temporary file beside the object, flushes it and renames it over the key.
  Readers and concurrent writers see one complete version, and a failed write
  leaves the previous object intact. Each put creates a new file with default
  permissions; the directory is not fsynced, so a power loss can roll a
  completed put back to the previous version.
- **Encoded Object URLs:** `Storage::url`/`LocalDriver::url` percent-encode
  each key segment. Local storage always returns `/storage/<key>` (serve the
  base directory there), never the filesystem base path.
- **Metadata-only Cache Inspection:** Memory and Redis drivers can return a
  sorted snapshot of at most 200 logical keys, UTF-8 value lengths and TTLs
  without returning values. Custom drivers fail explicitly unless they opt in;
  authorization and safe rendering of logical keys remain caller policy.
- **Offline Sync Foundation:** The optional native `offline-sync` feature
  provides bounded replay-safe mutations, explicit conflict resolution, and
  account-bound AES-256-GCM snapshots. Its static-dispatch coordinator bounds
  requests, times them out, and detects stalled cursors. Applications still own
  Keychain/Keystore access, atomic platform persistence, authenticated HTTP,
  retry, and background scheduling.

## 🚀 Usage

Most developers will not depend on `rullst-core` directly, as it is re-exported seamlessly through the primary `rullst` crate. 

If you are developing a plugin or advanced middleware for the Rullst ecosystem, you can add it explicitly:

Install the exact stable train with
`cargo add rullst-core@12.3.0`.

Core is runtime-only by default. Add just the database capabilities the
application needs:

```toml
[dependencies]
rullst-core = { version = "12.1.0", features = ["orm", "queue-sqlite", "offline-sync"] }
```

Enable `orm`, `queue-sqlite`, `queue-redis`, `offline-sync`, or `telemetry` only
when that integration is required. The primary `rullst` crate keeps `orm` and
`queue-sqlite` in its default feature set for application compatibility.

Both built-in queue drivers implement `Queue::dispatch_at` for schedules up to
366 days ahead. SQLite performs an automatic additive schema migration; Redis
uses a sorted set and server time, with a digest-pinned live CI contract. Custom
drivers fail with `QueueError::Unsupported` for future jobs until they implement
the scheduling method explicitly.

Successful SQLite jobs are removed unless the application explicitly calls
`Queue::sqlite_with_completed_history(database_url, retained_jobs)`. The limit
must be between 1 and 100,000; completion and pruning share one transaction.
Retained rows include the original payload, so the application must restrict
Studio/inspection access and choose an appropriate retention policy. Use
`Queue::purge_completed_history` to remove them.

`Cache::inspect(limit)` is opt-in diagnostic data, not an administration
endpoint. The limit must be 1–200. `CacheEntryMetadata` deliberately redacts
the logical key from `Debug` and never carries the cached value, but
`logical_key()` still returns application data to an authorized caller. Rullst
Studio converts it into a process-bound opaque token before rendering it.
`Cache::memory()` stores a TTL too large for the monotonic clock (such as
`u64::MAX`) as non-expiring instead of panicking.

For orchestrated deployments, construct `ApplicationLifecycle`, mount
`health_router_with_lifecycle(lifecycle.clone())`, then pass the same value to
`Server::with_lifecycle`. The server marks startup complete after binding,
begins drain before Axum waits for accepted requests, and marks startup failure
or termination as stopped. `run_with_shutdown` permits a caller-owned trigger.
Admission follows the ordinary response body through completion, error or drop,
including streamed data/trailers. It does not prove client receipt or track
upgraded connections. `wait_for_drain` is bounded; supervisors separately own
process kill deadlines and application WebSocket/background-task termination.
The process-local registry does not probe dependencies, authorize application
requests, coordinate replicas, or guarantee load-balancer propagation.

### Minimal HTTP Server

```rust
use rullst_core::{Router, Server};
use rullst_core::routing::get;

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

## 🔐 Security Audit

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.

## 📚 Documentation

For an architectural deep-dive into Rullst Core's event loop and middleware lifecycle, please visit the **[Rullst Book](https://github.com/Rullst/Rullst/blob/v12.3.0/docs/src/SUMMARY.md)**.