AkurAI Build
Menu

AkurAI-Build

public

Latest change 1c2159692a31765cd66ed709791ba11468054873 - Initial commit: bunfork v0.1.0 source tree by Olafur Bui

## Decision

Build a Bunfork-native Rust MPA kernel beside `bunfork-static-v1`. Do not extend the static artifact format into SSR or server actions.

This is the smallest truthful “full-stack” boundary: Rust owns request behavior; declarative files own HTML composition. SvelteKit, Next, Rari, Leptos, and Dioxus all separate route rendering from loaders/actions, response control, errors, and layouts—but their implementations also depend on JS/WASM, macros, RSC, hydration, or server-function code generation. Bunfork should copy the observable HTTP contract, not their implementation models. See SvelteKit server loads/actions and endpoints ([SvelteKit fixture](</home/olafurbui/bunfork/.refrepos/sveltekit/packages/adapter-vercel/test/apps/basic/src/routes/server-data/+page.server.ts:1>), [endpoint](</home/olafurbui/bunfork/.refrepos/sveltekit/packages/adapter-vercel/test/apps/basic/src/routes/api/json/+server.ts:1>)); Next’s file conventions ([project structure](</home/olafurbui/bunfork/.refrepos/nextjs/docs/01-app/01-getting-started/02-project-structure.mdx:55>)); Leptos response mutation ([ResponseParts](</home/olafurbui/bunfork/.refrepos/leptos/integrations/axum/src/lib.rs:72>)); and Dioxus typed cookies/errors ([login form](</home/olafurbui/bunfork/.refrepos/dioxus/examples/07-fullstack/login_form.rs:1>), [errors](</home/olafurbui/bunfork/.refrepos/dioxus/examples/07-fullstack/handling_errors.rs:1>)).

## Exact application contract

Compiled Rust application crate:

```rust
pub trait App: Send + Sync + 'static {
    fn routes(&self) -> &'static [Route];
}

pub struct Route {
    pub pattern: &'static str,
    pub methods: &'static [Method],
    pub page: &'static str,
    pub load: Option<LoadFn>,
    pub action: Option<ActionFn>,
}

type LoadFn = fn(RequestContext) -> BoxFuture<'static, Result<Value, AppError>>;
type ActionFn = fn(RequestContext, Form) -> BoxFuture<'static, Result<ActionResult, AppError>>;
```

The kernel exposes:

```rust
pub struct RequestContext {
    pub method: Method,
    pub uri: Uri,
    pub params: BTreeMap<String, String>,
    pub query: Query,
    pub headers: HeaderMap,
    pub cookies: Cookies,
    pub body: Bytes,
    pub state: Arc<AppState>,
}

pub enum ActionResult {
    Render(Value),
    Redirect { location: String, status: StatusCode }, // 303 normally
    Response(Response),
}

pub enum AppError {
    Status { status: StatusCode, public: &'static str },
    Redirect { location: String, status: StatusCode },
    Internal(anyhow::Error),
}
```

Use ordinary Rust functions and a route table. No procedural macros, plugin discovery, dynamic loading, embedded JS, or generated client protocol.

Declarative tree:

```text
app/
  pages/
    __layout.html
    index.html
    account/
      __layout.html
      index.html
      error.html
      loading.html        # optional; only used for explicit server-side staged rendering
      __action.html       # optional action result template
    404.html
    500.html
  public/
```

`__layout.html` wraps the nearest child outward-to-root. `template.html` receives:

```text
params, query, pathname, request, data, flash, errors
```

`request` exposes only method, URL, safe headers, and authenticated identity—not secrets, raw authorization headers, or database keys. Templates remain MiniJinja with autoescaping, extending the current request-local rendering contract ([current implementation](</home/olafurbui/bunfork/src/server.rs:376>)).

A page route may have a Rust loader and action registered by exact pattern. Declarative files cannot execute Rust, access the database, perform authorization, or mutate state.

## Request precedence

1. Security middleware: size limits, method checks, path normalization, hidden-path rejection.
2. Exact route match and parameter extraction.
3. Authentication and CSRF checks.
4. Loader for `GET`/`HEAD`.
5. Action for permitted non-`GET` methods.
6. Render page through layouts.
7. Convert `AppError` into redirect/status/error template.
8. Apply security and response headers.

Static routes outrank parameter routes, which outrank catch-alls, preserving the existing router’s useful precedence ([router matching](</home/olafurbui/bunfork/src/server.rs:262>)).

`GET` and `HEAD` render. `POST` invokes the action. `PUT`, `PATCH`, and `DELETE` are opt-in per route. Unsupported methods return `405` with `Allow`. Successful form mutations default to `303 See Other`; validation failures re-render with `422`; missing routes use `404.html`; unexpected failures use `500.html` without exposing error details.

## CSRF and auth

- Require `Origin` or same-site `Referer` validation for browser mutations.
- Require a per-session CSRF token in a hidden form field plus an `HttpOnly`, `SameSite=Lax`, `Secure` cookie.
- Compare tokens in constant time.
- Reject cross-origin state-changing requests, missing tokens, oversized bodies, and malformed form encoding.
- Sessions must be opaque, random, revocable server-side identifiers; never encode identity or authorization in an unsigned cookie.
- Default cookies: `HttpOnly; Secure; SameSite=Lax; Path=/`.
- Authorization runs in Rust before loader/action execution.
- CSRF is not needed for authenticated `GET`; state changes through `GET` are forbidden.

Dioxus demonstrates cookie-based auth and typed request extraction ([source](</home/olafurbui/bunfork/.refrepos/dioxus/examples/07-fullstack/login_form.rs:72>)); Bunfork should provide the security policy centrally rather than expose raw header handling to templates.

## Optional encrypted data

Keep SQLCipher optional and outside the page kernel:

```rust
pub trait Store: Send + Sync {
    fn get(&self, key: &str) -> Result<Option<Value>>;
    fn put(&self, key: &str, value: &Value) -> Result<()>;
}
```

`AppState` may contain `Arc<dyn Store>`. If configured, use the existing SQLCipher database and secret-loading safeguards ([database API](</home/olafurbui/bunfork/src/db.rs:31>)); otherwise the MPA server starts with no database, key, token, migrations, or vector service. Application data must never be serialized into cookies. Encryption at rest is optional; TLS remains mandatory for deployment.

## Coexistence with `bunfork-static-v1`

Separate commands and artifacts:

```text
bunfork serve --static ...       # immutable byte-serving kernel
bunfork serve --app target/app   # compiled Rust MPA
bunfork serve --static ... --app target/app   # explicit composition
```

Static lookup runs first only for paths declared by the static manifest. Application routes run second. `/api`, `/assets`, and other reserved paths must be explicit; no implicit shadowing.

The static artifact remains byte-preserving and JS-inert as required by reports 03–06 ([static boundary](</home/olafurbui/bunfork/docs/adversarial/06-frontend-protocol.md:1>)). It must never invoke loaders or actions. Conversely, MPA templates must not read or rewrite static files.

CLI values override manifest/config values; config overrides defaults. No framework config, package script, directory naming, or auto-detection changes behavior.

## Black-box tests

- Start MPA mode with no database or secrets.
- `GET`, `HEAD`, permitted actions, `405`, malformed paths, query decoding, repeated parameters, and body-size limits.
- Loader data reaches nested layouts and pages without request leakage under concurrency.
- HTML escaping covers params, query, form values, and errors.
- Valid form: `POST → mutation → 303 → GET`.
- Invalid form: `422`, preserved safe fields, no mutation.
- Missing/invalid CSRF, cross-origin `Origin`, replayed token, and missing session all fail.
- Authenticated loader/action sees identity; unauthenticated access returns `401` or `303` according to route policy.
- Custom nearest error template, root error template, `404`, and generic `500`.
- Cookie flags and redirect headers are exact.
- Encrypted store is absent in page-only mode; wrong SQLCipher key fails startup; no plaintext secret appears in response/logs.
- Static manifest tests from pass 6 remain unchanged.

## Explicit non-goals

No source compatibility with Svelte/Vue/TSX; no RSC, hydration, WASM, streaming, HMR, middleware/plugin system, server-function RPC protocol, automatic route discovery, framework adapters, arbitrary endpoint scripts, or runtime JavaScript.

Compile Rust because it is trusted, typed, and performs loaders/actions/auth/storage. Keep layouts, pages, and error presentation declarative because MiniJinja already provides that capability. This is enough to make “full-stack” truthful without pretending Bunfork implements the ecosystems it only hosts statically.