From fe82291ee5f95eb4b2398aa56110e68bf52875b4 Mon Sep 17 00:00:00 2001
From: mira-2026
Date: Thu, 6 Aug 2026 07:00:14 +0200
Subject: [PATCH 1/4] test(qualification): close greenfield phase zero
---
bun.lock | 4 +-
bunfig.toml | 2 +-
docs/api/endpoints.md | 54 +-
docs/architecture/greenfield-rewrite.md | 8 +-
.../application-architecture.md | 94 +-
.../greenfield-rewrite/implementation-plan.md | 69 +-
.../greenfield-rewrite/progress.md | 56 +-
.../runtime-and-delivery.md | 72 +-
docs/generated/packages-and-runtime.md | 4 +-
package.json | 4 +-
.../browser/queryCollectionAdapter.test.ts | 256 ++
.../browser/queryCollectionAdapter.ts | 152 ++
.../budgets/resourceBudgetCommand.ts | 199 ++
.../resourceBudgetOrchestration.test.ts | 39 +
.../budgets/resourceBudgetOrchestration.ts | 497 ++++
.../budgets/resourceBudgetPolicy.test.ts | 242 ++
qualification/budgets/resourceBudgetPolicy.ts | 404 ++++
qualification/budgets/resourceBudgetUnit.ts | 463 ++++
.../budgets/runResourceBudgetEvidence.ts | 13 +
.../runSafeChildCancellationEvidence.ts | 18 +
.../build/fixtures/frontend/index.html | 13 +
.../build/fixtures/frontend/src/LazyPanel.tsx | 7 +
.../frontend/src/QualificationApp.tsx | 22 +
.../build/fixtures/frontend/src/index.css | 5 +
.../build/fixtures/frontend/src/main.tsx | 10 +
.../build/frontendBuildQualification.test.ts | 121 +
.../build/frontendBuildQualification.ts | 146 ++
.../build/runFrontendBuildQualification.ts | 93 +
qualification/chat/chatBatching.test.ts | 80 +
qualification/chat/chatBatchingModel.ts | 234 ++
.../chat/chatBatchingQualification.ts | 205 ++
.../chat/runChatBatchingQualification.ts | 49 +
.../fixtures/2026.7.2-beta.7/agents.json | 19 +
.../fixtures/2026.7.2-beta.7/chat.json | 90 +
.../fixtures/2026.7.2-beta.7/cron.json | 18 +
.../fixtures/2026.7.2-beta.7/gateway.json | 23 +
.../fixtures/2026.7.2-beta.7/manifest.json | 177 ++
.../fixtures/2026.7.2-beta.7/sessions.json | 166 ++
.../fixtures/2026.7.2-beta.7/tasks.json | 76 +
qualification/openclaw/reviewedFixtures.ts | 273 +++
qualification/openclaw/runSourceAudit.ts | 91 +
qualification/openclaw/sourceAudit.test.ts | 451 ++++
qualification/openclaw/sourceAudit.ts | 1053 +++++++++
qualification/openclaw/sourceAuditSchemas.ts | 553 +++++
.../outbox/runSqliteOutboxEvidence.ts | 48 +
qualification/outbox/sqliteOutboxChild.ts | 236 ++
qualification/outbox/sqliteOutboxProtocol.ts | 36 +
.../outbox/sqliteOutboxQualification.test.ts | 238 ++
.../outbox/sqliteOutboxQualification.ts | 383 +++
qualification/outbox/sqliteOutboxStore.ts | 488 ++++
.../parity/fixtures/frontend-routes.json | 272 +++
.../parity/fixtures/greenfield-contracts.json | 178 ++
.../parity/fixtures/legacy-endpoints.json | 2069 +++++++++++++++++
.../parity/legacyBackendRouteInventory.ts | 159 ++
.../parity/parityFixtureCandidate.ts | 112 +
qualification/parity/parityInventory.test.ts | 224 ++
.../parity/parityInventorySchemas.ts | 281 +++
.../parity/reviewedParityInventory.ts | 206 ++
.../parity/sourceParityInventory.test.ts | 145 ++
qualification/parity/sourceParityInventory.ts | 469 ++++
.../resources/pausedTlsSseClient.test.ts | 102 +-
qualification/resources/pausedTlsSseClient.ts | 600 +++--
qualification/resources/sseMemoryScenario.ts | 22 +-
.../completeShutdownQualification.test.ts | 143 ++
.../shutdown/completeShutdownQualification.ts | 689 ++++++
.../shutdown/runCompleteShutdownEvidence.ts | 6 +
qualification/shutdown/shutdownDatabase.ts | 220 ++
qualification/shutdown/shutdownGrandchild.ts | 3 +
.../shutdown/shutdownIdleHttpConnection.ts | 152 ++
qualification/shutdown/shutdownProtocol.ts | 182 ++
qualification/shutdown/shutdownService.ts | 311 +++
.../shutdown/shutdownServiceResources.test.ts | 127 +
.../shutdown/shutdownServiceResources.ts | 575 +++++
qualification/test/asyncCleanupStack.test.ts | 121 +
qualification/test/asyncCleanupStack.ts | 119 +-
.../nativeWebSocketQualification.test.ts | 394 ++++
.../websocket/nativeWebSocketQualification.ts | 333 +++
.../websocket/rawWebSocketFixture.ts | 308 +++
.../websocket/rawWebSocketProtocol.ts | 343 +++
scripts/frontendBuild.ts | 2 +-
.../qualification/legacyBackendRouteProbe.ts | 36 +
src/app/server.ts | 53 +-
.../runtime/applicationRuntime.test.ts | 165 +-
.../platform/runtime/applicationRuntime.ts | 136 +-
src/server/test/support/requestContext.ts | 4 +
src/server/test/system/serverShutdown.test.ts | 68 +-
86 files changed, 16988 insertions(+), 395 deletions(-)
create mode 100644 qualification/browser/queryCollectionAdapter.test.ts
create mode 100644 qualification/browser/queryCollectionAdapter.ts
create mode 100644 qualification/budgets/resourceBudgetCommand.ts
create mode 100644 qualification/budgets/resourceBudgetOrchestration.test.ts
create mode 100644 qualification/budgets/resourceBudgetOrchestration.ts
create mode 100644 qualification/budgets/resourceBudgetPolicy.test.ts
create mode 100644 qualification/budgets/resourceBudgetPolicy.ts
create mode 100644 qualification/budgets/resourceBudgetUnit.ts
create mode 100644 qualification/budgets/runResourceBudgetEvidence.ts
create mode 100644 qualification/budgets/runSafeChildCancellationEvidence.ts
create mode 100644 qualification/build/fixtures/frontend/index.html
create mode 100644 qualification/build/fixtures/frontend/src/LazyPanel.tsx
create mode 100644 qualification/build/fixtures/frontend/src/QualificationApp.tsx
create mode 100644 qualification/build/fixtures/frontend/src/index.css
create mode 100644 qualification/build/fixtures/frontend/src/main.tsx
create mode 100644 qualification/build/frontendBuildQualification.test.ts
create mode 100644 qualification/build/frontendBuildQualification.ts
create mode 100644 qualification/build/runFrontendBuildQualification.ts
create mode 100644 qualification/chat/chatBatching.test.ts
create mode 100644 qualification/chat/chatBatchingModel.ts
create mode 100644 qualification/chat/chatBatchingQualification.ts
create mode 100644 qualification/chat/runChatBatchingQualification.ts
create mode 100644 qualification/openclaw/fixtures/2026.7.2-beta.7/agents.json
create mode 100644 qualification/openclaw/fixtures/2026.7.2-beta.7/chat.json
create mode 100644 qualification/openclaw/fixtures/2026.7.2-beta.7/cron.json
create mode 100644 qualification/openclaw/fixtures/2026.7.2-beta.7/gateway.json
create mode 100644 qualification/openclaw/fixtures/2026.7.2-beta.7/manifest.json
create mode 100644 qualification/openclaw/fixtures/2026.7.2-beta.7/sessions.json
create mode 100644 qualification/openclaw/fixtures/2026.7.2-beta.7/tasks.json
create mode 100644 qualification/openclaw/reviewedFixtures.ts
create mode 100644 qualification/openclaw/runSourceAudit.ts
create mode 100644 qualification/openclaw/sourceAudit.test.ts
create mode 100644 qualification/openclaw/sourceAudit.ts
create mode 100644 qualification/openclaw/sourceAuditSchemas.ts
create mode 100644 qualification/outbox/runSqliteOutboxEvidence.ts
create mode 100644 qualification/outbox/sqliteOutboxChild.ts
create mode 100644 qualification/outbox/sqliteOutboxProtocol.ts
create mode 100644 qualification/outbox/sqliteOutboxQualification.test.ts
create mode 100644 qualification/outbox/sqliteOutboxQualification.ts
create mode 100644 qualification/outbox/sqliteOutboxStore.ts
create mode 100644 qualification/parity/fixtures/frontend-routes.json
create mode 100644 qualification/parity/fixtures/greenfield-contracts.json
create mode 100644 qualification/parity/fixtures/legacy-endpoints.json
create mode 100644 qualification/parity/legacyBackendRouteInventory.ts
create mode 100644 qualification/parity/parityFixtureCandidate.ts
create mode 100644 qualification/parity/parityInventory.test.ts
create mode 100644 qualification/parity/parityInventorySchemas.ts
create mode 100644 qualification/parity/reviewedParityInventory.ts
create mode 100644 qualification/parity/sourceParityInventory.test.ts
create mode 100644 qualification/parity/sourceParityInventory.ts
create mode 100644 qualification/shutdown/completeShutdownQualification.test.ts
create mode 100644 qualification/shutdown/completeShutdownQualification.ts
create mode 100644 qualification/shutdown/runCompleteShutdownEvidence.ts
create mode 100644 qualification/shutdown/shutdownDatabase.ts
create mode 100644 qualification/shutdown/shutdownGrandchild.ts
create mode 100644 qualification/shutdown/shutdownIdleHttpConnection.ts
create mode 100644 qualification/shutdown/shutdownProtocol.ts
create mode 100644 qualification/shutdown/shutdownService.ts
create mode 100644 qualification/shutdown/shutdownServiceResources.test.ts
create mode 100644 qualification/shutdown/shutdownServiceResources.ts
create mode 100644 qualification/test/asyncCleanupStack.test.ts
create mode 100644 qualification/websocket/nativeWebSocketQualification.test.ts
create mode 100644 qualification/websocket/nativeWebSocketQualification.ts
create mode 100644 qualification/websocket/rawWebSocketFixture.ts
create mode 100644 qualification/websocket/rawWebSocketProtocol.ts
create mode 100644 scripts/qualification/legacyBackendRouteProbe.ts
diff --git a/bun.lock b/bun.lock
index 3aecd2a9b..5431a1cb0 100644
--- a/bun.lock
+++ b/bun.lock
@@ -15,8 +15,8 @@
"@simplewebauthn/server": "13.3.2",
"@tailwindcss/typography": "^0.5.20",
"@tanstack/query-core": "5.101.4",
- "@tanstack/query-db-collection": "^1.2.1",
- "@tanstack/react-db": "^0.1.95",
+ "@tanstack/query-db-collection": "1.2.1",
+ "@tanstack/react-db": "0.1.95",
"@tanstack/react-form": "^1.33.3",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-router": "^1.170.18",
diff --git a/bunfig.toml b/bunfig.toml
index 4de6cdc6e..822bc082e 100644
--- a/bunfig.toml
+++ b/bunfig.toml
@@ -14,5 +14,5 @@ coveragePathIgnorePatterns = [
]
[serve.static]
-plugins = ["bun-plugin-tailwind", "./scripts/reactCompilerPlugin.ts"]
+plugins = ["./scripts/reactCompilerPlugin.ts", "bun-plugin-tailwind"]
environment = "PUBLIC_*"
diff --git a/docs/api/endpoints.md b/docs/api/endpoints.md
index f55e9acaf..dd29cc583 100644
--- a/docs/api/endpoints.md
+++ b/docs/api/endpoints.md
@@ -13,7 +13,9 @@ route files under `backend/src/routes/` for exact validation details.
| Method | Path | Purpose |
| ------ | ------------------------- | ------------------------------------------------------ |
| `GET` | `/api/health/live` | Public web-process liveness. |
+| `HEAD` | `/api/health/live` | Bodyless public web-process liveness probe. |
| `GET` | `/api/health/ready` | Public activation readiness; `503` when not ready. |
+| `HEAD` | `/api/health/ready` | Bodyless readiness probe with the same status as GET. |
| `GET` | `/api/health/diagnostics` | Authenticated readiness details and dependency status. |
| `GET` | `/api/sessions` | Normalized session snapshot from Gateway. |
@@ -200,6 +202,13 @@ already-running execution finishes cooperatively.
| `POST` | `/api/backup` | Creates config backup. |
| `POST` | `/api/restart` | Queues an OpenClaw Gateway restart and waits for its persisted result. |
+## Dashboard Settings
+
+| Method | Path | Purpose |
+| ------ | --------------- | ------------------------------------------------------------- |
+| `GET` | `/api/settings` | Reads Dashboard preferences plus current Gateway connection. |
+| `PUT` | `/api/settings` | Updates the validated Dashboard preference subset atomically. |
+
## Files, Config Files, Logs, Media
| Method | Path | Purpose |
@@ -210,8 +219,9 @@ already-running execution finishes cooperatively.
| `GET` | `/api/config-files` | Lists OpenClaw config files. |
| `GET` | `/api/config-files/*` | Reads a config file under OpenClaw root. |
| `PUT` | `/api/config-files/*` | Writes a config file under OpenClaw root. |
-| `GET` | `/api/logs/info` | Lists log files/metadata. |
-| `GET` | `/api/logs/content` | Reads log content. |
+| `GET` | `/api/logs/dashboard` | Reads the bounded Dashboard service log tail. |
+| `GET` | `/api/logs/openclaw/files` | Lists readable OpenClaw log files and metadata. |
+| `GET` | `/api/logs/openclaw/content` | Reads a bounded tail from one allowlisted OpenClaw log file. |
| `GET` | `/api/media` | Serves or safely previews media bytes from OpenClaw media roots. |
| `GET` | `/api/chat/media/outgoing/*` | Proxies an exact managed Gateway media path with backend-held auth. |
@@ -232,31 +242,33 @@ upstream download metadata.
## Docker
-| Method | Path | Purpose |
-| -------- | ------------------------------------------------ | ----------------------------------------- |
-| `GET` | `/api/docker/containers` | Lists containers. |
-| `GET` | `/api/docker/containers/:containerId` | Reads container details. |
-| `POST` | `/api/docker/containers/:containerId/action` | Queues a container start/stop/restart. |
-| `GET` | `/api/docker/containers/:containerId/logs` | Reads container logs. |
-| `POST` | `/api/docker/exec/start` | Queues a worker-owned container exec job. |
-| `GET` | `/api/docker/exec/:jobId` | Reads persisted exec output/state. |
-| `POST` | `/api/docker/exec/:jobId/stop` | Requests exec cancellation. |
-| `GET` | `/api/docker/images` | Lists images. |
-| `DELETE` | `/api/docker/images/:imageId` | Queues image deletion. |
-| `GET` | `/api/docker/volumes` | Lists volumes. |
-| `DELETE` | `/api/docker/volumes/:volumeName` | Queues volume deletion. |
-| `POST` | `/api/docker/prune` | Queues a Docker prune target. |
-| `POST` | `/api/docker/stack/action` | Queues a Compose stack action. |
-| `GET` | `/api/docker/updater/services` | Lists managed update services. |
-| `GET` | `/api/docker/updater/events` | Lists update events. |
-| `POST` | `/api/docker/updater/run` | Queues an updater scan. |
-| `POST` | `/api/docker/updater/services/:serviceId/update` | Queues one managed service update. |
+| Method | Path | Purpose |
+| -------- | ------------------------------------------------ | ------------------------------------------- |
+| `GET` | `/api/docker/containers` | Lists containers. |
+| `GET` | `/api/docker/containers/stats` | Reads the current container stats snapshot. |
+| `GET` | `/api/docker/containers/:containerId` | Reads container details. |
+| `POST` | `/api/docker/containers/:containerId/action` | Queues a container start/stop/restart. |
+| `GET` | `/api/docker/containers/:containerId/logs` | Reads container logs. |
+| `POST` | `/api/docker/exec/start` | Queues a worker-owned container exec job. |
+| `GET` | `/api/docker/exec/:jobId` | Reads persisted exec output/state. |
+| `POST` | `/api/docker/exec/:jobId/stop` | Requests exec cancellation. |
+| `GET` | `/api/docker/images` | Lists images. |
+| `DELETE` | `/api/docker/images/:imageId` | Queues image deletion. |
+| `GET` | `/api/docker/volumes` | Lists volumes. |
+| `DELETE` | `/api/docker/volumes/:volumeName` | Queues volume deletion. |
+| `POST` | `/api/docker/prune` | Queues a Docker prune target. |
+| `POST` | `/api/docker/stack/action` | Queues a Compose stack action. |
+| `GET` | `/api/docker/updater/services` | Lists managed update services. |
+| `GET` | `/api/docker/updater/events` | Lists update events. |
+| `POST` | `/api/docker/updater/run` | Queues an updater scan. |
+| `POST` | `/api/docker/updater/services/:serviceId/update` | Queues one managed service update. |
## Pull Requests And Deployments
| Method | Path | Purpose |
| ------ | -------------------------------------------- | ---------------------------------------------------------- |
| `GET` | `/api/pull-requests` | Lists Dashboard PRs. |
+| `POST` | `/api/pull-requests/stacks` | Creates one reviewed native GitHub PR stack. |
| `POST` | `/api/pull-requests/:number/approve` | Queues merge, optionally followed by deploy. |
| `POST` | `/api/pull-requests/:number/reject` | Queues reject/close. |
| `POST` | `/api/pull-requests/:number/review-approval` | Queues review approval. |
diff --git a/docs/architecture/greenfield-rewrite.md b/docs/architecture/greenfield-rewrite.md
index 2be4053ff..be194525d 100644
--- a/docs/architecture/greenfield-rewrite.md
+++ b/docs/architecture/greenfield-rewrite.md
@@ -1,9 +1,11 @@
# Greenfield Rewrite Blueprint
-> **Status:** implementation started. The rewrite is built beside the current production
-> implementation and targets a fresh database with no compatibility layer.
+> **Status:** implementation active. Phase 0 evidence is complete and Phase 2 is complete for its
+> stated server scope; the remaining foundation, browser, domain, Gateway/chat, privileged,
+> hardening, and cutover phases are not complete. The rewrite is built beside the current
+> production implementation and targets a fresh database with no compatibility layer.
>
-> **Audit date:** 2026-08-04. Package versions and the Bun canary snapshot in this document
+> **Audit date:** 2026-08-06. Package versions and the Bun canary snapshot in this document
> are point-in-time facts. They are rechecked during an explicit candidate-promotion round,
> not for ordinary feature or review commits.
diff --git a/docs/architecture/greenfield-rewrite/application-architecture.md b/docs/architecture/greenfield-rewrite/application-architecture.md
index c93a544da..02e3ed92b 100644
--- a/docs/architecture/greenfield-rewrite/application-architecture.md
+++ b/docs/architecture/greenfield-rewrite/application-architecture.md
@@ -283,6 +283,29 @@ protocol again. Current-production Gateway/chat/session/agent/cron code supplies
not protocol authority. The consolidated controls and executable evidence are in the
[Phase 2 threat model](../../security/greenfield-phase-two-threat-model.md).
+### Current-protocol Control UI projections
+
+The 2026-08-06 OpenClaw audit separates protocol authority from Control UI projection through 22
+hash-pinned, redacted distribution artifacts. The current behavior informs Phase 4, but Dashboard
+must re-audit the installed source and use a typed protocol adapter rather than scrape, import, or
+mirror Control UI implementation details:
+
+- plan/checklist state is projected from generic `agent` events and retained only on the active
+ in-flight run; it is not a durable plan record or a dedicated plan RPC;
+- companion ask is labeled with `operator.read` upstream but starts new compute and is constrained
+ by process-local TTL and concurrency caps. Dashboard treats it as an explicit compute action,
+ preserves those bounded semantics, and does not cache it as read-only state;
+- the background-task ledger supports list, detail, and cancellation. Cancellation is a write/admin
+ operation, can lose a race to normal completion, and must expose that result instead of claiming
+ a task was stopped; and
+- `cancelled` and `timed_out` remain distinct protocol states even when a presentation groups both
+ with failures.
+
+The Phase 4 browser surface exposes the active plan/checklist, companion ask, and background-task
+details/cancellation through that adapter, with authorization and race behavior covered by recorded
+fixtures. These projections complement the persistent Dashboard chat journal; they do not make
+ephemeral OpenClaw in-flight state durable by inference.
+
### Raw HTTP exists only for protocol edges
The explicit raw-route registry owns requests whose semantics are HTTP rather than domain RPC:
@@ -343,12 +366,20 @@ constant failure markers.
The web `ApplicationRuntime` merges the realtime pump and one process-scoped authentication-work
service into the same `ManagedRuntime`. That authentication service owns separate bounded admission
and active-work semaphores for Gateway verification, password/Argon2 work, TOTP AES/HMAC work, and
-WebAuthn parsing/signature verification, plus a scoped fiber set for work that outlives an interrupted caller. Queued cancellation releases
-admission immediately; active non-cooperative work retains its permit until settlement. Promise-
-facing adapters fold typed capacity into explicit domain throttling outcomes, while Gateway
+WebAuthn parsing/signature verification, plus a scoped fiber set for work that outlives an
+interrupted caller. Queued cancellation releases admission immediately; active non-cooperative
+work retains its permit until settlement. Promise-facing adapters fold typed capacity into
+explicit domain throttling outcomes, while Gateway
capacity, deadline, and unavailable tags are exhaustively translated before the tRPC procedure
maps the resulting domain outcome. No request creates or disposes a runtime.
+The same `ManagedRuntime` coordinates listener shutdown. An external `stop(true)` request crosses
+the Promise-facing composition boundary as an abort signal; Effect owns the graceful-stop fiber,
+deadline/force race, tagged stop and timeout failures, separately bounded force attempt, and
+settlement of the original graceful operation before the runtime scope is disposed. A rejected
+graceful stop receives one bounded best-effort force attempt while preserving the initiating
+failure. No second runtime or manual timer/`Promise.race` shutdown system is created.
+
## Realtime Architecture
### One browser stream
@@ -417,11 +448,14 @@ an explicit local runtime state machine for active work:
- `chat_runtime_snapshots` stores the latest compact projection needed for fast restart
recovery.
-Gateway token/thinking/tool deltas are coalesced into small ordered batches before a SQLite
-transaction and SSE emission. The design never performs one durable commit per token. A final
-Gateway history fetch reconciles the runtime projection without duplicating messages. On
-restart, Dashboard restores the snapshot and remaining journal, reconnects to Gateway, and
-reconciles again.
+Gateway token and thinking deltas are coalesced into ordered 150 ms batches before a SQLite
+transaction and SSE emission. The interval matches the audited OpenClaw source throttle and is the
+smallest candidate that meets the measured write-rate, visual-delay, and crash-window policy for
+one, four, and eight concurrent runs. Tool/item boundaries, terminal deltas, cancellation, and
+completion flush immediately; the design never performs one durable commit per token. A final
+Gateway history fetch reconciles the runtime projection without duplicating messages. On restart,
+Dashboard restores the snapshot and remaining journal, reconnects to Gateway, and reconciles
+again.
This state machine must retain all current behavior: token streaming, thinking and tool row
ordering, tool failure scoping, final-message reconciliation, cancel/retry, concurrent sends,
@@ -447,6 +481,41 @@ small connection store may expose SSE/Gateway health without owning domain data.
own reducer/state-machine store because its ordered transient events must survive route changes
and reconnects. It is not combined with general server cache state.
+### Browser Effect boundary
+
+Effect is available in the browser, but the same selective boundary applies as on the server.
+TanStack and React continue to own rendering, server-state caches, normalized collections,
+forms, URL state, and ordinary component state. Effect owns browser work only when asynchronous
+lifetimes are themselves part of the correctness contract: scoped subscriptions or streams,
+coordinated cancellation, bounded queues/concurrency, explicit retry/backoff, multi-step uploads,
+and tagged operational failures.
+
+The first browser feature that needs such orchestration creates one browser-composition runtime
+and disposes it during application/test teardown. Hooks and renders never create runtimes, fibers,
+or duplicate retry loops. An Effect service publishes stable snapshots into the owning TanStack
+Store, Query, or collection boundary; it does not become a second domain-state cache. Simple tRPC
+query functions, Valibot parsing, reducers, deterministic state transitions, and individual event
+handlers remain ordinary TypeScript. Existing tRPC/TanStack cancellation and retry behavior is
+reused rather than wrapped merely because a function is asynchronous.
+
+### Evaluated browser dependency candidates
+
+The following registry/documentation review was performed on 2026-08-06. It records candidates,
+not blanket installation approval. Every adopted pre-1.0 package is exact-pinned and requalified in
+the vertical slice that first needs it; competing libraries are not shipped together.
+
+| Candidate | Current decision |
+| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `react-resizable-panels` `4.12.2` | Likely adoption for accessible chat, file, log, and terminal split panes. Add it only with the first real pane layout, keyboard/focus tests, bounded persisted sizes, responsive fallback, and teardown evidence. |
+| TanStack Markdown `0.0.13` | Alpha replacement candidate for the current `react-markdown`/remark/rehype chain. Before adoption, replay the complete chat/report/file corpus and verify the required GFM subset, accumulated AI streaming, unsafe HTML/URL handling, deterministic rendering, and bundle delta. |
+| TanStack Highlight `0.0.10` | Alpha companion candidate for Markdown and code/file previews. Qualify the explicit language registry, embedded-language fidelity, line/range annotations, escaping, themes, and bundle delta against the current `react-syntax-highlighter`/`refractor` surface. Adopt Markdown and Highlight independently. |
+| TanStack Charts + React adapter `0.6.5` | Preferred typed/accessibility candidate for Phase 3 metrics, but still pre-alpha. Compare representative time-series, categorical, tooltip, resize, keyboard, theme, export, and high-point-count cases against Recharts before selecting exactly one renderer. |
+| Recharts `3.10.1` | Mature fallback if the TanStack Charts spike fails. Its larger dependency surface, including Redux infrastructure, must be justified by concrete parity or stability evidence; it is not installed alongside TanStack Charts. |
+| TanStack Pacer / React Pacer `0.21.1`/`0.22.1` | Beta candidate only for repeated browser timing needs such as observable debounce, throttle, and UI batching. Server/process concurrency remains Effect-owned. The transitive `@tanstack/pacer-lite` used by TanStack DB is not an application API or reason to add the full package. |
+| Motion `13.0.0` | Optional later dependency for complex gesture, shared-layout, or interruptible animation parity. CSS/Tailwind remains the default; any adoption uses the current `motion` entry point, route-level code splitting, and reduced-motion tests rather than a direct legacy `framer-motion` import. |
+| `react-refresh` `0.18.0` | Not an application/runtime dependency. A future qualified custom HMR development path may own it as build tooling; the selected production AOT build does not ship it. |
+| SWR `2.5.0` | Rejected. It duplicates tRPC/TanStack Query remote-state ownership and would introduce a second cache, retry policy, and invalidation model. |
+
### What Query Collections are for
A TanStack DB Query Collection is the bridge from a TanStack Query snapshot to a normalized,
@@ -463,9 +532,12 @@ It is not used merely because data came from the server:
- form drafts stay in TanStack Form; and
- chat runtime events stay in the dedicated chat store/state machine.
-Collections are created once per `QueryClient` and hidden behind a small Dashboard adapter
-because TanStack DB is still pre-1.0. The package is exact-pinned. A server snapshot always
-wins over conflicting speculative collection state.
+Collections are created once per `QueryClient` and cache key and hidden behind a small Dashboard
+adapter because TanStack DB is still pre-1.0. The exact-qualified package set is
+`@tanstack/db@0.6.17`, `@tanstack/query-db-collection@1.2.1`,
+`@tanstack/react-db@0.1.95`, and `@tanstack/query-core@5.101.4`. Route teardown disposes only the
+route subscription; it does not destroy and recreate an asynchronous collection under the same
+cache key. A server snapshot always wins over conflicting speculative collection state.
### Component and route rules
diff --git a/docs/architecture/greenfield-rewrite/implementation-plan.md b/docs/architecture/greenfield-rewrite/implementation-plan.md
index 12dc17a05..1b48817f3 100644
--- a/docs/architecture/greenfield-rewrite/implementation-plan.md
+++ b/docs/architecture/greenfield-rewrite/implementation-plan.md
@@ -17,6 +17,10 @@ compatibility migration inside it.
**Exit gate:** every architecture risk marked mandatory below has a passing executable spike.
+**Status (2026-08-06): complete.** Exact-candidate qualification, source-derived parity, and the
+eight executable spikes below pass. This closes evidence selection only; Phase 1 foundation and
+the remaining rewrite phases are still incomplete.
+
### Phase 1: foundation
- create source boundaries, configuration, logging, errors, contract registry, Drizzle/native
@@ -84,30 +88,37 @@ Given the current chat, auth, worker, delivery, and host-integration surface, th
**50–80 focused engineer-days**, not a small transport refactor. Automation can reduce elapsed
time, but it cannot remove the qualification, security, restore, and parity gates.
-## Mandatory Spikes and Open Decisions
-
-The target choices are fixed unless one of these tests disproves the underlying assumption:
-
-1. **Bun full-stack build:** verify React Compiler-first transforms, Tailwind, lazy chunks,
- source maps, CSP, asset hashes, precompression, and bundle budgets. Then select exactly one
- production build path.
-2. **tRPC SSE on exact Bun:** verify credentials, aborts, tracked resume, error shapes,
- reverse-proxy behavior, deploy reconnect, and slow-consumer memory.
-3. **SQLite outbox latency:** measure adaptive polling with web/worker processes under chat and
- job load. Keep the database authoritative; change only the wakeup mechanism if latency or
- I/O misses the target.
-4. **Chat batching:** determine the smallest durable delta interval that preserves current
- visual streaming while bounding SQLite writes and restart loss.
-5. **TanStack DB adapter:** prove snapshot replacement, direct batch writes, query-cache
- synchronization, optimistic-conflict handling, and route teardown without duplicate rows.
-6. **Drizzle on Bun SQLite:** verify sync transactions, prepared statements, the `sql` tagged
- template, partial/unique indexes, generated migrations, Valibot row schemas, native-client
- access, and query plans on the exact pinned Drizzle version and resolved Bun qualification
- candidate.
-7. **Bun canary shutdown:** verify graceful SSE, Gateway, prepared-statement, worker lease, and
- child-process cleanup under systemd stop/restart.
-8. **Resource budgets:** measure build/test and representative privileged jobs in cgroups before
- finalizing service/job limits.
+## Mandatory Spikes and Decisions
+
+All eight Phase 0 spikes have executable evidence on the audited Bun candidate. Their selected
+outcomes remain normative unless a later runtime or dependency qualification disproves them:
+
+1. **Passed — Bun full-stack build:** use one compiler-first Bun HTML ahead-of-time production
+ build. Tailwind, lazy chunks, CSP, hashes, precompression, source-map policy, and bundle budgets
+ pass in the mechanism fixture and actual frontend build; there is no fallback build path.
+2. **Passed — tRPC SSE on exact Bun:** credentials, cancellation, tracked resume, typed errors,
+ proxy/TLS streaming, rolling reconnect, and bounded slow-consumer behavior pass.
+3. **Passed — SQLite outbox latency:** separate web and worker processes deliver a WAL-backed
+ durable outbox without gaps or duplicates, classify real busy/locked outcomes, and recover an
+ expired claim after hard worker termination.
+4. **Passed — chat batching:** use ordered 150 ms token/thinking batches. One, four, and eight
+ concurrent runs meet the selected write/delay policy, while tool/item, terminal, cancel, and
+ completion boundaries flush immediately.
+5. **Passed — TanStack DB adapter:** the exact-pinned local adapter proves snapshot replacement,
+ direct batch writes, query-cache synchronization, optimistic-conflict resolution, forwarded
+ cancellation, and subscription teardown without duplicate rows.
+6. **Passed — Drizzle on Bun SQLite:** synchronous transactions, prepared-statement lifetime,
+ native access, constraints/indexes/query plans, schema validation, migrations, checkpoint,
+ backup, restore, and integrity pass on the exact candidate.
+7. **Passed — Bun canary shutdown:** two service generations prove readiness withdrawal, SSE and
+ Gateway closure, prepared-statement/database disposal, worker-lease recovery, process-group
+ cleanup, WAL recovery, and bounded Effect-owned graceful-to-force listener shutdown.
+8. **Passed — resource budgets:** capped sequential build, test, SQLite, chat, shutdown, and child
+ cancellation runs complete without high/max/OOM events, memory pressure, or leaked resources.
+
+The exact candidate qualification additionally covers raw RFC 6455 continuation and fragmented
+UTF-8 reassembly, protocol/application size closes, deterministic cancellation, and explicit
+absence of reconnect. The source-derived inventory accounts for 156 HTTP operations plus `/ws`.
The OpenClaw audit is deliberately point-in-time. The Phase 2 one-shot verifier records the
installed `2026.7.2-beta.7 (dabe191)` protocol-v4 behavior, but it does not qualify persistent
@@ -163,7 +174,7 @@ not package memory alone:
- [`bun test`](https://bun.com/docs/test)
- [Full-stack development server and HTML imports](https://bun.com/docs/bundler/fullstack)
- [Official canary release asset](https://github.com/oven-sh/bun/releases/tag/canary)
-- [Latest audited Bun commit](https://github.com/oven-sh/bun/commit/43783cedd5653fa29bb9ac83df34633eae10fe75)
+- [Latest audited Bun commit](https://github.com/oven-sh/bun/commit/17d6843606d76620cb55d31424d7fb0aed51c367)
### tRPC and validation
@@ -195,6 +206,14 @@ not package memory alone:
- [TanStack Form validation](https://tanstack.com/form/latest/docs/framework/react/guides/validation)
- [TanStack Store React quick start](https://tanstack.com/store/latest/docs/framework/react/quick-start)
- [TanStack Virtual](https://tanstack.com/virtual/latest/docs/introduction)
+- [TanStack Pacer](https://tanstack.com/pacer/latest/docs/overview)
+- [TanStack Markdown](https://tanstack.com/markdown/latest/docs/overview)
+- [TanStack Highlight](https://tanstack.com/highlight/latest/docs/overview)
+- [TanStack Charts](https://tanstack.com/charts/latest/docs/overview)
+- [Recharts](https://www.npmjs.com/package/recharts)
+- [React Resizable Panels](https://www.npmjs.com/package/react-resizable-panels)
+- [Motion for React](https://motion.dev/docs/react)
+- [SWR](https://swr.vercel.app/)
### Database, security, and tooling
diff --git a/docs/architecture/greenfield-rewrite/progress.md b/docs/architecture/greenfield-rewrite/progress.md
index 66c6dd10a..98e1f8361 100644
--- a/docs/architecture/greenfield-rewrite/progress.md
+++ b/docs/architecture/greenfield-rewrite/progress.md
@@ -9,7 +9,7 @@ closes a phase; dated entries below provide the evidence, not a second status so
| Phase | Status | Current evidence and remaining gate |
| ----------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| 0 — Evidence and qualification | In progress | Bun/SQLite/tRPC/SSE and production-shaped realtime evidence passes; frontend build, TanStack DB, full shutdown, chat batching, and measured resource-budget spikes remain. |
+| 0 — Evidence and qualification | Complete | All eight mandatory spikes pass on exact Bun revision `17d6843606d76620cb55d31424d7fb0aed51c367`, including build, transport, database/outbox, browser data, chat batching, shutdown, parity, OpenClaw source audit, and capped resource evidence. |
| 1 — Foundation | In progress | Server composition, migrations, contracts, raw HTTP policy, realtime outbox, and the current generated-doc subset exist; browser/worker roots, complete import enforcement, complete generated references, and release/rollback closure remain. |
| 2 — Trust and transport | Complete for the stated server scope | Authentication, MFA, WebAuthn, automation credentials, audit, authenticated renewable SSE, one-shot native Gateway bootstrap verification, and the consolidated [threat model](../../security/greenfield-phase-two-threat-model.md) have executable evidence. Browser UI and production cutover remain later gates. |
| 3 — Core operator domains | Started | Monitoring transaction/schema foundations exist; task, agent, report, incident, notification, schedule/job, cache/metrics procedures and browser parity are not complete. |
@@ -586,11 +586,61 @@ closes a phase; dated entries below provide the evidence, not a second status so
wrong-ID/contradictory frames, exact mismatch classification, header and URL secrecy,
deterministic absence of retry after `startup-sidecars`, native connection refusal,
close-confirmed terminal races, redaction, timeout, and real HTTP-to-Effect-to-socket
- cancellation with zero user/session/audit/rate-limit publication. Raw continuation-frame
- qualification remains an explicit Phase 0 gate. The consolidated
+ cancellation with zero user/session/audit/rate-limit publication. Phase 0 now separately
+ qualifies raw continuation frames and fragmented-message behavior. The consolidated
[Phase 2 threat model](../../security/greenfield-phase-two-threat-model.md) maps the complete
authentication, MFA, WebAuthn, automation, SSE, migration, and Gateway evidence to misuse cases
and residual risks.
- This closes Phase 2 only for its documented server-side scope. It does not claim full native
persistent Gateway qualification. Phase 4 must re-audit the then-installed OpenClaw source and
protocol before implementing persistent connection, event recovery, sessions, chat, or cron.
+
+### 2026-08-06 — Phase 0 evidence and qualification closed
+
+- Bun `1.4.0-canary.1+17d684360`, full revision
+ `17d6843606d76620cb55d31424d7fb0aed51c367`, passes qualification typecheck and the complete
+ qualification suite: 133 tests, 682 assertions, zero failures, and 30 files. This is the exact
+ audited candidate for the round, not a repository-wide source-revision pin.
+- The selected frontend path is one compiler-first Bun HTML AOT build. Executable fixture and
+ actual-build evidence cover Tailwind, lazy chunks, CSP-compatible assets, hashes,
+ precompression, absent production source maps, and bundle budgets. The exact-pinned TanStack DB
+ adapter covers snapshot replacement, direct batch writes, query-cache synchronization,
+ optimistic conflicts, cancellation, and route-subscription teardown.
+- File-backed WAL evidence uses separate web and worker processes and covers reader/writer and
+ writer/writer behavior, real busy/locked classification, no-gap/no-duplicate outbox delivery,
+ hard-kill claim recovery, savepoints, prepared-statement disposal, checkpoint, backup, restore,
+ and integrity. Chat qualification selects 150 ms ordered token/thinking batches for one, four,
+ and eight concurrent runs, with immediate tool/item, terminal, cancel, and completion flushes.
+- Raw RFC 6455 tests cover continuation reassembly, a UTF-8 code point split across three frames,
+ orphan/interleaved-fragment `1002` closes, invalid-length and 64 KiB application-bound `1009`
+ closes, deterministic cancellation/close, partial writes, native refusal, and exactly one
+ connection attempt without reconnect.
+- The two-generation shutdown test withdraws readiness before cleanup, closes SSE and the local
+ Gateway connection, disposes the statement and WAL database, recovers the worker lease, ends the
+ detached process group, and restarts on the same database without a leak. The candidate's
+ intentional keep-alive behavior requires a scoped Effect graceful-stop fiber followed by a
+ separately bounded force escalation; the candidate records `listener-force-stopped` and closes
+ every owned resource. The production listener now uses the same process `ManagedRuntime` for its
+ tagged graceful/deadline/force orchestration, including explicit force requests, original-fiber
+ settlement, and best-effort containment after graceful rejection.
+- Source-derived parity now accounts for all 156 current HTTP operations plus `/ws`. The OpenClaw
+ audit pins 22 redacted source/protocol/UI artifacts for installed `2026.7.2-beta.7 (dabe191)`,
+ including the generic-event, ephemeral plan/checklist projection, compute-starting companion ask,
+ and background-task list/detail/cancel semantics. These are Phase 4 adapter requirements, not an
+ invitation to scrape the Control UI.
+- The exact-candidate capped resource matrix passes without `high`, `max`, `oom`, or `oom_kill`
+ memory events, memory pressure, or leaked process, unit, or temporary state:
+
+ | Scenario | Peak memory (bytes) | Elapsed (ms) | Peak tasks |
+ | --------------------- | ------------------: | -----------: | ---------: |
+ | Frontend build | 650,104,832 | 14,793 | 19 |
+ | Representative tests | 248,758,272 | 2,222 | 18 |
+ | SQLite outbox/restore | 101,896,192 | 1,218 | 20 |
+ | Chat batching | 42,676,224 | 97 | 12 |
+ | Complete shutdown | 128,774,144 | 3,133 | 25 |
+ | Child cancellation | 117,194,752 | 1,531 | 24 |
+
+- Phase 0 is complete, but the rewrite is not: Phase 1 remains in progress with browser/worker
+ roots, complete import enforcement, complete generated references, immutable release/rollback,
+ and end-to-end empty-database web/worker delivery still open. Final production load, restore,
+ cutover, and legacy-removal evidence remains in Phase 6.
diff --git a/docs/architecture/greenfield-rewrite/runtime-and-delivery.md b/docs/architecture/greenfield-rewrite/runtime-and-delivery.md
index 7c6044438..7bf1d6f0f 100644
--- a/docs/architecture/greenfield-rewrite/runtime-and-delivery.md
+++ b/docs/architecture/greenfield-rewrite/runtime-and-delivery.md
@@ -11,9 +11,9 @@
| Repository channel | `canary` |
| Required runtime version | `1.4.0` |
| Running production release runtime | `1.4.0-canary.1+e82022145` |
-| Audited qualification candidate | `1.4.0-canary.1+43783cedd` |
-| Audited full revision | `43783cedd5653fa29bb9ac83df34633eae10fe75` |
-| Audited commit date | 2026-08-03 22:02:12 UTC |
+| Audited qualification candidate | `1.4.0-canary.1+17d684360` |
+| Audited full revision | `17d6843606d76620cb55d31424d7fb0aed51c367` |
+| Audited commit date | 2026-08-06 00:27:30 UTC |
The audited revision is evidence for this qualification round, not a repository-wide pin.
Normal CI resolves the `canary` channel and runs the complete gate set. Release creation then
@@ -42,7 +42,7 @@ the Bun HTML pipeline, but workspace files and media retain explicit, policy-che
### Mandatory canary qualification
-Before the new repository baseline is locked, run the following in an isolated, memory-capped
+Before promoting a new repository baseline, run the following in an isolated, memory-capped
environment against the exact candidate binary:
1. Fetch-adapter query and mutation tests, including cookies, aborts, response headers, and
@@ -58,10 +58,49 @@ environment against the exact candidate binary:
6. `bun test --isolate` tests for fake timers, leaked handles, deterministic shutdown, and
bounded concurrency.
-The current one-shot Phase 2 verifier qualifies complete text `MessageEvent` delivery only. Raw
-continuation-frame reassembly and fragmented-message behavior remain an explicit open Phase 0
-native-WebSocket gate and must be qualified against the then-current Bun candidate before the
-repository baseline is locked.
+The 2026-08-06 qualification round passes on exact revision
+`17d6843606d76620cb55d31424d7fb0aed51c367`: qualification typecheck passes, and the full suite
+reports 133 tests, 682 assertions, and zero failures across 30 files. Its executable evidence
+includes:
+
+- compiler-first Bun HTML AOT output with Tailwind, lazy chunks, CSP-compatible assets, hashes,
+ precompression, no production source maps, and enforced bundle budgets;
+- Fetch/tRPC/SSE cancellation, resume, proxy, rolling-restart, and slow-consumer behavior;
+- raw RFC 6455 continuation reassembly with a UTF-8 code point split across frames, protocol-close
+ `1002`, application-bound `1009`, a 64 KiB limit, deterministic close, and exactly one connection
+ attempt without reconnect;
+- WAL SQLite with separate web and worker processes, actual busy/locked behavior, durable outbox
+ delivery and lease recovery, statement disposal, checkpoint, backup, restore, and integrity;
+- the exact-pinned TanStack DB adapter, snapshot/cache synchronization, batch writes, optimistic
+ conflict handling, cancellation, and route-subscription teardown;
+- 150 ms chat-delta batching for one, four, and eight concurrent runs with immediate boundary and
+ terminal flushes;
+- a two-generation shutdown with readiness withdrawal, SSE and Gateway closure, statement and
+ database disposal, worker-lease recovery, child-process-group cleanup, WAL recovery, and no
+ leaked process; and
+- source-derived parity for 156 current HTTP operations plus `/ws`, together with 22 hash-pinned,
+ redacted OpenClaw protocol and Control UI audit artifacts.
+
+The candidate intentionally makes `server.stop(false)` wait for idle keep-alive connections. The
+shutdown qualification therefore uses an Effect-scoped graceful-stop fiber with a bounded wait and
+a separately bounded `server.stop(true)` escalation. The exact candidate records
+`listener-force-stopped`, then closes SSE and every owned resource without a leak; the event model
+permits exactly one graceful or forced terminal outcome.
+
+The candidate resource matrix also passes without `high`, `max`, `oom`, or `oom_kill` memory
+events, memory pressure, or leaked process, unit, or temporary state:
+
+| Scenario | Peak memory (bytes) | Elapsed (ms) | Peak tasks |
+| --------------------- | ------------------: | -----------: | ---------: |
+| Frontend build | 650,104,832 | 14,793 | 19 |
+| Representative tests | 248,758,272 | 2,222 | 18 |
+| SQLite outbox/restore | 101,896,192 | 1,218 | 20 |
+| Chat batching | 42,676,224 | 97 | 12 |
+| Complete shutdown | 128,774,144 | 3,133 | 25 |
+| Child-process cancel | 117,194,752 | 1,531 | 24 |
+
+These measurements qualify the mechanisms and current limits; Phase 6 still owns final
+production-shaped load, restore, and cutover evidence.
`.bun-version` selects the `canary` channel through the official `setup-bun` action. The serving
process enforces Bun `1.4.0`, while the runtime revision remains diagnostic until release creation
@@ -100,17 +139,12 @@ const server = Bun.serve({
});
```
-The preferred frontend build uses Bun's HTML entrypoint and ahead-of-time production build.
-The React Compiler plugin must run before other Babel transforms, followed by Bun and the
-Tailwind plugin. Because Bun still labels the full-stack development server as work in
-progress, phase 0 must choose one proven build mode:
-
-- preferred: Bun HTML import/full-stack entry with an AOT production build; or
-- if the qualification fails: explicit browser and server `Bun.build` entrypoints.
-
-Only the selected mode is implemented. There is no production fallback or duplicate build
-path. In either case, the release contains prebuilt assets, hashes, compressed variants,
-source-map policy, and a manifest; production never compiles the frontend on request.
+Phase 0 selects Bun's HTML entrypoint with an ahead-of-time production build. The production
+devtools stub runs first when present, React Compiler then runs before Tailwind, and no runtime
+full-stack development server is part of delivery. The executable fixture and actual frontend
+build prove lazy chunks, CSP-compatible external assets, content hashes, absent production source
+maps, precompressed variants, and bundle budgets. There is no production fallback or duplicate
+build path: releases contain prebuilt assets and production never compiles the frontend on request.
## Configuration From Scratch
diff --git a/docs/generated/packages-and-runtime.md b/docs/generated/packages-and-runtime.md
index 4b3ccced4..a85167611 100644
--- a/docs/generated/packages-and-runtime.md
+++ b/docs/generated/packages-and-runtime.md
@@ -24,8 +24,8 @@
| `@simplewebauthn/server` | `13.3.2` | `13.3.2` | runtime |
| `@tailwindcss/typography` | `^0.5.20` | `0.5.20` | runtime |
| `@tanstack/query-core` | `5.101.4` | `5.101.4` | runtime |
-| `@tanstack/query-db-collection` | `^1.2.1` | `1.2.1` | runtime |
-| `@tanstack/react-db` | `^0.1.95` | `0.1.95` | runtime |
+| `@tanstack/query-db-collection` | `1.2.1` | `1.2.1` | runtime |
+| `@tanstack/react-db` | `0.1.95` | `0.1.95` | runtime |
| `@tanstack/react-form` | `^1.33.3` | `1.33.3` | runtime |
| `@tanstack/react-query` | `^5.101.4` | `5.101.4` | runtime |
| `@tanstack/react-router` | `^1.170.18` | `1.170.18` | runtime |
diff --git a/package.json b/package.json
index a02aef397..8d4e63ea2 100644
--- a/package.json
+++ b/package.json
@@ -57,8 +57,8 @@
"@simplewebauthn/server": "13.3.2",
"@tailwindcss/typography": "^0.5.20",
"@tanstack/query-core": "5.101.4",
- "@tanstack/query-db-collection": "^1.2.1",
- "@tanstack/react-db": "^0.1.95",
+ "@tanstack/query-db-collection": "1.2.1",
+ "@tanstack/react-db": "0.1.95",
"@tanstack/react-form": "^1.33.3",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-router": "^1.170.18",
diff --git a/qualification/browser/queryCollectionAdapter.test.ts b/qualification/browser/queryCollectionAdapter.test.ts
new file mode 100644
index 000000000..882ebad39
--- /dev/null
+++ b/qualification/browser/queryCollectionAdapter.test.ts
@@ -0,0 +1,256 @@
+import { describe, expect, test } from "bun:test";
+
+import { QueryClient } from "@tanstack/query-core";
+
+import {
+ createQualificationQueryCollection,
+ QualificationCollectionConflictError,
+ type QualificationPersistedUpdate,
+} from "./queryCollectionAdapter";
+
+interface QualificationItem {
+ id: string;
+ label: string;
+ version: number;
+}
+
+const collectionKey = ["qualification", "items"] as const;
+
+describe("TanStack DB Query Collection adapter qualification", () => {
+ test("replaces snapshots and synchronizes direct batches with Query cache", async () => {
+ const queryClient = createQueryClient();
+ let authoritative: QualificationItem[] = [
+ { id: "a", label: "server-a", version: 1 },
+ { id: "b", label: "server-b", version: 1 },
+ ];
+ const adapter = createQualificationQueryCollection({
+ id: "qualification-items",
+ queryClient,
+ queryKey: collectionKey,
+ fetchSnapshot: () => Promise.resolve(structuredClone(authoritative)),
+ });
+
+ try {
+ await adapter.preload();
+ expect(project(adapter.rows())).toEqual(authoritative);
+ expect(cachedItems(queryClient)).toEqual(authoritative);
+
+ adapter.applyBatch([
+ {
+ type: "upsert",
+ value: { id: "a", label: "delta-a", version: 2 },
+ },
+ { type: "delete", id: "b" },
+ {
+ type: "upsert",
+ value: { id: "c", label: "delta-c", version: 1 },
+ },
+ ]);
+ const deltaRows = [
+ { id: "a", label: "delta-a", version: 2 },
+ { id: "c", label: "delta-c", version: 1 },
+ ];
+ expect(project(adapter.rows())).toEqual(deltaRows);
+ expect(cachedItems(queryClient)).toEqual(deltaRows);
+
+ authoritative = [{ id: "a", label: "server-wins", version: 3 }];
+ await adapter.refetchAuthoritative();
+ expect(project(adapter.rows())).toEqual(authoritative);
+ expect(cachedItems(queryClient)).toEqual(authoritative);
+ } finally {
+ await adapter.dispose();
+ }
+ });
+
+ test("lets the authoritative refetch win an optimistic version conflict", async () => {
+ const queryClient = createQueryClient();
+ const persistence = Promise.withResolvers();
+ const persistedUpdates: QualificationPersistedUpdate[][] = [];
+ const authoritative = [
+ { id: "a", label: "authoritative", version: 2 },
+ ] satisfies QualificationItem[];
+ const adapter = createQualificationQueryCollection({
+ id: "qualification-optimistic-items",
+ queryClient,
+ queryKey: collectionKey,
+ fetchSnapshot: () => Promise.resolve(structuredClone(authoritative)),
+ persistUpdates: async (updates) => {
+ persistedUpdates.push([...updates]);
+ await persistence.promise;
+ },
+ });
+
+ try {
+ await adapter.preload();
+ const update = adapter.updateOptimistically("a", 2, {
+ label: "speculative",
+ version: 3,
+ });
+ expect(project(adapter.rows())).toEqual([
+ { id: "a", label: "speculative", version: 3 },
+ ]);
+
+ persistence.resolve();
+ await update;
+ expect(persistedUpdates).toEqual([
+ [
+ {
+ modified: { id: "a", label: "speculative", version: 3 },
+ original: { id: "a", label: "authoritative", version: 2 },
+ },
+ ],
+ ]);
+ expect(project(adapter.rows())).toEqual(authoritative);
+
+ let conflict: unknown;
+ try {
+ await adapter.updateOptimistically("a", 1, {
+ label: "stale",
+ version: 2,
+ });
+ } catch (error) {
+ conflict = error;
+ }
+ expect(conflict).toBeInstanceOf(QualificationCollectionConflictError);
+ expect(project(adapter.rows())).toEqual(authoritative);
+ } finally {
+ await adapter.dispose();
+ }
+ });
+
+ test("forwards AbortSignal and removes an in-flight query on teardown", async () => {
+ const queryClient = createQueryClient();
+ const fetchStarted = Promise.withResolvers();
+ const firstAdapter = createQualificationQueryCollection({
+ id: "qualification-route-items",
+ queryClient,
+ queryKey: collectionKey,
+ fetchSnapshot: (signal) => {
+ fetchStarted.resolve(signal);
+ return new Promise((_resolve, reject) => {
+ signal.addEventListener(
+ "abort",
+ () => reject(new DOMException("Aborted", "AbortError")),
+ { once: true }
+ );
+ });
+ },
+ });
+
+ const preload = firstAdapter.preload();
+ const signal = await fetchStarted.promise;
+ expect(signal.aborted).toBeFalse();
+ await firstAdapter.dispose();
+ await preload;
+ expect(signal.aborted).toBeTrue();
+ expect(firstAdapter.isDisposed).toBeTrue();
+ expect(cachedItems(queryClient)).toBeUndefined();
+ });
+
+ test("tears down route subscriptions without duplicate rows or listeners", async () => {
+ const queryClient = createQueryClient();
+ const adapter = createQualificationQueryCollection({
+ id: "qualification-route-items",
+ queryClient,
+ queryKey: collectionKey,
+ fetchSnapshot: () =>
+ Promise.resolve([
+ { id: "a", label: "first-route", version: 1 },
+ ] satisfies QualificationItem[]),
+ });
+ try {
+ await adapter.preload();
+ let firstRouteNotifications = 0;
+ const unsubscribeFirstRoute = adapter.subscribe(() => {
+ firstRouteNotifications += 1;
+ });
+ adapter.applyBatch([
+ {
+ type: "upsert",
+ value: { id: "a", label: "first-update", version: 2 },
+ },
+ ]);
+ const notificationsAtTeardown = firstRouteNotifications;
+ unsubscribeFirstRoute();
+
+ let replacementRouteNotifications = 0;
+ const unsubscribeReplacementRoute = adapter.subscribe(() => {
+ replacementRouteNotifications += 1;
+ });
+ adapter.applyBatch([
+ {
+ type: "upsert",
+ value: { id: "a", label: "single-row", version: 3 },
+ },
+ ]);
+ unsubscribeReplacementRoute();
+
+ expect(notificationsAtTeardown).toBe(2);
+ expect(firstRouteNotifications).toBe(2);
+ expect(replacementRouteNotifications).toBe(2);
+ expect(project(adapter.rows())).toEqual([
+ { id: "a", label: "single-row", version: 3 },
+ ]);
+ expect(cachedItems(queryClient)).toEqual([
+ { id: "a", label: "single-row", version: 3 },
+ ]);
+ } finally {
+ await adapter.dispose();
+ }
+ expect(cachedItems(queryClient)).toBeUndefined();
+ });
+
+ test("runs against the exact installed TanStack dependency set", async () => {
+ expect(await readInstalledVersions()).toEqual({
+ "@tanstack/db": "0.6.17",
+ "@tanstack/query-core": "5.101.4",
+ "@tanstack/query-db-collection": "1.2.1",
+ "@tanstack/react-db": "0.1.95",
+ });
+ });
+});
+
+function createQueryClient(): QueryClient {
+ return new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+ });
+}
+
+function project(items: readonly QualificationItem[]): QualificationItem[] {
+ return items.map(({ id, label, version }) => ({ id, label, version }));
+}
+
+function cachedItems(queryClient: QueryClient): QualificationItem[] | undefined {
+ return queryClient.getQueryData(collectionKey);
+}
+
+async function readInstalledVersions(): Promise> {
+ const packageNames = [
+ "@tanstack/db",
+ "@tanstack/query-core",
+ "@tanstack/query-db-collection",
+ "@tanstack/react-db",
+ ] as const;
+ const versions: Record = {};
+ for (const packageName of packageNames) {
+ const packageJsonUrl = new URL(
+ `../../node_modules/${packageName}/package.json`,
+ import.meta.url
+ );
+ const parsed: unknown = JSON.parse(await Bun.file(packageJsonUrl).text());
+ if (
+ typeof parsed !== "object" ||
+ parsed === null ||
+ !("version" in parsed) ||
+ typeof parsed.version !== "string"
+ ) {
+ throw new Error(`${packageName} has no package version`);
+ }
+ versions[packageName] = parsed.version;
+ }
+ return versions;
+}
diff --git a/qualification/browser/queryCollectionAdapter.ts b/qualification/browser/queryCollectionAdapter.ts
new file mode 100644
index 000000000..cf6481cfe
--- /dev/null
+++ b/qualification/browser/queryCollectionAdapter.ts
@@ -0,0 +1,152 @@
+import { QueryClient, type QueryKey } from "@tanstack/query-core";
+import { queryCollectionOptions } from "@tanstack/query-db-collection";
+import { createCollection, type UpdateMutationFnParams } from "@tanstack/react-db";
+
+export interface QualificationVersionedEntity {
+ id: string;
+ version: number;
+}
+
+export type QualificationCollectionDelta =
+ | { type: "delete"; id: string }
+ | { type: "upsert"; value: T };
+
+export interface QualificationPersistedUpdate {
+ modified: T;
+ original: T;
+}
+
+export interface QualificationQueryCollectionOptions<
+ T extends QualificationVersionedEntity,
+> {
+ fetchSnapshot: (signal: AbortSignal) => Promise;
+ id: string;
+ persistUpdates?: (
+ updates: readonly QualificationPersistedUpdate[]
+ ) => Promise;
+ queryClient: QueryClient;
+ queryKey: QueryKey;
+}
+
+export class QualificationCollectionConflictError extends Error {
+ readonly _tag = "QualificationCollectionConflictError";
+}
+
+/**
+ * Qualification-only seam around the current pre-1.0 Query Collection API.
+ * It exercises the intended browser ownership without creating production code.
+ * @param options Query client, snapshot, and persistence dependencies.
+ * @returns Qualification collection adapter.
+ */
+export function createQualificationQueryCollection<
+ T extends QualificationVersionedEntity,
+>(options: QualificationQueryCollectionOptions) {
+ const stableQueryKey = [...options.queryKey];
+ let disposed = false;
+ const onUpdate = options.persistUpdates
+ ? async ({ transaction }: UpdateMutationFnParams) => {
+ await options.persistUpdates?.(
+ transaction.mutations.map(({ modified, original }) => ({
+ modified,
+ original,
+ }))
+ );
+ }
+ : undefined;
+
+ const collection = createCollection(
+ queryCollectionOptions({
+ id: options.id,
+ queryClient: options.queryClient,
+ queryFn: async ({ signal }) => [...(await options.fetchSnapshot(signal))],
+ queryKey: stableQueryKey,
+ getKey: (entity: T) => entity.id,
+ ...(onUpdate ? { onUpdate } : {}),
+ })
+ );
+
+ function assertActive(): void {
+ if (disposed) {
+ throw new Error(`Qualification collection ${options.id} is disposed`);
+ }
+ }
+
+ return {
+ applyBatch(deltas: readonly QualificationCollectionDelta[]): void {
+ assertActive();
+ collection.utils.writeBatch(() => {
+ for (const delta of deltas) {
+ if (delta.type === "delete") {
+ collection.utils.writeDelete(delta.id);
+ } else {
+ collection.utils.writeUpsert(delta.value);
+ }
+ }
+ });
+ },
+ async dispose(): Promise {
+ if (disposed) return;
+ disposed = true;
+ await collection.cleanup();
+ options.queryClient.removeQueries({
+ exact: true,
+ queryKey: stableQueryKey,
+ });
+ },
+ get(id: string): T | undefined {
+ assertActive();
+ return collection.get(id);
+ },
+ get isDisposed(): boolean {
+ return disposed;
+ },
+ preload(): Promise {
+ assertActive();
+ return collection.preload();
+ },
+ async refetchAuthoritative(): Promise {
+ assertActive();
+ await collection.utils.refetch({ throwOnError: true });
+ },
+ rows(): readonly T[] {
+ assertActive();
+ return collection.toArray;
+ },
+ subscribe(listener: (rows: readonly T[]) => void): () => void {
+ assertActive();
+ const subscription = collection.subscribeChanges(
+ () => listener(collection.toArray),
+ { includeInitialState: true }
+ );
+ return () => subscription.unsubscribe();
+ },
+ async updateOptimistically(
+ id: string,
+ expectedVersion: number,
+ changes: Partial
+ ): Promise {
+ assertActive();
+ if (!options.persistUpdates) {
+ throw new Error(
+ `Qualification collection ${options.id} has no mutation persistence`
+ );
+ }
+ const current = collection.get(id);
+ if (!current || current.version !== expectedVersion) {
+ throw new QualificationCollectionConflictError(
+ `Expected ${id} at version ${expectedVersion}`
+ );
+ }
+ if (changes.id !== undefined && changes.id !== id) {
+ throw new QualificationCollectionConflictError(
+ "An optimistic update cannot change its entity id"
+ );
+ }
+
+ const transaction = collection.update(id, (draft) => {
+ Object.assign(draft, changes);
+ });
+ await transaction.isPersisted.promise;
+ },
+ };
+}
diff --git a/qualification/budgets/resourceBudgetCommand.ts b/qualification/budgets/resourceBudgetCommand.ts
new file mode 100644
index 000000000..bb811033c
--- /dev/null
+++ b/qualification/budgets/resourceBudgetCommand.ts
@@ -0,0 +1,199 @@
+import path from "node:path";
+
+import {
+ assertResourceBudgetUnitName,
+ resourceBudgetPolicy,
+ type ResourceBudgetScenarioId,
+} from "./resourceBudgetPolicy.ts";
+
+const launcherEnvironmentNames = [
+ "DBUS_SESSION_BUS_ADDRESS",
+ "HOME",
+ "LANG",
+ "PATH",
+ "XDG_RUNTIME_DIR",
+] as const;
+
+export interface ResourceBudgetCommandOptions {
+ readonly bunExecutable: string;
+ readonly childEntrypoint: string;
+ readonly envExecutable: string;
+ readonly environment: Readonly>;
+ readonly repositoryRoot: string;
+ readonly resultPath: string;
+ readonly scenarioId: ResourceBudgetScenarioId;
+ readonly systemctlExecutable: string;
+ readonly systemdRunExecutable: string;
+ readonly temporaryDirectory: string;
+ readonly unitName: string;
+}
+
+export interface ResourceBudgetLauncherCommand {
+ readonly argv: readonly string[];
+ readonly environment: Readonly>;
+ readonly resultPath: string;
+ readonly scenarioId: ResourceBudgetScenarioId;
+ readonly systemctlExecutable: string;
+ readonly unitName: string;
+}
+
+export interface ResourceBudgetWorkloadCommand {
+ readonly argv: readonly string[];
+ readonly environment: Readonly>;
+}
+
+function assertAbsolutePath(label: string, value: string): void {
+ if (!path.isAbsolute(value) || value.includes("\0")) {
+ throw new TypeError(`${label} must be an absolute path`);
+ }
+}
+
+function sanitizedEnvironment(
+ source: Readonly>,
+ temporaryDirectory: string
+): Readonly> {
+ const home = source.HOME;
+ if (!home) throw new Error("HOME is required for resource-budget qualification");
+ return Object.freeze({
+ CI: "1",
+ FORCE_COLOR: "0",
+ HOME: home,
+ LANG: "C.UTF-8",
+ NODE_ENV: "test",
+ NO_COLOR: "1",
+ PATH: "/usr/local/bin:/usr/bin:/bin",
+ TMPDIR: temporaryDirectory,
+ });
+}
+
+function childEnvironmentArguments(
+ environment: Readonly>
+): string[] {
+ return Object.entries(environment)
+ .toSorted(([left], [right]) => left.localeCompare(right))
+ .map(([name, value]) => `${name}=${value}`);
+}
+
+/**
+ * Builds the exact argv-only command executed by the capped wrapper.
+ * @param scenarioId Reviewed workload identity.
+ * @param repositoryRoot Absolute repository root.
+ * @param bunExecutable Absolute Bun executable.
+ * @param environment Sanitized workload environment.
+ * @returns Immutable argv-only workload command.
+ */
+export function buildResourceBudgetWorkloadCommand(
+ scenarioId: ResourceBudgetScenarioId,
+ repositoryRoot: string,
+ bunExecutable: string,
+ environment: Readonly>
+): ResourceBudgetWorkloadCommand {
+ assertAbsolutePath("Repository root", repositoryRoot);
+ assertAbsolutePath("Bun executable", bunExecutable);
+ const qualification = (...segments: string[]) =>
+ path.join(repositoryRoot, "qualification", ...segments);
+ const testFiles = [
+ qualification("runtimeCandidate.test.ts"),
+ qualification("resources", "cgroupV2.test.ts"),
+ qualification("build", "frontendBuildQualification.test.ts"),
+ qualification("browser", "queryCollectionAdapter.test.ts"),
+ qualification("openclaw", "sourceAudit.test.ts"),
+ qualification("chat", "chatBatching.test.ts"),
+ ];
+ const scenarioArguments: Record = {
+ "chat-batching": [qualification("chat", "runChatBatchingQualification.ts")],
+ "child-cancellation": [
+ qualification("budgets", "runSafeChildCancellationEvidence.ts"),
+ ],
+ "complete-shutdown": [
+ qualification("shutdown", "runCompleteShutdownEvidence.ts"),
+ ],
+ "frontend-build": [qualification("build", "runFrontendBuildQualification.ts")],
+ "representative-tests": ["test", ...testFiles],
+ "sqlite-outbox": [qualification("outbox", "runSqliteOutboxEvidence.ts"), "1"],
+ };
+ return Object.freeze({
+ argv: Object.freeze([bunExecutable, ...scenarioArguments[scenarioId]]),
+ environment,
+ });
+}
+
+/**
+ * Builds one transient user-systemd command with explicit cgroup v2 limits.
+ * @param options Resolved paths, unit identity, and sanitized host environment.
+ * @returns Immutable launcher command.
+ */
+export function buildResourceBudgetLauncherCommand(
+ options: ResourceBudgetCommandOptions
+): ResourceBudgetLauncherCommand {
+ for (const [label, value] of [
+ ["Bun executable", options.bunExecutable],
+ ["Child entrypoint", options.childEntrypoint],
+ ["env executable", options.envExecutable],
+ ["Repository root", options.repositoryRoot],
+ ["Result path", options.resultPath],
+ ["systemctl executable", options.systemctlExecutable],
+ ["systemd-run executable", options.systemdRunExecutable],
+ ["Temporary directory", options.temporaryDirectory],
+ ] as const) {
+ assertAbsolutePath(label, value);
+ }
+ assertResourceBudgetUnitName(options.unitName);
+
+ const launcherEnvironment = Object.freeze(
+ Object.fromEntries(
+ launcherEnvironmentNames.flatMap((name) => {
+ const value = options.environment[name];
+ return value === undefined ? [] : [[name, value]];
+ })
+ )
+ );
+ const workloadEnvironment = sanitizedEnvironment(
+ options.environment,
+ options.temporaryDirectory
+ );
+ const limits = resourceBudgetPolicy.scenarios[options.scenarioId].limits;
+ const argv = Object.freeze([
+ options.systemdRunExecutable,
+ "--user",
+ "--wait",
+ "--pipe",
+ "--collect",
+ "--quiet",
+ `--unit=${options.unitName}`,
+ "--slice=app.slice",
+ "--expand-environment=no",
+ "--nice=10",
+ `--working-directory=${options.repositoryRoot}`,
+ "--property=MemoryAccounting=yes",
+ "--property=CPUAccounting=yes",
+ "--property=TasksAccounting=yes",
+ `--property=MemoryHigh=${limits.memoryHighBytes}`,
+ `--property=MemoryMax=${limits.memoryMaxBytes}`,
+ `--property=MemorySwapMax=${limits.memorySwapMaxBytes}`,
+ `--property=TasksMax=${limits.tasksMax}`,
+ `--property=CPUQuota=${limits.cpuQuotaPercent}%`,
+ `--property=RuntimeMaxSec=${limits.runtimeMaxSeconds}s`,
+ "--property=OOMPolicy=kill",
+ "--property=KillMode=control-group",
+ "--property=TimeoutStopSec=5s",
+ options.envExecutable,
+ "-i",
+ ...childEnvironmentArguments(workloadEnvironment),
+ options.bunExecutable,
+ options.childEntrypoint,
+ `--repository=${options.repositoryRoot}`,
+ `--result=${options.resultPath}`,
+ `--scenario=${options.scenarioId}`,
+ `--unit=${options.unitName}`,
+ ]);
+
+ return Object.freeze({
+ argv,
+ environment: launcherEnvironment,
+ resultPath: options.resultPath,
+ scenarioId: options.scenarioId,
+ systemctlExecutable: options.systemctlExecutable,
+ unitName: options.unitName,
+ });
+}
diff --git a/qualification/budgets/resourceBudgetOrchestration.test.ts b/qualification/budgets/resourceBudgetOrchestration.test.ts
new file mode 100644
index 000000000..46f41d6d0
--- /dev/null
+++ b/qualification/budgets/resourceBudgetOrchestration.test.ts
@@ -0,0 +1,39 @@
+import { describe, expect, test } from "bun:test";
+
+import { classifyResourceBudgetUnitCollection } from "./resourceBudgetOrchestration.ts";
+
+describe("resource-budget orchestration", () => {
+ test("accepts only an explicit not-found load state as collected", () => {
+ expect(
+ classifyResourceBudgetUnitCollection({
+ exitCode: 0,
+ stderr: "",
+ stdout: "LoadState=not-found\n",
+ })
+ ).toEqual({ state: "collected" });
+ expect(
+ classifyResourceBudgetUnitCollection({
+ exitCode: 0,
+ stderr: "",
+ stdout: "LoadState=loaded\n",
+ })
+ ).toEqual({ state: "pending" });
+ });
+
+ test("surfaces systemctl failures instead of treating them as cleanup", () => {
+ const result = classifyResourceBudgetUnitCollection({
+ exitCode: 1,
+ stderr: "Failed to connect to bus",
+ stdout: "",
+ });
+
+ expect(result.state).toBe("failed");
+ if (result.state !== "failed") throw new Error("Expected failed inspection");
+ expect(result.error._tag).toBe("ResourceBudgetOrchestrationError");
+ expect(result.error.operation).toBe("inspect-unit-collection");
+ expect(result.error.cause).toEqual({
+ exitCode: 1,
+ stderr: "Failed to connect to bus",
+ });
+ });
+});
diff --git a/qualification/budgets/resourceBudgetOrchestration.ts b/qualification/budgets/resourceBudgetOrchestration.ts
new file mode 100644
index 000000000..0a593d0c5
--- /dev/null
+++ b/qualification/budgets/resourceBudgetOrchestration.ts
@@ -0,0 +1,497 @@
+import { mkdir, mkdtemp, rm, stat } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import path from "node:path";
+
+import { Data, Effect, Schedule, Scope } from "effect";
+
+import {
+ buildResourceBudgetLauncherCommand,
+ type ResourceBudgetLauncherCommand,
+} from "./resourceBudgetCommand.ts";
+import {
+ assessResourceBudgetEvidence,
+ createResourceBudgetUnitName,
+ expectedResourceBudgetCgroupPath,
+ parseResourceBudgetUnitReport,
+ resourceBudgetPolicy,
+ resourceBudgetScenarioIds,
+ type ResourceBudgetAssessment,
+ type ResourceBudgetScenarioEvidence,
+ type ResourceBudgetScenarioId,
+} from "./resourceBudgetPolicy.ts";
+
+type ManagedProcess = Bun.Subprocess<"ignore", "pipe", "pipe">;
+const unitCollectionSchedule = Schedule.spaced("20 millis").pipe(
+ Schedule.upTo({ times: 100 })
+);
+
+export class ResourceBudgetOrchestrationError extends Data.TaggedError(
+ "ResourceBudgetOrchestrationError"
+)<{
+ readonly cause?: unknown;
+ readonly operation: string;
+ readonly scenarioId?: ResourceBudgetScenarioId;
+}> {}
+
+export class ResourceBudgetOrchestrationDeadlineError extends Data.TaggedError(
+ "ResourceBudgetOrchestrationDeadlineError"
+)<{
+ readonly operation: string;
+ readonly scenarioId: ResourceBudgetScenarioId;
+}> {}
+
+class ResourceBudgetUnitPendingError extends Data.TaggedError(
+ "ResourceBudgetUnitPendingError"
+)<{
+ readonly operation: string;
+}> {}
+
+interface ProcessResult {
+ readonly exitCode: number;
+ readonly stderr: string;
+ readonly stdout: string;
+}
+
+export type ResourceBudgetUnitCollectionInspection =
+ | Readonly<{ readonly state: "collected" }>
+ | Readonly<{ readonly state: "pending" }>
+ | Readonly<{
+ readonly error: ResourceBudgetOrchestrationError;
+ readonly state: "failed";
+ }>;
+
+interface ResourceBudgetExecutables {
+ readonly bun: string;
+ readonly env: string;
+ readonly systemctl: string;
+ readonly systemdRun: string;
+}
+
+export interface ResourceBudgetQualificationReport {
+ readonly assessments: readonly Readonly[];
+ readonly bunRevision: string;
+ readonly bunVersion: string;
+ readonly ciInvariants: readonly string[];
+ readonly hostMeasurementsAreTimingGates: false;
+ readonly scenarioEvidence: readonly ResourceBudgetScenarioEvidence[];
+}
+
+function requiredExecutable(name: string): string {
+ const executable = Bun.which(name);
+ if (executable === null || !path.isAbsolute(executable)) {
+ throw new Error(`${name} is required for resource-budget qualification`);
+ }
+ return executable;
+}
+
+function temporaryWorkspace() {
+ return Effect.acquireRelease(
+ Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetOrchestrationError({
+ cause,
+ operation: "create-temporary-workspace",
+ }),
+ try: () => mkdtemp(path.join(tmpdir(), "mira-resource-budget-")),
+ }),
+ (directory) =>
+ Effect.tryPromise(() => rm(directory, { force: true, recursive: true })).pipe(
+ Effect.orDie
+ )
+ );
+}
+
+function awaitProcessExit(process_: ManagedProcess, operation: string) {
+ return Effect.tryPromise({
+ catch: (cause) => new ResourceBudgetOrchestrationError({ cause, operation }),
+ try: () => process_.exited,
+ });
+}
+
+function stopProcess(process_: ManagedProcess): Effect.Effect {
+ if (process_.exitCode !== null || process_.signalCode !== null) return Effect.void;
+ return Effect.sync(() => process_.kill("SIGTERM")).pipe(
+ Effect.andThen(awaitProcessExit(process_, "stop-subprocess")),
+ Effect.timeoutOrElse({
+ duration: "2 seconds",
+ orElse: () =>
+ Effect.sync(() => process_.kill("SIGKILL")).pipe(
+ Effect.andThen(awaitProcessExit(process_, "kill-subprocess"))
+ ),
+ }),
+ Effect.asVoid,
+ Effect.orDie
+ );
+}
+
+function processResource(
+ argv: readonly string[],
+ environment: Readonly>,
+ maximumOutputBytes: number,
+ operation: string
+): Effect.Effect {
+ return Effect.gen(function* () {
+ const signal = yield* Effect.abortSignal;
+ return yield* Effect.acquireRelease(
+ Effect.try({
+ catch: (cause) =>
+ new ResourceBudgetOrchestrationError({ cause, operation }),
+ try: () =>
+ Bun.spawn([...argv], {
+ env: environment,
+ killSignal: "SIGTERM",
+ maxBuffer: maximumOutputBytes,
+ signal,
+ stderr: "pipe",
+ stdin: "ignore",
+ stdout: "pipe",
+ }),
+ }),
+ stopProcess
+ );
+ });
+}
+
+function captureOutput(stream: ReadableStream, operation: string) {
+ return Effect.tryPromise({
+ catch: (cause) => new ResourceBudgetOrchestrationError({ cause, operation }),
+ try: () => new Response(stream).text(),
+ });
+}
+
+function runBoundedProcess(
+ argv: readonly string[],
+ environment: Readonly>,
+ maximumOutputBytes: number,
+ deadlineMs: number,
+ operation: string,
+ scenarioId: ResourceBudgetScenarioId
+): Effect.Effect<
+ ProcessResult,
+ ResourceBudgetOrchestrationError | ResourceBudgetOrchestrationDeadlineError
+> {
+ return Effect.scoped(
+ Effect.gen(function* () {
+ const process_ = yield* processResource(
+ argv,
+ environment,
+ maximumOutputBytes,
+ operation
+ );
+ const [exitCode, stderr, stdout] = yield* Effect.all(
+ [
+ awaitProcessExit(process_, operation),
+ captureOutput(process_.stderr, `${operation}:stderr`),
+ captureOutput(process_.stdout, `${operation}:stdout`),
+ ] as const,
+ { concurrency: "unbounded" }
+ ).pipe(
+ Effect.timeoutOrElse({
+ duration: deadlineMs,
+ orElse: () =>
+ Effect.fail(
+ new ResourceBudgetOrchestrationDeadlineError({
+ operation,
+ scenarioId,
+ })
+ ),
+ })
+ );
+ return { exitCode, stderr, stdout };
+ })
+ );
+}
+
+function systemctl(
+ command: ResourceBudgetLauncherCommand,
+ arguments_: readonly string[],
+ operation: string
+) {
+ return runBoundedProcess(
+ [
+ command.systemctlExecutable,
+ "--user",
+ "--no-ask-password",
+ "--no-pager",
+ ...arguments_,
+ ],
+ command.environment,
+ 16 * 1024,
+ 3000,
+ operation,
+ command.scenarioId
+ );
+}
+
+/**
+ * Classifies one bounded `systemctl show` result without hiding transport failures.
+ * @param result Captured systemctl process result.
+ * @returns Explicit collected, pending, or failed inspection state.
+ */
+export function classifyResourceBudgetUnitCollection(
+ result: Readonly
+): ResourceBudgetUnitCollectionInspection {
+ if (result.exitCode !== 0) {
+ return {
+ error: new ResourceBudgetOrchestrationError({
+ cause: {
+ exitCode: result.exitCode,
+ stderr: result.stderr.trim(),
+ },
+ operation: "inspect-unit-collection",
+ }),
+ state: "failed",
+ };
+ }
+ return result.stdout.trim() === "LoadState=not-found"
+ ? { state: "collected" }
+ : { state: "pending" };
+}
+
+function unitIsCollected(command: ResourceBudgetLauncherCommand) {
+ return systemctl(
+ command,
+ ["show", `${command.unitName}.service`, "--property=LoadState"],
+ "inspect-unit-collection"
+ ).pipe(
+ Effect.flatMap(
+ (
+ result
+ ): Effect.Effect<
+ boolean,
+ ResourceBudgetOrchestrationError | ResourceBudgetUnitPendingError
+ > => {
+ const inspection = classifyResourceBudgetUnitCollection(result);
+ switch (inspection.state) {
+ case "collected": {
+ return Effect.succeed(true);
+ }
+ case "failed": {
+ return Effect.fail(inspection.error);
+ }
+ case "pending": {
+ return Effect.fail(
+ new ResourceBudgetUnitPendingError({
+ operation: "await-unit-collection",
+ })
+ );
+ }
+ }
+ }
+ ),
+ Effect.retry({ schedule: unitCollectionSchedule }),
+ Effect.catchTag("ResourceBudgetUnitPendingError", () => Effect.succeed(false))
+ );
+}
+
+function cgroupIsRemoved(cgroupPath: string) {
+ const filesystemPath = path.join("/sys/fs/cgroup", `.${cgroupPath}`);
+ const attempt = Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetOrchestrationError({
+ cause,
+ operation: "inspect-cgroup-removal",
+ }),
+ try: async () => {
+ try {
+ await stat(filesystemPath);
+ return false;
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return true;
+ throw error;
+ }
+ },
+ }).pipe(
+ Effect.flatMap((removed) =>
+ removed
+ ? Effect.succeed(true)
+ : Effect.fail(
+ new ResourceBudgetUnitPendingError({
+ operation: "await-cgroup-removal",
+ })
+ )
+ )
+ );
+ return attempt.pipe(
+ Effect.retry({ schedule: unitCollectionSchedule }),
+ Effect.catchTag("ResourceBudgetUnitPendingError", () => Effect.succeed(false))
+ );
+}
+
+function cleanupTransientUnit(command: ResourceBudgetLauncherCommand) {
+ const unit = `${command.unitName}.service`;
+ return Effect.gen(function* () {
+ const stopped = yield* systemctl(command, ["stop", unit], "stop-unit");
+ if (stopped.exitCode !== 0) {
+ yield* systemctl(
+ command,
+ ["kill", "--kill-whom=all", "--signal=SIGKILL", unit],
+ "kill-unit"
+ );
+ yield* systemctl(command, ["stop", unit], "stop-killed-unit");
+ }
+ yield* systemctl(command, ["reset-failed", unit], "reset-failed-unit");
+ const collected = yield* unitIsCollected(command);
+ if (!collected) {
+ return yield* Effect.fail(
+ new ResourceBudgetOrchestrationError({
+ operation: "collect-transient-unit",
+ scenarioId: command.scenarioId,
+ })
+ );
+ }
+ }).pipe(Effect.orDie);
+}
+
+function transientUnitResource(command: ResourceBudgetLauncherCommand) {
+ return Effect.acquireRelease(Effect.succeed(command), cleanupTransientUnit);
+}
+
+function readResult(command: ResourceBudgetLauncherCommand) {
+ return Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetOrchestrationError({
+ cause,
+ operation: "read-unit-report",
+ scenarioId: command.scenarioId,
+ }),
+ try: async () => {
+ const resultFile = Bun.file(command.resultPath);
+ if (!(await resultFile.exists())) {
+ throw new Error("Resource-budget unit did not write a report");
+ }
+ if (resultFile.size > resourceBudgetPolicy.resultMaxBytes) {
+ throw new Error("Resource-budget unit report exceeds its bound");
+ }
+ return parseResourceBudgetUnitReport(await resultFile.text());
+ },
+ });
+}
+
+function runScenario(
+ scenarioId: ResourceBudgetScenarioId,
+ workspace: string,
+ executables: ResourceBudgetExecutables,
+ userId: number
+) {
+ const unitName = createResourceBudgetUnitName(scenarioId);
+ const scenarioDirectory = path.join(workspace, scenarioId);
+ const temporaryDirectory = path.join(scenarioDirectory, "tmp");
+ const resultPath = path.join(scenarioDirectory, "result.json");
+ const command = buildResourceBudgetLauncherCommand({
+ bunExecutable: executables.bun,
+ childEntrypoint: path.join(import.meta.dir, "resourceBudgetUnit.ts"),
+ envExecutable: executables.env,
+ environment: process.env,
+ repositoryRoot: path.resolve(import.meta.dir, "../.."),
+ resultPath,
+ scenarioId,
+ systemctlExecutable: executables.systemctl,
+ systemdRunExecutable: executables.systemdRun,
+ temporaryDirectory,
+ unitName,
+ });
+ const cgroupPath = expectedResourceBudgetCgroupPath(userId, unitName);
+
+ return Effect.gen(function* () {
+ yield* Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetOrchestrationError({
+ cause,
+ operation: "create-scenario-directory",
+ scenarioId,
+ }),
+ try: () => mkdir(temporaryDirectory, { recursive: true }),
+ });
+ const completed = yield* Effect.scoped(
+ Effect.gen(function* () {
+ yield* transientUnitResource(command);
+ const limits = resourceBudgetPolicy.scenarios[scenarioId].limits;
+ const launcher = yield* runBoundedProcess(
+ command.argv,
+ command.environment,
+ resourceBudgetPolicy.launcherOutputMaxBytes,
+ limits.outerDeadlineMs,
+ "run-transient-unit",
+ scenarioId
+ );
+ const report = yield* readResult(command);
+ return { launcher, report };
+ })
+ );
+ const [unitCollected, cgroupRemoved] = yield* Effect.all(
+ [unitIsCollected(command), cgroupIsRemoved(cgroupPath)] as const,
+ { concurrency: "unbounded" }
+ );
+ const evidence: ResourceBudgetScenarioEvidence = {
+ cgroupRemoved,
+ launcherExitCode: completed.launcher.exitCode,
+ report: completed.report,
+ unitCollected,
+ };
+ if (completed.launcher.exitCode !== 0) {
+ const diagnostic = [
+ completed.launcher.stderr.trim(),
+ completed.launcher.stdout.trim(),
+ ]
+ .filter((value) => value.length > 0)
+ .join("\n")
+ .slice(0, 16 * 1024);
+ return yield* Effect.fail(
+ new ResourceBudgetOrchestrationError({
+ cause: diagnostic,
+ operation: "transient-unit-exit",
+ scenarioId,
+ })
+ );
+ }
+ return evidence;
+ });
+}
+
+/** Executes all representative workloads sequentially under reviewed cgroup limits. */
+export const resourceBudgetQualification: Effect.Effect<
+ ResourceBudgetQualificationReport,
+ ResourceBudgetOrchestrationError | ResourceBudgetOrchestrationDeadlineError
+> = Effect.scoped(
+ Effect.gen(function* () {
+ if (process.getuid === undefined) {
+ return yield* Effect.fail(
+ new ResourceBudgetOrchestrationError({
+ operation: "read-current-user-id",
+ })
+ );
+ }
+ const workspace = yield* temporaryWorkspace();
+ const executables: ResourceBudgetExecutables = {
+ bun: process.execPath,
+ env: requiredExecutable("env"),
+ systemctl: requiredExecutable("systemctl"),
+ systemdRun: requiredExecutable("systemd-run"),
+ };
+ const userId = process.getuid();
+ const scenarioEvidence = yield* Effect.forEach(
+ resourceBudgetScenarioIds,
+ (scenarioId) => runScenario(scenarioId, workspace, executables, userId),
+ { concurrency: 1 }
+ );
+ const assessments = scenarioEvidence.map((evidence) =>
+ assessResourceBudgetEvidence(evidence, userId)
+ );
+ return Object.freeze({
+ assessments: Object.freeze(assessments),
+ bunRevision: Bun.revision,
+ bunVersion: Bun.version,
+ ciInvariants: Object.freeze([
+ "exact transient-unit controller limits",
+ "zero OOM/high/max events",
+ "zero workload exit status and no signal",
+ "memory peak below memory.high",
+ "no child process or transient-unit leakage",
+ "bounded output and Effect-owned deadlines",
+ ]),
+ hostMeasurementsAreTimingGates: false as const,
+ scenarioEvidence: Object.freeze(scenarioEvidence),
+ });
+ })
+);
diff --git a/qualification/budgets/resourceBudgetPolicy.test.ts b/qualification/budgets/resourceBudgetPolicy.test.ts
new file mode 100644
index 000000000..a0aabf696
--- /dev/null
+++ b/qualification/budgets/resourceBudgetPolicy.test.ts
@@ -0,0 +1,242 @@
+import { describe, expect, test } from "bun:test";
+
+import {
+ buildResourceBudgetLauncherCommand,
+ buildResourceBudgetWorkloadCommand,
+} from "./resourceBudgetCommand.ts";
+import {
+ assessResourceBudgetEvidence,
+ createResourceBudgetUnitName,
+ expectedResourceBudgetCgroupPath,
+ parseResourceBudgetUnitReport,
+ resourceBudgetPolicy,
+ resourceBudgetScenarioIds,
+ type ResourceBudgetScenarioEvidence,
+} from "./resourceBudgetPolicy.ts";
+
+const userId = 1001;
+const wrapperProcessId = 4242;
+const unitName = createResourceBudgetUnitName(
+ "chat-batching",
+ "00000000-0000-4000-8000-000000000001"
+);
+
+function validEvidence() {
+ const events = {
+ high: 0,
+ low: 0,
+ max: 0,
+ oom: 0,
+ oomGroupKill: 0,
+ oomKill: 0,
+ };
+ return {
+ cgroupRemoved: true as boolean,
+ launcherExitCode: 0,
+ report: {
+ cgroup: {
+ final: {
+ cpuNrThrottled: 2,
+ cpuPressure: { fullTotalMicros: 1, someTotalMicros: 8 },
+ cpuThrottledMicros: 20,
+ cpuUsageMicros: 80_000,
+ memoryCurrentBytes: 32 * 1024 * 1024,
+ memoryEvents: { ...events },
+ memoryPressure: { fullTotalMicros: 1, someTotalMicros: 6 },
+ pidsCurrent: 1,
+ },
+ finalProcessIds: [wrapperProcessId],
+ initial: {
+ cpuNrThrottled: 0,
+ cpuPressure: { fullTotalMicros: 0, someTotalMicros: 3 },
+ cpuThrottledMicros: 0,
+ cpuUsageMicros: 10_000,
+ memoryCurrentBytes: 24 * 1024 * 1024,
+ memoryEvents: { ...events },
+ memoryPressure: { fullTotalMicros: 0, someTotalMicros: 2 },
+ pidsCurrent: 1,
+ },
+ memoryPeakBytes: 64 * 1024 * 1024,
+ path: expectedResourceBudgetCgroupPath(userId, unitName),
+ pidsPeak: 8,
+ },
+ formatVersion: 1,
+ limits: {
+ cpuPeriodMicros: 100_000,
+ cpuQuotaMicros: 100_000,
+ memoryHighBytes: 128 * 1024 * 1024,
+ memoryMaxBytes: 192 * 1024 * 1024,
+ memorySwapMaxBytes: 0,
+ oomGroup: true,
+ pidsMax: 64,
+ },
+ runtime: {
+ bunRevision: "0".repeat(40),
+ bunVersion: "1.4.0",
+ },
+ scenarioId: "chat-batching",
+ unitName,
+ workload: {
+ durationMs: 1200,
+ exitCode: 0,
+ signalCode: null,
+ stderrBytes: 0,
+ stdoutBytes: 2048,
+ },
+ wrapperProcessId,
+ },
+ unitCollected: true as boolean,
+ } satisfies ResourceBudgetScenarioEvidence;
+}
+
+describe("resource-budget policy", () => {
+ test("freezes one reviewed explicit limit profile for every representative workload", () => {
+ expect(Object.keys(resourceBudgetPolicy.scenarios).toSorted()).toEqual(
+ [...resourceBudgetScenarioIds].toSorted()
+ );
+ expect(Object.isFrozen(resourceBudgetPolicy)).toBeTrue();
+ expect(Object.isFrozen(resourceBudgetPolicy.scenarios)).toBeTrue();
+ for (const scenario of Object.values(resourceBudgetPolicy.scenarios)) {
+ expect(Object.isFrozen(scenario)).toBeTrue();
+ expect(Object.isFrozen(scenario.limits)).toBeTrue();
+ expect(scenario.limits.memoryHighBytes).toBeLessThan(
+ scenario.limits.memoryMaxBytes
+ );
+ expect(scenario.limits.workloadDeadlineMs).toBeLessThan(
+ scenario.limits.runtimeMaxSeconds * 1000
+ );
+ expect(scenario.limits.runtimeMaxSeconds * 1000).toBeLessThan(
+ scenario.limits.outerDeadlineMs
+ );
+ expect(scenario.limits.memorySwapMaxBytes).toBe(0);
+ }
+ });
+
+ test("builds an argv-only transient unit with no inherited application secrets", () => {
+ const command = buildResourceBudgetLauncherCommand({
+ bunExecutable: "/home/test/.bun/bin/bun",
+ childEntrypoint: "/repo/qualification/budgets/resourceBudgetUnit.ts",
+ envExecutable: "/usr/bin/env",
+ environment: {
+ DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/1001/bus",
+ HOME: "/home/test",
+ MIRA_SECRET: "must-not-leak",
+ OPENCLAW_GATEWAY_TOKEN: "must-not-leak",
+ PATH: "/untrusted/bin",
+ PUBLIC_AMBIENT: "must-not-leak",
+ XDG_RUNTIME_DIR: "/run/user/1001",
+ },
+ repositoryRoot: "/repo",
+ resultPath: "/tmp/result.json",
+ scenarioId: "chat-batching",
+ systemctlExecutable: "/usr/bin/systemctl",
+ systemdRunExecutable: "/usr/bin/systemd-run",
+ temporaryDirectory: "/tmp/workload",
+ unitName,
+ });
+
+ expect(command.argv).toContain("--collect");
+ expect(command.argv).toContain("--property=MemoryHigh=134217728");
+ expect(command.argv).toContain("--property=MemoryMax=201326592");
+ expect(command.argv).toContain("--property=MemorySwapMax=0");
+ expect(command.argv).toContain("--property=TasksMax=64");
+ expect(command.argv).toContain("--property=CPUQuota=100%");
+ expect(command.argv).toContain("--property=OOMPolicy=kill");
+ expect(command.argv).toContain("-i");
+ expect(command.argv.join(" ")).not.toContain("must-not-leak");
+ expect(command.environment).toEqual({
+ DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/1001/bus",
+ HOME: "/home/test",
+ PATH: "/untrusted/bin",
+ XDG_RUNTIME_DIR: "/run/user/1001",
+ });
+ });
+
+ test("maps every scenario to a bounded first-party qualification command", () => {
+ const environment = Object.freeze({ HOME: "/home/test" });
+ for (const scenarioId of resourceBudgetScenarioIds) {
+ const command = buildResourceBudgetWorkloadCommand(
+ scenarioId,
+ "/repo",
+ "/home/test/.bun/bin/bun",
+ environment
+ );
+ expect(command.argv[0]).toBe("/home/test/.bun/bin/bun");
+ expect(command.argv.join(" ")).toContain("/repo/qualification/");
+ expect(command.environment).toBe(environment);
+ }
+ });
+
+ test("accepts bounded evidence and returns host measurements without timing gates", () => {
+ const evidence = validEvidence();
+ expect(parseResourceBudgetUnitReport(JSON.stringify(evidence.report))).toEqual(
+ evidence.report
+ );
+ expect(assessResourceBudgetEvidence(evidence, userId)).toEqual({
+ cpuPressureMicros: 5,
+ cpuThrottledMicros: 20,
+ cpuUsageMicros: 70_000,
+ durationMs: 1200,
+ memoryHeadroomBytes: 64 * 1024 * 1024,
+ memoryPeakBytes: 64 * 1024 * 1024,
+ memoryPressureMicros: 4,
+ pidsPeak: 8,
+ scenarioId: "chat-batching",
+ });
+ });
+
+ test("rejects pressure events, cap crossings, failures, and leaked resources", () => {
+ const cases: Array<
+ [string, (candidate: ReturnType) => void]
+ > = [
+ [
+ "memory.events oomKill",
+ (candidate) => {
+ candidate.report.cgroup.final.memoryEvents.oomKill = 1;
+ },
+ ],
+ [
+ "crossed memory.high",
+ (candidate) => {
+ candidate.report.cgroup.memoryPeakBytes = 128 * 1024 * 1024;
+ },
+ ],
+ [
+ "workload did not exit zero",
+ (candidate) => {
+ candidate.report.workload.exitCode = 1;
+ },
+ ],
+ [
+ "leaked processes",
+ (candidate) => {
+ candidate.report.cgroup.finalProcessIds.push(9999);
+ },
+ ],
+ [
+ "unit was not collected",
+ (candidate) => {
+ candidate.cgroupRemoved = false;
+ },
+ ],
+ ];
+ for (const [message, mutate] of cases) {
+ const candidate = structuredClone(validEvidence());
+ mutate(candidate);
+ expect(() => assessResourceBudgetEvidence(candidate, userId)).toThrow(
+ message
+ );
+ }
+ });
+
+ test("rejects malformed reports and attacker-controlled unit names", () => {
+ expect(() =>
+ parseResourceBudgetUnitReport(
+ JSON.stringify({ ...validEvidence().report, unexpected: true })
+ )
+ ).toThrow();
+ expect(() =>
+ createResourceBudgetUnitName("chat-batching", "../attacker")
+ ).toThrow("unit identifier");
+ });
+});
diff --git a/qualification/budgets/resourceBudgetPolicy.ts b/qualification/budgets/resourceBudgetPolicy.ts
new file mode 100644
index 000000000..8dd7093ce
--- /dev/null
+++ b/qualification/budgets/resourceBudgetPolicy.ts
@@ -0,0 +1,404 @@
+import path from "node:path";
+
+import * as v from "valibot";
+
+const mebibyte = 1024 * 1024;
+const resourceBudgetUnitIdentifierPattern =
+ /^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/iu;
+const resourceBudgetUnitNamePattern =
+ /^mira-dashboard-resource-(?:frontend-build|representative-tests|sqlite-outbox|chat-batching|complete-shutdown|child-cancellation)-[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/iu;
+
+export const resourceBudgetScenarioIds = [
+ "frontend-build",
+ "representative-tests",
+ "sqlite-outbox",
+ "chat-batching",
+ "complete-shutdown",
+ "child-cancellation",
+] as const;
+
+export type ResourceBudgetScenarioId = (typeof resourceBudgetScenarioIds)[number];
+
+export interface ResourceBudgetLimits {
+ readonly cpuQuotaPercent: number;
+ readonly memoryHighBytes: number;
+ readonly memoryMaxBytes: number;
+ readonly memorySwapMaxBytes: number;
+ readonly outerDeadlineMs: number;
+ readonly runtimeMaxSeconds: number;
+ readonly tasksMax: number;
+ readonly workloadDeadlineMs: number;
+}
+
+export interface ResourceBudgetScenarioPolicy {
+ readonly description: string;
+ readonly limits: Readonly;
+}
+
+const sharedSmallWorkloadLimits = Object.freeze({
+ cpuQuotaPercent: 100,
+ memorySwapMaxBytes: 0,
+ outerDeadlineMs: 75_000,
+ runtimeMaxSeconds: 60,
+ tasksMax: 64,
+ workloadDeadlineMs: 50_000,
+});
+
+function frozenScenario(
+ description: string,
+ limits: ResourceBudgetLimits
+): Readonly {
+ return Object.freeze({ description, limits: Object.freeze(limits) });
+}
+
+const resourceBudgetScenarios = Object.freeze({
+ "chat-batching": frozenScenario("OpenClaw-shaped deterministic chat batching", {
+ ...sharedSmallWorkloadLimits,
+ memoryHighBytes: 128 * mebibyte,
+ memoryMaxBytes: 192 * mebibyte,
+ }),
+ "child-cancellation": frozenScenario(
+ "Effect interruption and detached process-group cleanup",
+ {
+ ...sharedSmallWorkloadLimits,
+ memoryHighBytes: 192 * mebibyte,
+ memoryMaxBytes: 256 * mebibyte,
+ }
+ ),
+ "complete-shutdown": frozenScenario(
+ "Two-generation complete shutdown and WAL recovery",
+ {
+ ...sharedSmallWorkloadLimits,
+ memoryHighBytes: 256 * mebibyte,
+ memoryMaxBytes: 384 * mebibyte,
+ }
+ ),
+ "frontend-build": frozenScenario(
+ "Production frontend build with hashes and compression",
+ {
+ cpuQuotaPercent: 200,
+ memoryHighBytes: 768 * mebibyte,
+ memoryMaxBytes: 1024 * mebibyte,
+ memorySwapMaxBytes: 0,
+ outerDeadlineMs: 195_000,
+ runtimeMaxSeconds: 180,
+ tasksMax: 96,
+ workloadDeadlineMs: 165_000,
+ }
+ ),
+ "representative-tests": frozenScenario(
+ "Bounded representative Phase 0 qualification tests",
+ {
+ cpuQuotaPercent: 200,
+ memoryHighBytes: 768 * mebibyte,
+ memoryMaxBytes: 1024 * mebibyte,
+ memorySwapMaxBytes: 0,
+ outerDeadlineMs: 195_000,
+ runtimeMaxSeconds: 180,
+ tasksMax: 96,
+ workloadDeadlineMs: 165_000,
+ }
+ ),
+ "sqlite-outbox": frozenScenario(
+ "Multi-process SQLite outbox, crash recovery, and restore",
+ {
+ ...sharedSmallWorkloadLimits,
+ memoryHighBytes: 256 * mebibyte,
+ memoryMaxBytes: 384 * mebibyte,
+ }
+ ),
+} satisfies Record);
+
+/** Reviewed host-measurement caps. CI validates policy mechanics without timing gates. */
+export const resourceBudgetPolicy = Object.freeze({
+ childOutputMaxBytes: 512 * 1024,
+ formatVersion: 1 as const,
+ launcherOutputMaxBytes: 64 * 1024,
+ resultMaxBytes: 64 * 1024,
+ scenarios: resourceBudgetScenarios,
+});
+
+const nonnegativeIntegerSchema = v.pipe(v.number(), v.safeInteger(), v.minValue(0));
+const positiveIntegerSchema = v.pipe(v.number(), v.safeInteger(), v.minValue(1));
+const memoryEventsSchema = v.strictObject({
+ high: nonnegativeIntegerSchema,
+ low: nonnegativeIntegerSchema,
+ max: nonnegativeIntegerSchema,
+ oom: nonnegativeIntegerSchema,
+ oomGroupKill: nonnegativeIntegerSchema,
+ oomKill: nonnegativeIntegerSchema,
+});
+const pressureSchema = v.strictObject({
+ fullTotalMicros: nonnegativeIntegerSchema,
+ someTotalMicros: nonnegativeIntegerSchema,
+});
+const resourceSnapshotSchema = v.strictObject({
+ cpuNrThrottled: nonnegativeIntegerSchema,
+ cpuPressure: pressureSchema,
+ cpuThrottledMicros: nonnegativeIntegerSchema,
+ cpuUsageMicros: nonnegativeIntegerSchema,
+ memoryCurrentBytes: nonnegativeIntegerSchema,
+ memoryEvents: memoryEventsSchema,
+ memoryPressure: pressureSchema,
+ pidsCurrent: nonnegativeIntegerSchema,
+});
+const resourceBudgetScenarioSchema = v.picklist(resourceBudgetScenarioIds);
+const cgroupPathSchema = v.pipe(v.string(), v.startsWith("/"));
+const bunRevisionSchema = v.pipe(v.string(), v.regex(/^[\da-f]{40}$/u));
+const bunVersionSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(64));
+const signalCodeSchema = v.pipe(v.string(), v.maxLength(32));
+const cgroupReportSchema = v.strictObject({
+ final: resourceSnapshotSchema,
+ finalProcessIds: v.array(positiveIntegerSchema),
+ initial: resourceSnapshotSchema,
+ memoryPeakBytes: nonnegativeIntegerSchema,
+ path: cgroupPathSchema,
+ pidsPeak: nonnegativeIntegerSchema,
+});
+const observedLimitsSchema = v.strictObject({
+ cpuPeriodMicros: positiveIntegerSchema,
+ cpuQuotaMicros: positiveIntegerSchema,
+ memoryHighBytes: positiveIntegerSchema,
+ memoryMaxBytes: positiveIntegerSchema,
+ memorySwapMaxBytes: nonnegativeIntegerSchema,
+ oomGroup: v.literal(true),
+ pidsMax: positiveIntegerSchema,
+});
+const runtimeIdentitySchema = v.strictObject({
+ bunRevision: bunRevisionSchema,
+ bunVersion: bunVersionSchema,
+});
+const workloadResultSchema = v.strictObject({
+ durationMs: nonnegativeIntegerSchema,
+ exitCode: v.nullable(nonnegativeIntegerSchema),
+ signalCode: v.nullable(signalCodeSchema),
+ stderrBytes: nonnegativeIntegerSchema,
+ stdoutBytes: nonnegativeIntegerSchema,
+});
+
+export const resourceBudgetUnitReportSchema = v.strictObject({
+ cgroup: cgroupReportSchema,
+ formatVersion: v.literal(resourceBudgetPolicy.formatVersion),
+ limits: observedLimitsSchema,
+ runtime: runtimeIdentitySchema,
+ scenarioId: resourceBudgetScenarioSchema,
+ unitName: v.pipe(v.string(), v.regex(resourceBudgetUnitNamePattern)),
+ workload: workloadResultSchema,
+ wrapperProcessId: positiveIntegerSchema,
+});
+
+export type ResourceBudgetUnitReport = v.InferOutput<
+ typeof resourceBudgetUnitReportSchema
+>;
+
+export interface ResourceBudgetScenarioEvidence {
+ readonly cgroupRemoved: boolean;
+ readonly launcherExitCode: number;
+ readonly report: ResourceBudgetUnitReport;
+ readonly unitCollected: boolean;
+}
+
+export interface ResourceBudgetAssessment {
+ readonly cpuPressureMicros: number;
+ readonly cpuThrottledMicros: number;
+ readonly cpuUsageMicros: number;
+ readonly durationMs: number;
+ readonly memoryHeadroomBytes: number;
+ readonly memoryPeakBytes: number;
+ readonly memoryPressureMicros: number;
+ readonly pidsPeak: number;
+ readonly scenarioId: ResourceBudgetScenarioId;
+}
+
+/**
+ * Creates a unique, non-user-controlled transient service name.
+ * @param scenarioId Reviewed workload identity.
+ * @param identifier UUID-compatible random identifier.
+ * @returns Valid transient service name without the `.service` suffix.
+ */
+export function createResourceBudgetUnitName(
+ scenarioId: ResourceBudgetScenarioId,
+ identifier: string = crypto.randomUUID()
+): string {
+ if (!resourceBudgetUnitIdentifierPattern.test(identifier)) {
+ throw new TypeError("Resource-budget unit identifier is invalid");
+ }
+ return `mira-dashboard-resource-${scenarioId}-${identifier}`;
+}
+
+/**
+ * Rejects unit names outside the exact qualification grammar.
+ * @param unitName Candidate transient service name.
+ */
+export function assertResourceBudgetUnitName(unitName: string): void {
+ if (!resourceBudgetUnitNamePattern.test(unitName)) {
+ throw new TypeError("Resource-budget unit name is invalid");
+ }
+}
+
+/**
+ * Returns the exact app.slice cgroup created by the user systemd manager.
+ * @param userId POSIX user-manager identity.
+ * @param unitName Validated transient service name.
+ * @returns Absolute unified cgroup path.
+ */
+export function expectedResourceBudgetCgroupPath(
+ userId: number,
+ unitName: string
+): string {
+ assertResourceBudgetUnitName(unitName);
+ if (!Number.isSafeInteger(userId) || userId < 0) {
+ throw new TypeError("Resource-budget user ID is invalid");
+ }
+ return path.posix.join(
+ "/user.slice",
+ `user-${userId}.slice`,
+ `user@${userId}.service`,
+ "app.slice",
+ `${unitName}.service`
+ );
+}
+
+/**
+ * Parses the bounded unit report written by the capped child.
+ * @param value Raw JSON report.
+ * @returns Strict validated unit report.
+ */
+export function parseResourceBudgetUnitReport(value: string): ResourceBudgetUnitReport {
+ let candidate: unknown;
+ try {
+ candidate = JSON.parse(value);
+ } catch (error) {
+ throw new Error("Resource-budget unit report is not valid JSON", {
+ cause: error,
+ });
+ }
+ return v.parse(resourceBudgetUnitReportSchema, candidate);
+}
+
+function delta(label: string, finalValue: number, initialValue: number): number {
+ if (finalValue < initialValue) {
+ throw new Error(`Resource-budget ${label} counter moved backwards`);
+ }
+ return finalValue - initialValue;
+}
+
+function assertObservedLimits(
+ report: ResourceBudgetUnitReport,
+ expected: Readonly
+): void {
+ const observed = report.limits;
+ if (
+ observed.memoryHighBytes !== expected.memoryHighBytes ||
+ observed.memoryMaxBytes !== expected.memoryMaxBytes ||
+ observed.memorySwapMaxBytes !== expected.memorySwapMaxBytes ||
+ observed.pidsMax !== expected.tasksMax ||
+ observed.cpuQuotaMicros * 100 !==
+ observed.cpuPeriodMicros * expected.cpuQuotaPercent ||
+ !observed.oomGroup
+ ) {
+ throw new Error(
+ `Resource-budget ${report.scenarioId} limits do not match policy`
+ );
+ }
+}
+
+/**
+ * Applies deterministic acceptance invariants to one host-measured scenario.
+ * CPU and pressure totals are retained as evidence, not flaky CI thresholds.
+ * @param evidence Unit report plus post-run cleanup observations.
+ * @param userId POSIX user-manager identity used to derive the exact cgroup.
+ * @returns Non-gating host measurements for the accepted scenario.
+ */
+export function assessResourceBudgetEvidence(
+ evidence: Readonly,
+ userId: number
+): Readonly {
+ const { report } = evidence;
+ const scenario = resourceBudgetPolicy.scenarios[report.scenarioId];
+ assertObservedLimits(report, scenario.limits);
+
+ const expectedCgroupPath = expectedResourceBudgetCgroupPath(userId, report.unitName);
+ if (report.cgroup.path !== expectedCgroupPath) {
+ throw new Error(
+ `Resource-budget ${report.scenarioId} ran in ${report.cgroup.path}; expected ${expectedCgroupPath}`
+ );
+ }
+ if (evidence.launcherExitCode !== 0 || report.workload.exitCode !== 0) {
+ throw new Error(
+ `Resource-budget ${report.scenarioId} workload did not exit zero`
+ );
+ }
+ if (report.workload.signalCode !== null) {
+ throw new Error(`Resource-budget ${report.scenarioId} workload was signalled`);
+ }
+ if (report.workload.durationMs > scenario.limits.workloadDeadlineMs) {
+ throw new Error(`Resource-budget ${report.scenarioId} exceeded its deadline`);
+ }
+ if (
+ report.workload.stdoutBytes > resourceBudgetPolicy.childOutputMaxBytes ||
+ report.workload.stderrBytes > resourceBudgetPolicy.childOutputMaxBytes
+ ) {
+ throw new Error(`Resource-budget ${report.scenarioId} exceeded its output bound`);
+ }
+ if (report.cgroup.memoryPeakBytes >= scenario.limits.memoryHighBytes) {
+ throw new Error(`Resource-budget ${report.scenarioId} crossed memory.high`);
+ }
+ if (report.cgroup.pidsPeak > scenario.limits.tasksMax) {
+ throw new Error(`Resource-budget ${report.scenarioId} crossed TasksMax`);
+ }
+ if (
+ report.cgroup.finalProcessIds.length !== 1 ||
+ report.cgroup.finalProcessIds[0] !== report.wrapperProcessId
+ ) {
+ throw new Error(`Resource-budget ${report.scenarioId} leaked processes`);
+ }
+ if (!evidence.unitCollected || !evidence.cgroupRemoved) {
+ throw new Error(`Resource-budget ${report.scenarioId} unit was not collected`);
+ }
+
+ const initialEvents = report.cgroup.initial.memoryEvents;
+ const finalEvents = report.cgroup.final.memoryEvents;
+ for (const eventName of ["high", "max", "oom", "oomKill", "oomGroupKill"] as const) {
+ if (
+ delta(
+ `memory.events ${eventName}`,
+ finalEvents[eventName],
+ initialEvents[eventName]
+ ) !== 0
+ ) {
+ throw new Error(
+ `Resource-budget ${report.scenarioId} observed memory.events ${eventName}`
+ );
+ }
+ }
+
+ return Object.freeze({
+ cpuPressureMicros: delta(
+ "cpu pressure",
+ report.cgroup.final.cpuPressure.someTotalMicros,
+ report.cgroup.initial.cpuPressure.someTotalMicros
+ ),
+ cpuThrottledMicros: delta(
+ "CPU throttling",
+ report.cgroup.final.cpuThrottledMicros,
+ report.cgroup.initial.cpuThrottledMicros
+ ),
+ cpuUsageMicros: delta(
+ "CPU usage",
+ report.cgroup.final.cpuUsageMicros,
+ report.cgroup.initial.cpuUsageMicros
+ ),
+ durationMs: report.workload.durationMs,
+ memoryHeadroomBytes:
+ scenario.limits.memoryHighBytes - report.cgroup.memoryPeakBytes,
+ memoryPeakBytes: report.cgroup.memoryPeakBytes,
+ memoryPressureMicros: delta(
+ "memory pressure",
+ report.cgroup.final.memoryPressure.someTotalMicros,
+ report.cgroup.initial.memoryPressure.someTotalMicros
+ ),
+ pidsPeak: report.cgroup.pidsPeak,
+ scenarioId: report.scenarioId,
+ });
+}
diff --git a/qualification/budgets/resourceBudgetUnit.ts b/qualification/budgets/resourceBudgetUnit.ts
new file mode 100644
index 000000000..aa1e6733b
--- /dev/null
+++ b/qualification/budgets/resourceBudgetUnit.ts
@@ -0,0 +1,463 @@
+import { rename } from "node:fs/promises";
+import path from "node:path";
+
+import { Clock, Data, Effect, Scope } from "effect";
+
+import {
+ readCgroupV2ControlFile,
+ readCurrentCgroupV2Snapshot,
+ type CgroupV2MemoryEvents,
+} from "../resources/cgroupV2.ts";
+import {
+ buildResourceBudgetWorkloadCommand,
+ type ResourceBudgetWorkloadCommand,
+} from "./resourceBudgetCommand.ts";
+import {
+ assertResourceBudgetUnitName,
+ expectedResourceBudgetCgroupPath,
+ resourceBudgetPolicy,
+ resourceBudgetScenarioIds,
+ type ResourceBudgetScenarioId,
+ type ResourceBudgetUnitReport,
+} from "./resourceBudgetPolicy.ts";
+
+const nanosecondsPerMillisecond = 1_000_000n;
+type WorkloadProcess = Bun.Subprocess<"ignore", "pipe", "pipe">;
+
+export class ResourceBudgetUnitError extends Data.TaggedError("ResourceBudgetUnitError")<{
+ readonly cause?: unknown;
+ readonly operation: string;
+}> {}
+
+export class ResourceBudgetUnitDeadlineError extends Data.TaggedError(
+ "ResourceBudgetUnitDeadlineError"
+)<{
+ readonly scenarioId: ResourceBudgetScenarioId;
+}> {}
+
+interface ResourceBudgetUnitArguments {
+ readonly repositoryRoot: string;
+ readonly resultPath: string;
+ readonly scenarioId: ResourceBudgetScenarioId;
+ readonly unitName: string;
+}
+
+interface PressureTotals {
+ readonly fullTotalMicros: number;
+ readonly someTotalMicros: number;
+}
+
+interface ResourceSnapshot {
+ readonly cpuNrThrottled: number;
+ readonly cpuPressure: PressureTotals;
+ readonly cpuThrottledMicros: number;
+ readonly cpuUsageMicros: number;
+ readonly memoryCurrentBytes: number;
+ readonly memoryEvents: Readonly;
+ readonly memoryPressure: PressureTotals;
+ readonly pidsCurrent: number;
+}
+
+function parseArguments(arguments_: readonly string[]): ResourceBudgetUnitArguments {
+ if (arguments_.length !== 4) {
+ throw new TypeError("Resource-budget unit requires exactly four arguments");
+ }
+ const values = Object.fromEntries(
+ arguments_.map((argument) => {
+ const separator = argument.indexOf("=");
+ if (!argument.startsWith("--") || separator <= 2) {
+ throw new TypeError("Resource-budget unit argument is malformed");
+ }
+ return [argument.slice(2, separator), argument.slice(separator + 1)];
+ })
+ );
+ if (Object.keys(values).length !== 4) {
+ throw new TypeError("Resource-budget unit arguments contain duplicates");
+ }
+ const repositoryRoot = values.repository ?? "";
+ const resultPath = values.result ?? "";
+ const scenarioId = values.scenario ?? "";
+ const unitName = values.unit ?? "";
+ if (
+ !path.isAbsolute(repositoryRoot) ||
+ !path.isAbsolute(resultPath) ||
+ repositoryRoot.includes("\0") ||
+ resultPath.includes("\0") ||
+ !resourceBudgetScenarioIds.includes(scenarioId as ResourceBudgetScenarioId)
+ ) {
+ throw new TypeError("Resource-budget unit arguments are invalid");
+ }
+ assertResourceBudgetUnitName(unitName);
+ return {
+ repositoryRoot,
+ resultPath,
+ scenarioId: scenarioId as ResourceBudgetScenarioId,
+ unitName,
+ };
+}
+
+function parseNamedCounters(
+ value: string,
+ requiredNames: readonly string[]
+): Map {
+ const counters = new Map();
+ for (const line of value.split(/\r?\n/u)) {
+ const normalized = line.trim();
+ if (normalized.length === 0) continue;
+ const [name, rawCounter, ...extra] = normalized.split(/\s+/u);
+ if (
+ name === undefined ||
+ rawCounter === undefined ||
+ extra.length > 0 ||
+ !/^\d+$/u.test(rawCounter) ||
+ counters.has(name)
+ ) {
+ throw new Error("Malformed cgroup counter file");
+ }
+ const counter = Number(rawCounter);
+ if (!Number.isSafeInteger(counter)) {
+ throw new TypeError("Cgroup counter exceeds the safe integer range");
+ }
+ counters.set(name, counter);
+ }
+ for (const name of requiredNames) {
+ if (!counters.has(name)) throw new Error(`Missing cgroup counter ${name}`);
+ }
+ return counters;
+}
+
+function parsePressure(value: string): PressureTotals {
+ const totals = new Map();
+ for (const line of value.split(/\r?\n/u)) {
+ const normalized = line.trim();
+ if (normalized.length === 0) continue;
+ const [kind, ...fields] = normalized.split(/\s+/u);
+ if ((kind !== "some" && kind !== "full") || totals.has(kind)) {
+ throw new Error("Malformed cgroup pressure file");
+ }
+ const total = fields.find((field) => field.startsWith("total="));
+ const rawTotal = total?.slice("total=".length) ?? "";
+ if (!/^\d+$/u.test(rawTotal)) {
+ throw new Error("Malformed cgroup pressure total");
+ }
+ const parsed = Number(rawTotal);
+ if (!Number.isSafeInteger(parsed)) {
+ throw new TypeError("Cgroup pressure total exceeds the safe integer range");
+ }
+ totals.set(kind, parsed);
+ }
+ const fullTotalMicros = totals.get("full");
+ const someTotalMicros = totals.get("some");
+ if (fullTotalMicros === undefined || someTotalMicros === undefined) {
+ throw new Error("Incomplete cgroup pressure file");
+ }
+ return { fullTotalMicros, someTotalMicros };
+}
+
+function readResourceSnapshot(): Effect.Effect<
+ ResourceSnapshot,
+ ResourceBudgetUnitError
+> {
+ return Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetUnitError({ cause, operation: "read-cgroup-snapshot" }),
+ try: async () => {
+ const cgroup = await readCurrentCgroupV2Snapshot();
+ const [cpuStatText, cpuPressureText, memoryPressureText] = await Promise.all([
+ readCgroupV2ControlFile(cgroup.path, "cpu.stat"),
+ readCgroupV2ControlFile(cgroup.path, "cpu.pressure"),
+ readCgroupV2ControlFile(cgroup.path, "memory.pressure"),
+ ]);
+ const cpu = parseNamedCounters(cpuStatText, [
+ "usage_usec",
+ "nr_throttled",
+ "throttled_usec",
+ ]);
+ return {
+ cpuNrThrottled: cpu.get("nr_throttled")!,
+ cpuPressure: parsePressure(cpuPressureText),
+ cpuThrottledMicros: cpu.get("throttled_usec")!,
+ cpuUsageMicros: cpu.get("usage_usec")!,
+ memoryCurrentBytes: cgroup.memoryCurrentBytes,
+ memoryEvents: cgroup.memoryEvents,
+ memoryPressure: parsePressure(memoryPressureText),
+ pidsCurrent: cgroup.pidsCurrent,
+ };
+ },
+ });
+}
+
+function awaitProcessExit(
+ process_: WorkloadProcess
+): Effect.Effect {
+ return Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetUnitError({ cause, operation: "await-workload-exit" }),
+ try: () => process_.exited,
+ });
+}
+
+function stopProcess(process_: WorkloadProcess): Effect.Effect {
+ if (process_.exitCode !== null || process_.signalCode !== null) return Effect.void;
+ const graceful = Effect.sync(() => process_.kill("SIGTERM")).pipe(
+ Effect.andThen(awaitProcessExit(process_)),
+ Effect.timeoutOrElse({
+ duration: "2 seconds",
+ orElse: () =>
+ Effect.sync(() => process_.kill("SIGKILL")).pipe(
+ Effect.andThen(awaitProcessExit(process_))
+ ),
+ })
+ );
+ return graceful.pipe(Effect.asVoid, Effect.orDie);
+}
+
+function workloadProcessResource(
+ command: ResourceBudgetWorkloadCommand,
+ repositoryRoot: string
+): Effect.Effect {
+ return Effect.gen(function* () {
+ const signal = yield* Effect.abortSignal;
+ return yield* Effect.acquireRelease(
+ Effect.try({
+ catch: (cause) =>
+ new ResourceBudgetUnitError({
+ cause,
+ operation: "spawn-workload",
+ }),
+ try: () =>
+ Bun.spawn([...command.argv], {
+ cwd: repositoryRoot,
+ env: command.environment,
+ killSignal: "SIGTERM",
+ maxBuffer: resourceBudgetPolicy.childOutputMaxBytes,
+ signal,
+ stderr: "pipe",
+ stdin: "ignore",
+ stdout: "pipe",
+ }),
+ }),
+ stopProcess
+ );
+ });
+}
+
+function captureOutput(
+ stream: ReadableStream,
+ operation: string
+): Effect.Effect {
+ return Effect.tryPromise({
+ catch: (cause) => new ResourceBudgetUnitError({ cause, operation }),
+ try: async () => {
+ const output = await new Response(stream).arrayBuffer();
+ return output.byteLength;
+ },
+ });
+}
+
+function runWorkload(
+ arguments_: ResourceBudgetUnitArguments,
+ environment: Readonly>
+) {
+ const command = buildResourceBudgetWorkloadCommand(
+ arguments_.scenarioId,
+ arguments_.repositoryRoot,
+ process.execPath,
+ environment
+ );
+ const deadlineMs =
+ resourceBudgetPolicy.scenarios[arguments_.scenarioId].limits.workloadDeadlineMs;
+ return Effect.scoped(
+ Effect.gen(function* () {
+ const startedAt = yield* Clock.monotonicTimeNanos;
+ const process_ = yield* workloadProcessResource(
+ command,
+ arguments_.repositoryRoot
+ );
+ const [exitCode, stderrBytes, stdoutBytes] = yield* Effect.all(
+ [
+ awaitProcessExit(process_),
+ captureOutput(process_.stderr, "read-workload-stderr"),
+ captureOutput(process_.stdout, "read-workload-stdout"),
+ ] as const,
+ { concurrency: "unbounded" }
+ ).pipe(
+ Effect.timeoutOrElse({
+ duration: deadlineMs,
+ orElse: () =>
+ Effect.fail(
+ new ResourceBudgetUnitDeadlineError({
+ scenarioId: arguments_.scenarioId,
+ })
+ ),
+ })
+ );
+ const endedAt = yield* Clock.monotonicTimeNanos;
+ return Object.freeze({
+ durationMs: Number((endedAt - startedAt) / nanosecondsPerMillisecond),
+ exitCode,
+ signalCode: process_.signalCode,
+ stderrBytes,
+ stdoutBytes,
+ });
+ })
+ );
+}
+
+function readFinalProcessIds(cgroupPath: string) {
+ return Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetUnitError({ cause, operation: "read-final-processes" }),
+ try: async () => {
+ const value = await readCgroupV2ControlFile(cgroupPath, "cgroup.procs");
+ return value
+ .split(/\r?\n/u)
+ .map((entry) => entry.trim())
+ .filter((entry) => entry.length > 0)
+ .map((entry) => {
+ if (!/^\d+$/u.test(entry)) {
+ throw new Error("Malformed cgroup.procs entry");
+ }
+ return Number(entry);
+ })
+ .toSorted((left, right) => left - right);
+ },
+ });
+}
+
+function writeReport(resultPath: string, report: ResourceBudgetUnitReport) {
+ return Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetUnitError({ cause, operation: "write-unit-report" }),
+ try: async () => {
+ const temporaryPath = `${resultPath}.tmp`;
+ await Bun.write(temporaryPath, `${JSON.stringify(report, null, 2)}\n`);
+ await rename(temporaryPath, resultPath);
+ },
+ });
+}
+
+function runUnit(arguments_: ResourceBudgetUnitArguments) {
+ return Effect.gen(function* () {
+ if (process.getuid === undefined) {
+ return yield* Effect.fail(
+ new ResourceBudgetUnitError({ operation: "read-user-id" })
+ );
+ }
+ const initialCgroup = yield* Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetUnitError({
+ cause,
+ operation: "read-initial-cgroup",
+ }),
+ try: () => readCurrentCgroupV2Snapshot(),
+ });
+ const expectedPath = expectedResourceBudgetCgroupPath(
+ process.getuid(),
+ arguments_.unitName
+ );
+ if (initialCgroup.path !== expectedPath) {
+ return yield* Effect.fail(
+ new ResourceBudgetUnitError({ operation: "verify-cgroup-membership" })
+ );
+ }
+ const policy = resourceBudgetPolicy.scenarios[arguments_.scenarioId].limits;
+ if (
+ initialCgroup.memoryHighBytes !== policy.memoryHighBytes ||
+ initialCgroup.memoryMaxBytes !== policy.memoryMaxBytes ||
+ initialCgroup.memorySwapMaxBytes !== policy.memorySwapMaxBytes ||
+ initialCgroup.pidsMax !== policy.tasksMax ||
+ initialCgroup.cpuQuotaMicros === "max" ||
+ initialCgroup.cpuQuotaMicros * 100 !==
+ initialCgroup.cpuPeriodMicros * policy.cpuQuotaPercent ||
+ !initialCgroup.oomGroup
+ ) {
+ return yield* Effect.fail(
+ new ResourceBudgetUnitError({ operation: "verify-cgroup-policy" })
+ );
+ }
+
+ const initial = yield* readResourceSnapshot();
+ const environment = Object.freeze({
+ CI: process.env.CI ?? "1",
+ FORCE_COLOR: "0",
+ HOME: process.env.HOME ?? "",
+ LANG: "C.UTF-8",
+ NODE_ENV: "test",
+ NO_COLOR: "1",
+ PATH: "/usr/local/bin:/usr/bin:/bin",
+ TMPDIR: process.env.TMPDIR ?? "/tmp",
+ });
+ const workload = yield* runWorkload(arguments_, environment);
+ const finalCgroup = yield* Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetUnitError({
+ cause,
+ operation: "read-final-cgroup",
+ }),
+ try: () => readCurrentCgroupV2Snapshot(),
+ });
+ const [final, finalProcessIds, pidsPeakText] = yield* Effect.all(
+ [
+ readResourceSnapshot(),
+ readFinalProcessIds(finalCgroup.path),
+ Effect.tryPromise({
+ catch: (cause) =>
+ new ResourceBudgetUnitError({
+ cause,
+ operation: "read-pids-peak",
+ }),
+ try: () => readCgroupV2ControlFile(finalCgroup.path, "pids.peak"),
+ }),
+ ] as const,
+ { concurrency: "unbounded" }
+ );
+ const pidsPeak = Number(pidsPeakText.trim());
+ if (!Number.isSafeInteger(pidsPeak) || pidsPeak < 0) {
+ return yield* Effect.fail(
+ new ResourceBudgetUnitError({ operation: "parse-pids-peak" })
+ );
+ }
+ const report: ResourceBudgetUnitReport = {
+ cgroup: {
+ final,
+ finalProcessIds,
+ initial,
+ memoryPeakBytes: finalCgroup.memoryPeakBytes,
+ path: finalCgroup.path,
+ pidsPeak,
+ },
+ formatVersion: resourceBudgetPolicy.formatVersion,
+ limits: {
+ cpuPeriodMicros: finalCgroup.cpuPeriodMicros,
+ cpuQuotaMicros: finalCgroup.cpuQuotaMicros as number,
+ memoryHighBytes: finalCgroup.memoryHighBytes as number,
+ memoryMaxBytes: finalCgroup.memoryMaxBytes as number,
+ memorySwapMaxBytes: finalCgroup.memorySwapMaxBytes as number,
+ oomGroup: true,
+ pidsMax: finalCgroup.pidsMax as number,
+ },
+ runtime: {
+ bunRevision: Bun.revision,
+ bunVersion: Bun.version,
+ },
+ scenarioId: arguments_.scenarioId,
+ unitName: arguments_.unitName,
+ workload,
+ wrapperProcessId: process.pid,
+ };
+ yield* writeReport(arguments_.resultPath, report);
+ });
+}
+
+if (import.meta.main) {
+ try {
+ const arguments_ = parseArguments(Bun.argv.slice(2));
+ await Effect.runPromise(runUnit(arguments_));
+ } catch (error) {
+ process.stderr.write(
+ `${Bun.inspect(error, { colors: false, depth: 6 }).slice(0, 16 * 1024)}\n`
+ );
+ process.exitCode = 1;
+ }
+}
diff --git a/qualification/budgets/runResourceBudgetEvidence.ts b/qualification/budgets/runResourceBudgetEvidence.ts
new file mode 100644
index 000000000..0a511da7d
--- /dev/null
+++ b/qualification/budgets/runResourceBudgetEvidence.ts
@@ -0,0 +1,13 @@
+import { Effect } from "effect";
+
+import { resourceBudgetQualification } from "./resourceBudgetOrchestration.ts";
+
+try {
+ const report = await Effect.runPromise(resourceBudgetQualification);
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
+} catch (error) {
+ process.stderr.write(
+ `${Bun.inspect(error, { colors: false, depth: 8 }).slice(0, 32 * 1024)}\n`
+ );
+ process.exitCode = 1;
+}
diff --git a/qualification/budgets/runSafeChildCancellationEvidence.ts b/qualification/budgets/runSafeChildCancellationEvidence.ts
new file mode 100644
index 000000000..662cf5ebe
--- /dev/null
+++ b/qualification/budgets/runSafeChildCancellationEvidence.ts
@@ -0,0 +1,18 @@
+import { Effect } from "effect";
+
+import { interruptedShutdownQualification } from "../shutdown/completeShutdownQualification.ts";
+
+const report = await Effect.runPromise(interruptedShutdownQualification);
+if (
+ report.processGroupMembersAfterInterruption.length > 0 ||
+ report.stoppedStatus.phase !== "stopped"
+) {
+ throw new Error("Interrupted child-process scope did not cleanly terminate");
+}
+process.stdout.write(
+ `${JSON.stringify({
+ processGroupMembersAfterInterruption: report.processGroupMembersAfterInterruption,
+ processGroupMembersWhileReady: report.processGroupMembersWhileReady.length,
+ stoppedPhase: report.stoppedStatus.phase,
+ })}\n`
+);
diff --git a/qualification/build/fixtures/frontend/index.html b/qualification/build/fixtures/frontend/index.html
new file mode 100644
index 000000000..98d699414
--- /dev/null
+++ b/qualification/build/fixtures/frontend/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Frontend build qualification
+
+
+
+
+
+
+
diff --git a/qualification/build/fixtures/frontend/src/LazyPanel.tsx b/qualification/build/fixtures/frontend/src/LazyPanel.tsx
new file mode 100644
index 000000000..8eaebd4cb
--- /dev/null
+++ b/qualification/build/fixtures/frontend/src/LazyPanel.tsx
@@ -0,0 +1,7 @@
+export default function LazyPanel() {
+ return (
+
+ Loaded from a route-shaped lazy chunk.
+
+ );
+}
diff --git a/qualification/build/fixtures/frontend/src/QualificationApp.tsx b/qualification/build/fixtures/frontend/src/QualificationApp.tsx
new file mode 100644
index 000000000..2e603ad77
--- /dev/null
+++ b/qualification/build/fixtures/frontend/src/QualificationApp.tsx
@@ -0,0 +1,22 @@
+import { lazy, Suspense, useState } from "react";
+
+const LazyPanel = lazy(() => import("./LazyPanel"));
+
+export default function QualificationApp() {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
+ Loading…
}>
+
+
+
+ );
+}
diff --git a/qualification/build/fixtures/frontend/src/index.css b/qualification/build/fixtures/frontend/src/index.css
new file mode 100644
index 000000000..449f81f15
--- /dev/null
+++ b/qualification/build/fixtures/frontend/src/index.css
@@ -0,0 +1,5 @@
+@import "tailwindcss";
+
+html {
+ color-scheme: dark;
+}
diff --git a/qualification/build/fixtures/frontend/src/main.tsx b/qualification/build/fixtures/frontend/src/main.tsx
new file mode 100644
index 000000000..02d16e25f
--- /dev/null
+++ b/qualification/build/fixtures/frontend/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+
+import QualificationApp from "./QualificationApp";
+
+createRoot(document.querySelector("#root")!).render(
+
+
+
+);
diff --git a/qualification/build/frontendBuildQualification.test.ts b/qualification/build/frontendBuildQualification.test.ts
new file mode 100644
index 000000000..42ffc1d1b
--- /dev/null
+++ b/qualification/build/frontendBuildQualification.test.ts
@@ -0,0 +1,121 @@
+import { describe, expect, test } from "bun:test";
+import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import path from "node:path";
+
+import {
+ assertSelfHostedFrontendHtml,
+ buildQualificationFrontend,
+ qualificationFrontendPluginOrder,
+} from "./frontendBuildQualification";
+
+const hashedAssetPattern = /^assets\/.+-[a-z\d]{8}\.(?:css|js)$/u;
+
+describe("Bun frontend build qualification", () => {
+ test("proves compiler-first HTML mechanics, Tailwind, lazy chunks, and delivery policy", async () => {
+ const developmentOutdir = await mkdtemp(
+ path.join(tmpdir(), "mira-build-development-")
+ );
+ const productionOutdir = await mkdtemp(
+ path.join(tmpdir(), "mira-build-production-")
+ );
+
+ try {
+ expect(qualificationFrontendPluginOrder).toEqual([
+ "react-compiler",
+ "@tailwindcss/bun",
+ ]);
+
+ const development = await buildQualificationFrontend(
+ "development",
+ developmentOutdir
+ );
+ expect(
+ development.outputPaths.some((outputPath) => outputPath.endsWith(".map"))
+ ).toBeTrue();
+ await assertSelfHostedFrontendHtml(
+ path.join(developmentOutdir, "index.html")
+ );
+
+ const production = await buildQualificationFrontend(
+ "production",
+ productionOutdir
+ );
+ const productionFiles = await listRelativeFiles(productionOutdir);
+ expect(
+ production.outputPaths.some((outputPath) => outputPath.endsWith(".map"))
+ ).toBeFalse();
+ expect(
+ production.outputPaths
+ .filter((outputPath) => /\.(?:css|js)$/u.test(outputPath))
+ .every((outputPath) => hashedAssetPattern.test(outputPath))
+ ).toBeTrue();
+ expect(production.compressedFileCount).toBeGreaterThan(0);
+ expect(productionFiles.some((file) => file.endsWith(".br"))).toBeTrue();
+ expect(productionFiles.some((file) => file.endsWith(".gz"))).toBeTrue();
+ expect(production.metrics?.formatVersion).toBe(1);
+
+ const lazyJavaScript = production.outputPaths.filter(
+ (outputPath) =>
+ outputPath.endsWith(".js") &&
+ !production.initialOutputPaths.includes(outputPath)
+ );
+ expect(lazyJavaScript.length).toBeGreaterThan(0);
+ expect(
+ Object.values(production.metafile.outputs).some((output) =>
+ output.imports.some(({ kind }) => kind === "dynamic-import")
+ )
+ ).toBeTrue();
+
+ const javascript = await readFilesWithExtension(
+ productionOutdir,
+ productionFiles,
+ ".js"
+ );
+ const stylesheet = await readFilesWithExtension(
+ productionOutdir,
+ productionFiles,
+ ".css"
+ );
+ expect(javascript).toContain("useMemoCache");
+ expect(stylesheet).toContain(".bg-indigo-600");
+ await assertSelfHostedFrontendHtml(path.join(productionOutdir, "index.html"));
+ } finally {
+ await Promise.all([
+ rm(developmentOutdir, { force: true, recursive: true }),
+ rm(productionOutdir, { force: true, recursive: true }),
+ ]);
+ }
+ }, 60_000);
+});
+
+async function listRelativeFiles(directory: string): Promise {
+ const files: string[] = [];
+ const pending = [directory];
+ while (pending.length > 0) {
+ const current = pending.pop();
+ if (!current) continue;
+ for (const entry of await readdir(current, { withFileTypes: true })) {
+ const entryPath = path.join(current, entry.name);
+ if (entry.isDirectory()) {
+ pending.push(entryPath);
+ } else if (entry.isFile()) {
+ files.push(path.relative(directory, entryPath).replaceAll("\\", "/"));
+ }
+ }
+ }
+ return files.toSorted();
+}
+
+async function readFilesWithExtension(
+ directory: string,
+ files: readonly string[],
+ extension: string
+): Promise {
+ const contents = await Promise.all(
+ files
+ .filter((file) => file.endsWith(extension))
+ .map((file) => readFile(path.join(directory, file), "utf8"))
+ );
+ return contents.join("\n");
+}
diff --git a/qualification/build/frontendBuildQualification.ts b/qualification/build/frontendBuildQualification.ts
new file mode 100644
index 000000000..f2b1c06bd
--- /dev/null
+++ b/qualification/build/frontendBuildQualification.ts
@@ -0,0 +1,146 @@
+import { mkdir, readFile, rm } from "node:fs/promises";
+import path from "node:path";
+
+import tailwindPlugin from "bun-plugin-tailwind";
+
+import {
+ assertFrontendBundleBudgets,
+ initialFrontendOutputKeys,
+ measureFrontendBundle,
+ type FrontendBundleMetrics,
+ writeFrontendHtmlAppEntrypoint,
+ writePrecompressedFrontendAssets,
+} from "../../scripts/frontendBuildArtifacts";
+import reactCompilerPlugin from "../../scripts/reactCompilerPlugin";
+
+export type QualificationFrontendBuildMode = "development" | "production";
+
+export interface QualificationFrontendBuildEvidence {
+ compressedFileCount: number;
+ initialOutputPaths: string[];
+ metafile: Bun.BuildMetafile;
+ metrics?: FrontendBundleMetrics;
+ outputPaths: string[];
+}
+
+const qualificationFrontendEntrypoint = path.resolve(
+ "qualification/build/fixtures/frontend/index.html"
+);
+
+export const qualificationFrontendPluginOrder = [
+ reactCompilerPlugin.name,
+ tailwindPlugin.name,
+] as const;
+
+/**
+ * Builds a minimal HTML-entry frontend with the target compiler-first pipeline.
+ * The fixture intentionally shares the production artifact policy helpers.
+ * @param mode Build policy to qualify.
+ * @param outdir Disposable build output directory.
+ * @returns Build metadata and delivery evidence.
+ */
+export async function buildQualificationFrontend(
+ mode: QualificationFrontendBuildMode,
+ outdir: string
+): Promise {
+ const resolvedOutdir = path.resolve(outdir);
+ const isProduction = mode === "production";
+
+ await rm(resolvedOutdir, { force: true, recursive: true });
+ await mkdir(resolvedOutdir, { recursive: true });
+
+ const result = await Bun.build({
+ define: {
+ "process.env.NODE_ENV": JSON.stringify(mode),
+ },
+ entrypoints: [qualificationFrontendEntrypoint],
+ minify: isProduction,
+ metafile: true,
+ naming: {
+ asset: "assets/[name]-[hash].[ext]",
+ chunk: "assets/[name]-[hash].[ext]",
+ },
+ outdir: resolvedOutdir,
+ plugins: [reactCompilerPlugin, tailwindPlugin],
+ publicPath: "/",
+ sourcemap: isProduction ? "none" : "linked",
+ splitting: true,
+ target: "browser",
+ });
+
+ if (!result.success) {
+ throw new AggregateError(result.logs, "Qualification frontend build failed");
+ }
+ if (!result.metafile) {
+ throw new Error("Qualification frontend build did not produce metadata");
+ }
+
+ await writeFrontendHtmlAppEntrypoint(result.metafile, resolvedOutdir);
+ const initialOutputPaths = [...initialFrontendOutputKeys(result.metafile)].map(
+ (outputPath) => normalizedOutputPath(outputPath, resolvedOutdir)
+ );
+ const outputPaths = result.outputs.map(({ path: outputPath }) =>
+ normalizedOutputPath(outputPath, resolvedOutdir)
+ );
+
+ if (!isProduction) {
+ return {
+ compressedFileCount: 0,
+ initialOutputPaths,
+ metafile: result.metafile,
+ outputPaths,
+ };
+ }
+
+ const metrics = await measureFrontendBundle(result.metafile, resolvedOutdir);
+ assertFrontendBundleBudgets(metrics.measurements);
+ const compressedFileCount = await writePrecompressedFrontendAssets(
+ result.outputs.map(({ path: outputPath }) => outputPath)
+ );
+
+ return {
+ compressedFileCount,
+ initialOutputPaths,
+ metafile: result.metafile,
+ metrics,
+ outputPaths,
+ };
+}
+
+/**
+ * Fails when generated HTML would require inline or third-party script/style CSP.
+ * @param indexPath Generated HTML entrypoint.
+ * @returns Promise that resolves when the entrypoint is self-hosted.
+ */
+export async function assertSelfHostedFrontendHtml(indexPath: string): Promise {
+ const html = await readFile(indexPath, "utf8");
+ const scripts = [...html.matchAll(/',
+ "utf8"
+ );
+ await assertSelfHostedFrontendHtml(indexPath);
+
+ for (const html of [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '
',
+ '
',
+ '',
+ ]) {
+ await writeFile(indexPath, html, "utf8");
+ let rejected = false;
+ try {
+ await assertSelfHostedFrontendHtml(indexPath);
+ } catch (error) {
+ rejected = true;
+ expect(error).toBeInstanceOf(Error);
+ }
+ expect(rejected).toBeTrue();
+ }
+ } finally {
+ await rm(directory, { force: true, recursive: true });
+ }
+ });
});
async function listRelativeFiles(directory: string): Promise {
diff --git a/qualification/build/frontendBuildQualification.ts b/qualification/build/frontendBuildQualification.ts
index f2b1c06bd..c44e855f9 100644
--- a/qualification/build/frontendBuildQualification.ts
+++ b/qualification/build/frontendBuildQualification.ts
@@ -24,7 +24,8 @@ export interface QualificationFrontendBuildEvidence {
}
const qualificationFrontendEntrypoint = path.resolve(
- "qualification/build/fixtures/frontend/index.html"
+ import.meta.dir,
+ "fixtures/frontend/index.html"
);
export const qualificationFrontendPluginOrder = [
@@ -32,6 +33,20 @@ export const qualificationFrontendPluginOrder = [
tailwindPlugin.name,
] as const;
+const frontendHtmlResourceAttributes = new Set([
+ "action",
+ "background",
+ "cite",
+ "data",
+ "formaction",
+ "href",
+ "manifest",
+ "poster",
+ "src",
+ "xlink:href",
+]);
+const frontendHtmlSourceSetAttributes = new Set(["imagesrcset", "srcset"]);
+
/**
* Builds a minimal HTML-entry frontend with the target compiler-first pipeline.
* The fixture intentionally shares the production artifact policy helpers.
@@ -114,33 +129,133 @@ export async function buildQualificationFrontend(
*/
export async function assertSelfHostedFrontendHtml(indexPath: string): Promise {
const html = await readFile(indexPath, "utf8");
- const scripts = [...html.matchAll(/