Menu
AkurAI-Build
publicLatest change 1c2159692a31765cd66ed709791ba11468054873 - Initial commit: bunfork v0.1.0 source tree by Olafur Bui
## Pass 6 decision
Adopt one narrow contract:
> `bunfork-static-v1` hosts an explicitly admitted, prebuilt static directory. It does not compile, proxy, execute, or emulate SvelteKit, Next, Nuxt, Vite, or SSR output.
This matches the reports’ strongest common boundary: official framework builds produce browser artifacts; Bunfork may serve those bytes only ([03-sveltekit.md](/home/olafurbui/bunfork/docs/adversarial/03-sveltekit.md:3), [04-nextjs.md](/home/olafurbui/bunfork/docs/adversarial/04-nextjs.md:1), [05-vue-nuxt.md](/home/olafurbui/bunfork/docs/adversarial/05-vue-nuxt.md:1)).
### Exact CLI
Framework build hooks are explicit argv, never detected or shell-expanded:
```sh
bunfork build \
--run <program> \
--arg <arg>... \
--output <DIR> \
--manifest <FILE>
```
Examples:
```sh
bunfork build --run npm --arg run --arg build --output dist
bunfork build --run pnpm --arg exec --arg vite --arg build --output dist
bunfork build --run cargo --arg run --arg -- --build --output dist
```
Execution rules:
- `program` is passed directly to `Command`; no shell, interpolation, pipes, globbing, or implicit package manager.
- Working directory is explicit: `--cwd <DIR>`, defaulting to the current directory.
- Environment inheritance is denied by default. Add repeated `--env NAME=VALUE` only if required.
- Output must be an existing directory after the command exits successfully.
- Nonzero exit, signal termination, timeout, output absence, or malformed output is fatal.
- This replaces the current unconstrained `cargo` subprocess path ([main.rs](/home/olafurbui/bunfork/src/main.rs:331), [main.rs](/home/olafurbui/bunfork/src/main.rs:564)).
Admission and serving:
```sh
bunfork admit --artifact <DIR> --manifest <FILE>
bunfork serve --static <DIR> --manifest <FILE> \
--address 0.0.0.0:3100
```
`--address` defaults to `0.0.0.0:3100`, preserving the current default ([main.rs](/home/olafurbui/bunfork/src/main.rs:20), [README.md](/home/olafurbui/bunfork/README.md:19)).
`dev` remains a separate, explicit command:
```sh
bunfork dev --static <DIR> --manifest <FILE> \
--address 0.0.0.0:3100
```
It may rescan and revalidate the artifact on each request, but it is not HMR. No browser client injection, proxying, source compilation, or framework config interpretation.
### Frozen manifest
```json
{
"format": "bunfork-static-v1",
"root": ".",
"mode": "mpa",
"base": "/",
"fallback": null,
"trailing_slash": "directory",
"precompressed": true,
"immutable": true,
"files": {
"index.html": {
"sha256": "hex-64",
"bytes": 1234
}
}
}
```
Required invariants:
- `root` is `"."` only.
- `mode` is `mpa` or `spa`.
- `base` is `/` or a normalized absolute prefix ending in `/`.
- `fallback` is `null` or a relative file path; reject `index.html` by default.
- `trailing_slash` is `ignore`, `redirect`, or `directory`.
- `precompressed` and `immutable` are booleans and must be `true` for this version.
- `files` is complete, lexicographically sorted, and contains SHA-256 plus byte length for every regular file, including the manifest only if it is outside the hashed set.
- Unknown keys, duplicate keys, invalid digests, missing files, extra files, changed bytes, symlinks, hard links, devices, hidden path segments, and paths escaping the root are fatal.
Admission produces a deterministic report containing the normalized manifest digest, file count, total bytes, routes, fallback, compression variants, and rejection findings. Startup rechecks the digest before binding. Any post-admission mutation causes startup failure.
### Routing
For `GET` and `HEAD` only:
1. Validate URL decoding and reject traversal or malformed encoding.
2. Require the configured `base`; reject requests outside it.
3. `/` maps to `index.html`.
4. `/x/` maps to `x/index.html`.
5. `/x` maps to `x/index.html` only in `directory` mode.
6. `redirect` mode redirects `/x` to `/x/`.
7. Never infer `.html`.
8. MPA misses return `404`, using `404.html` if present.
9. SPA fallback applies only after ordinary lookup fails and never to asset, source-map, JSON, or API-like paths.
10. `HEAD` returns GET status and headers with no body.
11. Other methods return `405` and `Allow: GET, HEAD`.
Serve `.br` or `.gz` only when advertised; otherwise serve the original, always emitting `Vary: Accept-Encoding`. Hashed assets receive long immutable caching; HTML, fallback, and error documents are revalidated. Preserve bytes and never inject scripts or rewrite HTML ([03-sveltekit.md](/home/olafurbui/bunfork/docs/adversarial/03-sveltekit.md:65)).
### Precedence
Explicit CLI values override manifest values. Manifest values override built-in defaults. Environment variables may supply only global server defaults such as address, and have lower precedence than argv. No framework config, package script, directory name, or detected file changes behavior.
### Database and deployment ownership
`serve --static` and `dev --static` must not load SQLCipher, migrations, database keys, API tokens, tenant, or model. The current unconditional secret/database startup is therefore split out of static serving ([main.rs](/home/olafurbui/bunfork/src/main.rs:215)).
Deployment ownership is manifest-based:
- `deploy --force` may replace only a directory containing a valid `bunfork-static-v1` manifest whose complete digest set verifies.
- Never replace `.git`, `src`, `data`, `target`, `backups`, arbitrary direct children, or an unowned directory.
- Stage in a new sibling directory, fsync files and directory, atomically rename, and retain the previous verified release.
- Do not copy source pages, Cargo output, secrets, databases, or systemd files into a static artifact.
- The current ownership check is Bunfork-specific and insufficient for this format ([main.rs](/home/olafurbui/bunfork/src/main.rs:483), [03-sveltekit.md](/home/olafurbui/bunfork/docs/adversarial/03-sveltekit.md:84)).
### Explicit non-goals
Do not add now:
- auto-detection;
- HMR or HMR proxying;
- SSR sidecars;
- Nitro, Next standalone, adapter-node, server actions, API routes, middleware, RSC, runtime loaders;
- source-map interpretation or JavaScript execution.
Those outputs require a server/runtime contract and are explicitly outside static export boundaries ([04-nextjs.md](/home/olafurbui/bunfork/docs/adversarial/04-nextjs.md:35), [05-vue-nuxt.md](/home/olafurbui/bunfork/docs/adversarial/05-vue-nuxt.md:46)).
### Acceptance tests
1. Build command with spaces/metacharacters proves argv isolation; failing subprocess prevents admission.
2. Identical artifact trees produce identical manifest bytes and digest.
3. One-byte mutation, added file, removed file, symlink, hard link, hidden file, device, traversal, or malformed manifest prevents startup.
4. Start static mode with no database or secrets; `/`, assets, deep links, and `HEAD` succeed.
5. Verify exact `GET`/`HEAD`/`405`, base-path rejection, trailing-slash behavior, MPA 404, SPA fallback exclusion, MIME, ETag, cache, compression, CSP, and byte identity.
6. `deploy --force` preserves sentinels in `.git`, `src`, and `data`, while replacing only a previously admitted owned artifact.
7. Official SvelteKit `adapter-static`, Next `output: export`, Vite, and Nuxt `generate` outputs pass; SSR/server/Nitro/standalone fixtures fail before bind.