new: v0.3 — kubernetes-native sandboxes and the multi-user control plane. read the changelog ›

What shipped, when, and why.

Generated from the repository's CHANGELOG.md. Versions follow SemVer; entries follow Keep a Changelog.

Unreleased

Changed

  • The web surface — marketing site, docs, and dashboard — is rebranded to a kernel.sh-inspired design language. Warm beige canvas with charcoal dark passages (the dashboard's dark theme becomes the charcoal variant, and light is now the default), Inter + IBM Plex Mono replacing Geist, ultra-light lowercase chrome type with positive letter-spacing, hairline borders instead of shadows, and a single chartreuse accent that floods interactive surfaces on hover. Presentation only: no API, route, or behavior changes, and code/user content keep their original case. apps/web/app/kernel.css replaces geist.css as the dashboard skin layer.
v0.6.0
2026-08-05

Added

  • api: report the resolved network enforcer so the dashboard cannot offer what it cannot enforce (82d880d)
  • cloud: turn on governed network access — chart 0.5.1 + the Cilium enforcer (e94729a)
  • network-grant dashboard UI — govern sandbox egress from the dashboard (5f6287e)
  • web: authorize a parked network grant from the timeline (ca0b2da)
  • web: declare an agent's network needs on a revision (13ee4a6)
  • web: edit the sandbox egress ceiling in Governance (e0c9957)
  • web: narrow a single run to offline from the composer (b0d0ff0)
  • web: shared target editor for network grant rules (c39895f)
  • web: type mirrors and presentation helpers for network grants (4b6fa7d)

Fixed

  • web,api: review fixes — preserve the allow catalog across ceiling switches, never fabricate a declaration, persist and reset the run narrowing choice, pin enforcer delegation (21108b4)

Documentation

  • cloud: record the Cilium cutover, its two upstream bugs, and the last mile (33b0524)
  • plan: implementation plan for the network-grant dashboard UI (8dffed5)
  • spec: governed sandbox egress, selectable in the dashboard (395bdab)
v0.5.1
2026-08-04

Fixed

  • chart: the controlled resolver could not exec — grant it NET_BIND_SERVICE (d5ccd59)
  • chart: the controlled resolver could not exec — grant it NET_BIND_SERVICE (dd1382d)
  • network: a CAS answers "did I win", never "is there work left" (75d7e61)
  • network: a CAS loser must distinguish "someone else won" from "resolved away" (256a723)
  • network: a DNS deny Cilium cannot express, plus the third review's blockers (786b86d)
  • network: close the review's blocking findings (991deb5)
  • network: close the second review's blockers, including a race my own fix added (494ac12)
  • network: disambiguate legacy deny snapshots, and reconcile live grants (343b60b)
  • network: fail closed at the renderer, and align adoption with ownership (8c226ef)
  • network: reconcile from the datapath, and bound the schema in both directions (ea3fec9)
  • network: scope DNS lookups to the grant, align the deny wall, correct the order (a8ae238)
  • network: ten security fixes for governed network access (nine review rounds) (4ee659d)
v0.5.0
2026-08-04

Added

  • core: bounded denied-flow observation + EKS acceptance runbook (b0ddb5d)
  • core: network grant domain, authorization pause, Cilium spike findings (1145e6e)
  • Enterprise Recipes — versioned templates that stamp governed automations (dcacd9b)
  • governed sandbox network access (d006881)
  • k8s: actually program the per-run network policy at provision (29fa915)
  • k8s: Cilium enforcer + verify() at provision (d491bc1)
  • k8s: Cilium provider seam, per-run policy lowering, chart-static wall (9d9c2ac)
  • k8s: implement the Cilium enforcer and invoke verify() at provision (7df0baf)
  • server: resolve, park, and revoke sandbox network grants (96def7a)

Documentation

  • network-grant runbook, design doc, threat-model residuals, claim fixes (d317ade)
v0.4.0
2026-07-31

⚠ BREAKING CHANGES

  • policies: fold managed overrides into head rules in the engine

Added

  • audience-scoped sandbox credentials — llm/tool/control/workspace split (#33) (fa87936)
  • bring your own MCP server, intuitively + authoritative harness/model (#24) (ac7653f)
  • broker,db: tear down upstream MCP sessions from any replica (#34) (7007ac5)
  • capabilities: design-doc Phase 5 — capability & MCP catalog (c31f3e0)
  • catalog: connector-catalog bulk import — MCP Registry (primary) + open-connector (supplement) (#25) (6ee603e)
  • catalog: decorate entries with live connection/bundle state; connected cards get Disconnect/Reconnect (b1da355)
  • chart: archive object store and replica declaration (#34) (95ad63d)
  • ci,dx: non-vacuous CI, supply-chain gate, GHCR distribution, user guides, policy proptests (#22) (7aabf3b)
  • ci: prove the permission gate with no model spend, and gate it on every PR (0e49849)
  • ci: secrets acceptance matrix — KMS, invariant 20, virtual keys, RLS (#32,#75) (3bb321d)
  • codex: codex-runner image + app-server supervisor; facade strips server tools (Phase 6 step 6) (b13a8ab)
  • connectors: Phase 5.5 — connector catalog & OAuth credential custody (81e1887)
  • core,server: frozen-schema argument enforcement at the gate (#33) (708cecc)
  • core: connection requirements + run-binding fields on RunSpec (#31) (d72aedc)
  • core: event invocation context + github result destinations + TrustTier::as_str (a6ed044)
  • core: read-only trust tier classifier (fork events review, never write) (b9af0a8)
  • db,server,chart: remove the ceilings that made 300 concurrent runs impossible (#34) (e7ecb3d)
  • db: 0013 appendix — legacy brokered bundles to connection requirements, subscriptions repointed (#31) (4de4e9c)
  • db: atomic subscription+schedule update with stale guard (042655c)
  • db: event delivery/dispatch/external-result tables + trust-tier & dispatch binding (migration 0005) (0891d45)
  • db: identity layer — migration 0012, TenantScope, identity repositories (#30) (86395a8)
  • db: migration 0013 — connection ownership, tool snapshots, run resource bindings (#31) (b632736)
  • db: RLS policies + tenant GUC plumbing, wave A (#32,#75) (9f27a47)
  • db: RLS wave B — identity + audited system_worker bypass (#32,#75) (cd5f00d)
  • demo: fixture repo + demo compose (3c1f5c9)
  • demo: just demo — five-minute no-key first-run + validation drills (007da29)
  • dev: local Postgres container replaces Neon for local development (21c5b03)
  • docs,web: public /docs platform — relocated engine, search, new guides (f3b4454)
  • docs,web: repo docs tree + in-app /developer docs engine (7865c42)
  • durable automation API contract, PATCH /v1/triggers/{id}, self-explanatory template box (db19dba)
  • dx: one-command bootstrap (just setup) + environment preflight (just doctor) (d4bb3b9)
  • dx: one-command bootstrap (just setup) + environment preflight (just doctor) (3ae195c)
  • e2e: codex phase 10 (protocol replay + no-model probes + live tier) + deploy wiring (Phase 6 step 8) (95abec5)
  • facade+gate: second dialect enforcement boundary, OpenAI metering, intent-based tool budget, approval digest binding (Phase 6 step 4) (955bc57)
  • github: expose updated_at/pushed_at in the repo picker projection (c3c12f1)
  • github: Phase 5.6 — seamless GitHub connect via App manifest + install dances (c56638f)
  • governance: the Governance page — per-tool permissions matrix + managed overrides (#36) (e7a253f)
  • governor,db: cross-replica egress governance — durable rate windows + breaker (#34) (023c151)
  • harness: server-side harness registry; per-harness API defaults; orchestrator env seam (Phase 6 steps 1-3) (fd2b266)
  • k8s: Phase 0 — provider seam + collection hardening (Docker-only) (#49) (7930e96)
  • k8s: Phase 1 — KubernetesProvider + workspaced collector + dual listener (#50) (223e5e8)
  • k8s: Phase 2 — Helm chart + verified network hardening + per-cloud presets (#51) (336bc92)
  • k8s: Phase 3 — CI + provider conformance (#52) (8c54b7b)
  • phase-f: Codex review gate fixes + operational metrics (#34) (54df7e0)
  • policies: append-only policy_versions — migration 0026 + storage (eb1232b)
  • policies: codex-review hardening — CAS publishes, strict drafts, enforced append-only (b72f35a)
  • policies: DB-native policies — versioned storage, structured authoring, attachment (§17 #11) (eb0d426)
  • policies: fold managed overrides into head rules in the engine (45eda12)
  • policies: retire policy-sync; governance e2e for the versioned model (319679e)
  • redact,codex: scrub OpenAI project keys + fbx session/trigger tokens; codex-runner package.json (Phase 6 step 6 prep) (5fea3e2)
  • replay-runner: deterministic replay driver + transcript (d14c77e)
  • replay-runner: image + just replay-build (a40639b)
  • runner: move the control token off the environment before exec (#34) (a9a5bea)
  • runner: shared runner-lib (contract + shims) + token-renew loop + server-side renew hardening (Phase 6 step 5) (ffc57fe)
  • scripts: db-clean-tests — a scalpel for test residue, not a reset (#46) (ee9bfb5)
  • server,db: approval single-emission + pg_notify, session lease/epoch fencing, delivery claims (#33) (800f8c3)
  • server,db: durable four-state execution claims around brokered dispatch (#33) (33dcf45)
  • server,db: durable request-keyed LLM budget reservations (#33) (757d87e)
  • server,db: one-time browser-bound OAuth state rows (#32) (ab6f82e)
  • server,db: reusable OAuth client registrations (#32) (f075a8c)
  • server,db: versioned envelope sealing with per-tenant DEKs (#32) (34d4898)
  • server,workspace: workload identity, archive object store, load harness (#34) (33fefb8)
  • server: /v1/admin/orgs — break-glass, IdP lifecycle, issuer migration (#30) (c1906bb)
  • server: /v1/auth — IdP-agnostic OIDC login, sessions, switch, logout (#30) (c659a45)
  • server: binding consumers — broker/workspace/publish rechecks, approval authority (#31) (3c003e7)
  • server: binding resolution service — requirements to frozen run resource bindings (#31) (b301e3f)
  • server: connection ownership — personal/organization owners, use authorization, /auth/me user_id (#31) (e09388e)
  • server: connection tool snapshots — forced-negotiation photograph, bundle cutover, generation custody (#31) (ba08d91)
  • server: contract URL + ingress helpers on trigger get/list/rotate (de85bf0)
  • server: event trigger subscriptions — connection binding, $17 #2 default events, publish modes (273922d)
  • server: fork trust tier is real — read-only enforcement at the permission gate (7a070dd)
  • server: github app connections — rs256 jwt, installation tokens, sealed webhook secret (e3b869f)
  • server: github connector — webhook verify + PR normalize behind the seam (74390cc)
  • server: outbound rate limits + per-connection circuit breakers (#33) (79dc55b)
  • server: PATCH /v1/triggers/{id} for the mutable subscription surface (3c48b8e)
  • server: per-run MCP session manager + 2025-11-25 conformance (#33) (89dbaa9)
  • server: per-tenant LiteLLM virtual keys; master key confined to provisioning (#32) (1275ae2)
  • server: PR comment/check publishers — stable update-in-place identity, App-only (74fd34e)
  • server: Principal resolver, RBAC, PATs, CSRF — identity enforcement (#30) (e469d09)
  • server: provider-ignorant event spine — ingress, two-level dedup, subscription fan-out (f339538)
  • server: pure PATCH resolution for trigger subscriptions (973d83a)
  • server: resumable legacy→KMS re-seal with count parity (#32) (b39b5dd)
  • server: shared egress boundary — SSRF-hardened clients + clone admission (#33) (739f6b2)
  • web: app-store composer — unified AppPicker, guardrail presets, plain language (9e90e45)
  • web: bundle picker for agent attach — select registered capabilities instead of typing refs (ba4269a)
  • web: connection ownership, tool snapshots, requirements editor, explicit bindings (#31) (f599f1b)
  • web: dashboard redesign — 5-item IA, dark design system, app-store integrations (c6eb415)
  • web: durable /automations/{id} page with live API contract (680083b)
  • web: edit template and settings on the automation detail page (8a57fc9)
  • web: enable codex harness; per-harness models reset on switch (Phase 6 step 9) (0c3170f)
  • web: extract pure automation-contract helpers (a2f67d1)
  • web: FLUIDBOX_WEB_MODE sso proxy, login page, session shell (#30) (2991ace)
  • web: homepage v2 — patterns grid + real-product visibility section (9319363)
  • web: improve control-plane UX and resilience (709114f)
  • web: marketing site redesign — dark editorial system, film hero, end-user pass (1b9e52c)
  • web: move the dashboard under /app — the public/app boundary (293ba74)
  • web: policy attachment + structured authoring (specs A & C) (0c8ba6e)
  • web: public marketing site — home, product, open-source, security, pricing, changelog (3d8eb25)
  • web: self-explanatory template box + pre-save API preview (42d44dc)
  • web: SEO surface — sitemap, robots, OG/Twitter card, noindex boundaries (21db1ea)
  • web: server-side auth gate — sso login wall, session-aware /login, deep-link return (93f6ed6)
  • web: shared AutomationContract; secrets modal is secrets-only (2d0f5af)
  • web: surface the brokered execution outcome in the run timeline (#33) (3d97cf5)
  • web: unify dashboard and run workflows (#26) (e3a475d)
  • web: WorkOS AuthKit web-tier gate for /app (FLUIDBOX_WEB_AUTH=workos) (04019dc)

Fixed

  • capabilities: admit real-vendor tool manuals in the photograph screen (738d758)
  • ci,docs: strengthen false-green asserts; correct the go_url residual disclosure (#32) (fb65948)
  • ci: bindings-e2e — instant provisioning failure via dead-registry image ref; settle budget 300s (#31) (033ace0)
  • ci: CI round 2 — __Host- flow cookie, DB fixture shapes, family lockstep, AWS SDK rustls stack (#32) (2d7657b)
  • ci: identity-e2e — curl -w composition, psql tuple-only captures, fail-fast preconditions (#30) (196e2a7)
  • ci: read gate-proof evidence through a container, not from the host (23a44ab)
  • close the three P2s, pin the runner supply chain, gate publishing (#112) (19b7f7d)
  • codex: correct item/tool/requestUserInput response schema to {answers:{}} (Step 6 re-review) (b68a300)
  • codex: incorporate Step 6 review — governance completeness, move-path, state dirs, schemas (codex/gpt-5.6-sol) (d080064)
  • codex: make the codex harness actually start (it never did) (b7a4d8f)
  • core: govern every tool the pinned CLI advertises; deny sub-execution (c0aa531)
  • core: register ToolSearch in the canonical tool vocabulary (92fec3c)
  • db,ci,docs: final-review wave — RLS blast radius, e2e port, runtime-role coverage (#32) (6c0e5f9)
  • db,server: Codex verify — ingress scope-after-verify, switch config-lock, residual predicates (#30) (5549e5b)
  • db,server: drop the unused delivery-claim index; scope the epoch-fence claim (#33) (b626ba8)
  • db,tools: catalog importer targets the global partial slug index; isolate mcp-shape CHECK negative (#31) (ff6c74e)
  • db: 0013 conversion — branch coverage tests, mixed-bundle drop notice, safe string-pin parse (#31) (357762c)
  • db: 0013 conversion — floating keep-lists, append-only clone for sandbox-latest (#31) (f622fb7)
  • db: 0013 conversion — fresh next-rev per appended revision (#31) (9c99601)
  • db: 0013 conversion — per-source revisions, keep-list-preserving repoints (#31) (eafad54)
  • db: Codex round-1a — write-side tenant proofs, switch-claim hardening, audit truncate guard (#30) (187fb6d)
  • db: final-review minors — projections, user_status symmetry, bootstrap/migrate test gaps (#30) (581f26f)
  • db: RLS review wave — role posture validation, enumerated grants, audit tenant floor (#32) (09e1cdf)
  • db: scope the sweeper tests to their own session (#33) (d4fb57d)
  • db: switch-claim takes the config lock first; jwks cache active-only (#30) (d00d348)
  • db: the 0013 conversion is a GLOBAL scan — confine the tests to one tenant (#32) (2932aa9)
  • demo: fail when the run fails; agree with the server on the docker daemon (40ce3c9)
  • demo: prove the daemon can read the checkout before running (18fc153)
  • demo: the from-source first run was broken; add static self-checks (b52a554)
  • e2e,core: three SCRIPT defects behind the first hardening run (#33) (14d6f64)
  • e2e,ledger: codex phase green (16/16) — summarize() lists edit paths; replay assertion counts (41a3081)
  • e2e: deflake trigger rotation race; align github phase with #33 identities (9817bf1)
  • e2e: make no-live mode zero-spend and deterministic (closes the CI flake class) (#23) (89f518c)
  • e2e: strip stray ellipsis byte that broke e2e.sh preflight under set -u (8c4657a)
  • e2e: suite fixes surfaced by the full local run (611/0 across the five phase suites) (ccaf199)
  • e2e: WRONG ASSERTION — (i.4) demanded 'cancelling' its own fixture cannot produce (#33) (0f0da8e)
  • eval: quote the required-token message, and make the guard parse the file (4377c96)
  • eval: require an admin token; loopback the dashboard; guard both in CI (3bbf73b)
  • facade,e2e,web: post-ship Phase 6 correctness review (894cbc4)
  • facade+gate: incorporate Step 4 review — dup-key differential, verdict CAS, NULL-digest fail-closed, dialect errors, SSE cap (gpt-5.6-sol xhigh) (30bc78c)
  • final-review follow-ups — preserve next_fire_at on cadence-neutral PATCH, no-op {} schedule, clearer errors/hints (37ac1db)
  • gate: CAS-first on the budget + approval-timeout paths (Step 4 re-review) (c9e5d75)
  • gate: server-side /result idempotency across revoke + /permission terminal check (Step 5 re-review) (94a854a)
  • github: drop default_events with the webhook on non-public deployments (9185202)
  • github: omit the webhook from manifests on non-public FLUIDBOX_PUBLIC_URL (6de3334)
  • k8s: archive streaming — pack to disk with a size cap, stream the GET, reclaim leaks (M4, L3) (1b238a2)
  • k8s: batch-5 Codex round 2 — toleration fidelity, render-time validation, release binding tightened (c45f90b)
  • k8s: batch-5 Codex round 3 — strict toleration fields, semver tag guard, doc truth (09bab0d)
  • k8s: batch-6 Codex round 2 — pod-side streaming, atomic pack, fail-closed caps, safer archive lifecycle (bed3217)
  • k8s: batch-6 Codex round 3 — symlink-entry ceiling, session-state-aware TTL sweep, quieter-never-silent failures (8b945be)
  • k8s: batch-7 Codex round 2 — rolling-deploy-safe reconcile, guarded adoption, honest no-handle collection (8a6a2af)
  • k8s: batch-7 Codex round 3 — boot-sweep parse parity, honest label mismatch, launch-mins floor (732334e)
  • k8s: buffer the archive file reader in workspaced init (7ddacad)
  • k8s: cleanups — fail-closed gate resolution, UID-guarded deletes and collection, quiesce replay (L2, L10, L11) (8c78339)
  • k8s: cleanups — fail-closed gate resolution, UID-guarded deletes, quiesce replay (L2, L10, L11) (4ec0b09)
  • k8s: close the Codex round-2 findings on the finalizer (M1 pre-handle window + 8 defects) (8d01bfe)
  • k8s: close the netpol admission race with a bounded observation protocol (95e8459)
  • k8s: enforce a fresh destination before extract/copy (Codex batch-3v3) (98f4ebb)
  • k8s: extract in-tree symlinks in the workspace archive (H4, L4-pack) (db11f77)
  • k8s: extract in-tree symlinks in the workspace archive (H4, L4-pack) (09230b6)
  • k8s: finalizer durability — winning intent is the single source of truth (H2,H3,H5,M1,L6,L7) (f10e3ce)
  • k8s: harden symlink extraction + preserve links in copy_tree (Codex review of #61) (a0fa0a8)
  • k8s: helm↔provider wiring — sandbox values reach the provider, digests render, probe gate parity (M3, M9, M10, L12) (428a5dd)
  • k8s: helm↔provider wiring — sandbox values reach the provider, digests render, the boot probe gains gate parity (M3, M9, M10, L12) (9adb745)
  • k8s: install ring CryptoProvider so the Kubernetes provider boots (3fc508e)
  • k8s: install ring CryptoProvider so the Kubernetes provider boots (54a292b)
  • k8s: integrity-check exec-collected diffs, resume dropped streams (M2, L4-exec) (7e8e95c)
  • k8s: listener hardening — no /internal on the public plane under K8s (M8, L1, L5, L8) (4453b11)
  • k8s: make canonicalize the sole symlink-containment authority (Codex re-review) (06f518c)
  • k8s: make the kind-calico CI tier a real check (H1) (#60) (e84ea64)
  • k8s: numeric runAsUser for bundled LiteLLM (365e657)
  • k8s: numeric runAsUser for bundled LiteLLM (root image + runAsNonRoot) (319a8b3)
  • k8s: reconcile — periodic adopt-or-terminate sweep, graded config errors, node-loss visibility, Docker-parity pre-launch diffs (M5, M6, M7, L9) (bd4fdeb)
  • k8s: reconcile — periodic adopt-or-terminate sweep, graded config errors, node-loss visibility, Docker-parity pre-launch diffs (M5, M6, M7, L9) (f80b74b)
  • k8s: refuse a symlinked destination in clear_dir_contents (Codex batch-3v4) (f3276c8)
  • k8s: remove a duplicated match line that broke the build (batch-3 docs commit) (1c6bc5c)
  • k8s: round-3 finalizer hardening — durable budget sweep, settle-window, verified RunResult (1efca79)
  • k8s: round-6 — transactional attach fence, evidence-preserving abandon (7a225f6)
  • k8s: rounds 4-5 finalizer convergence — intent-aware launch ownership, gated collection, self-cleaning losers (799ff63)
  • make an audience mismatch fail loudly; tighten audience disclosures (#33) (787396e)
  • oauth: gate CIMD on a fetchable public URL; re-resolve stale client identities (f55a588)
  • policies: codex confirmation nits — fail-closed seeds, direct RLS probe, sharper e2e pins (5ddebeb)
  • policies: codex final-review fixes — divergent YAML, preview staleness, strict imports (b688960)
  • policies: review-response hardening — proportional seed refusal, policy delete, precise wildcard fold (adaa74c)
  • release: close the three RC verification blockers (7647357)
  • release: don't let the Cargo.lock step mask the real release-please error (#110) (1f28e3e)
  • release: drop README.md from release-please extra-files (2d10b5d)
  • release: let the version guard express a prerelease (4d6abf3)
  • release: make release-please work with a virtual cargo workspace (#109) (f699bbe)
  • review: address PR #27 whole-branch external review — 4 P1s, 3 P2s, 3 minors (0bec91a)
  • runner-test: an unopened fd is a platform assumption, not a fact (#34) (b74d990)
  • runner+e2e: Step 5 review — e2e build context, self-rescheduling renew, /result ack-on-revoke (gpt-5.6-sol) (1540d77)
  • runner: gate EVERY Claude tool call via a PreToolUse hook (66e0bb2)
  • scale-e2e: bound each concurrent gate request (#34) (b53fa43)
  • scale-e2e: wait on the burst PIDs, not the whole job set (#34) (aee65df)
  • scripts: governance e2e runs from any cwd; .env.example uses reachable bind (9d6dd7b)
  • seed: bootstrap policies insert-if-absent so UI edits survive reboot (fc770ba)
  • seed: source the curated agent's harness id + defaults through the registry (steps 1-3 review) (38a2f7d)
  • server,ci,docs: refresh-singleflight bug, restore-drill fixture, global-row RLS asserts, runbook truth (#32) (ff9b00c)
  • server,ci: audited JSON rejections; SSE exit-code assertion; comment accuracy (#30) (4562053)
  • server,ci: oauth critical section on one pooled connection; AS error log sanitization; per-bearer init assertion after refresh (#31) (d533f24)
  • server,ci: socket-peer client IP unless trusted proxy; e2e readiness path (#30) (1718a26)
  • server,core: bound schema validation cost; close ambient-proxy, git-env and OAuth redirect holes (#33) (478707d)
  • server,core: linear SSE parsing, capped server-request replies, bounded OAuth reads (#33) (8de3630)
  • server,db,ci: connector_oauth_flows verifier joins the sealed-family lockstep (#32) (518a2bc)
  • server,db,runner: adopt durable outcomes on lost CAS; tenant-partition the governor; stable shim idempotency (#33) (d5a974e)
  • server,db: bound claim churn, unify the terminal refusal, gate the legacy path (#33) (3846b38)
  • server,db: charge only on a durable usage write; repair two false-green guards (#33) (aa9e9ca)
  • server,db: codex round findings — recheck seam, oauth atomicity, snapshot guards, xss (#31) (51f739c)
  • server,db: land the OAuth custody bag inside the start's lock-holding txn (#32) (2e945ab)
  • server,db: oauth bump-in-update + post-lock generation gate; sanitized upstream errors; capped discovery reads; trigger-token principal (#31) (d87fb88)
  • server,db: OAuth review wave — activation CAS, bounded key recovery, mint cleanup (#32) (27bcc43)
  • server,web,ci: Codex round-1c — arming locks, strict PATCH, proxy strictness, acceptance tightening (#30) (fa72847)
  • server,web,ci: Codex verify 1c — lock-coherent PATCH/deactivation, audited refusals, interleaving tests (#30) (07058cb)
  • server,web: tenant-scoped GitHub App flows; Phase C UI cutover fixes (#31) (8207972)
  • server: approval decide_own excludes brokered calls; strict Decision body (#30) (89d5762)
  • server: authorize the terminal MCP session DELETE (#33) (2dbe42b)
  • server: bind the CAS to the real start epoch; tighten key-rejection classification (#32) (6a5989f)
  • server: bindings — reject unknown explicit slots, github_app publish assert, authority-branch tests (#31) (40dd6e0)
  • server: CIMD arm adopts existing registration identity (#32) (0ab091d)
  • server: claim a deployment KEK identity before serving; sweep expired DEKs (#32) (7fda061)
  • server: close the connector-OAuth SSRF pre-flight gap; admission layer + hardening asserts (#33) (dbdc7ed)
  • server: Codex round-1b — artifact scoping, JWKS/claims hardening, redirect canonicalization, SSE bound (#30) (c6da453)
  • server: Codex verify 1b — JWKS pairing/persistence, null claims, alg canonicality, SSE bound (#30) (797f255)
  • server: conservative reservation bound; no duplicate GitHub effects; fence the launch writes (#33) (343c80d)
  • server: final-review wave — retirement gate quadrant, registration FK + heal semantics, transit AAD purpose, tenant-key 401 recovery (#32) (b51cdb2)
  • server: KMS review wave — KEK compatibility gate, DEK singleflight, bounded cache, audit fidelity (#32) (6950f21)
  • server: oauth custody commits are checked — fail closed, never cache on ambiguity (#31) (1b830fe)
  • server: oauth refresh — commit check dominates both result branches (#31) (d11d0bd)
  • server: owner-role grants take the config lock; audit label + doc nits (#30) (cfe24d2)
  • server: per-hop SSRF client for identity fetches; JWKS negative-cache scoping (#30) (f5cd53e)
  • server: re-stamp delivery claims per attempt; tighten Task 6 disclosures (#33) (a9d27de)
  • server: release the finalization claim when the driver lacks the lease (#33) (ea11853)
  • server: route global registration writes through the audited bypass (#32) (2dd59d3)
  • server: SSE re-auth bound holds across query awaits and backoff (#30) (0a4f6d2)
  • web,ci: add-server wizard shapes; bindings-e2e coverage — ambiguity, publish binding, exact negotiation (#31) (24f50be)
  • web,ci: requirement owner-aware binding mode; wizard snapshot settling; e2e — signature verification, workspace consumer proof, per-photograph negotiation (#31) (f927641)
  • web: extract + test proxy security helpers; bare login route; apiPatch (#30) (a3acc5e)
  • web: hide revoked registrations/connections; e2e leaves no live fixtures (6b70d02)
  • web: label credentialless connections "no auth", not "api key" (#31) (90b20db)
  • workers: sweep sessions stalled before launch; allow Created→Failed (b7d56f4)
  • workspace,scale-e2e: two stale comments the whole-branch review caught (#34) (9019a48)

Changed

  • db,server: TenantScope wave A — sessions, events, approvals, workers (#30) (9ad0abe)
  • db,server: TenantScope wave B — agents, connections, triggers, create_run (#30) (6d3bff7)

Documentation

  • $17 #1-#3 recorded settled; event-spine invariant + env seams in CLAUDE.md/.env.example (a5bb7f9)
  • add a full Kubernetes deployment guide (acdb27a)
  • add root CLAUDE.md for future Claude Code instances (81eb75d)
  • CHANGELOG 0.2.0 section + EKS-acceptance handover (3d33a2f)
  • CHANGELOG 0.2.0 section + EKS-acceptance session handover (1c533a1)
  • claude: harness registry + canonical-tool-vocabulary invariant + two-image note (Phase 6) (f1ba07e)
  • connector-catalog & OAuth-custody slice — research findings + dense session brief (user-selected next, ahead of Phase 6) (ff4b53f)
  • correct four stale claims in the Phase E handover (#33) (ab484cd)
  • correct Phase D truth-pass overstatements (#32) (d775dcb)
  • correct the gate, egress and acceptance claims to what is actually proven (c8c61cc)
  • correct the scale-hang root cause in the handover (#34) (26ff201)
  • db: add insert_audit_standalone to the bypass inventory (#32) (5836e14)
  • db: the migrate! reminder was stale for four migrations (#34) (76760d4)
  • design agent workspaces and trigger integrations (fb1f4d3)
  • design for the five-minute first-run demo + launch media (4e415b6)
  • document the outbound rate-limit and circuit-breaker knobs (#33) (aa3491c)
  • drop references to the removed source-spec file (4ec6037)
  • eks: Phase-F live EKS acceptance — PASS, zero-orphan audited (f6bf7b9)
  • full Kubernetes deployment guide (c967192)
  • handover — 6.A hardening shipped; next is borrow-the-agent (user decision) (67e83b8)
  • handover — Phase 3 (scheduled borrowing) design intent + §17 #5 recommendation (1fb37e9)
  • handover rev 4 — design-doc Phase 2 (API triggers + signed callbacks) shipped (0d8cd78)
  • handover rev 5 — design-doc Phase 3 (scheduled borrowing) shipped; §17 #5 settled defaults recorded (95177e5)
  • handover rev 6 — design-doc Phase 4 shipped (github pr-review fan-out on the connector seam) (01a7f17)
  • handover: rev 10 — Codex (second harness, Phase 6) shipped (31ebd84)
  • handovers: 2026-07-13 governance/GTM brief + 2026-07-14 codex-MCP debug session (#53) (1eaf724)
  • hosted: incorporate Codex adversarial review round 1 (11 findings) (539e818)
  • hosted: incorporate Codex adversarial review round 2 (5 findings) (f58cf0e)
  • hosted: incorporate Codex adversarial review round 3 (2 findings) (8ff83c0)
  • hosted: incorporate independent fidelity review (3 findings) (74e14c8)
  • hosted: Phase A draft — hosted product boundary (matrix, admission policy, network, threat model) (0cab245)
  • hosted: rollout gates with checkable exit criteria (#34) (ba5c6b7)
  • implementation plan for automation API contract + template clarity (b6b6be9)
  • implementation plan for the five-minute demo + launch clips (742992b)
  • independent prime-time red-team assessment (reports only) (3faaac3)
  • k8s: continuance handover for the PR #47 fix series (batches 5-7) (329765b)
  • k8s: document the residual symlinked-dest hardening as a follow-up (L15) (0e2ddc4)
  • live EKS acceptance for the Kubernetes-native provider (closes #48) (e8a162c)
  • mark the red-team findings superseded by this integration (72f572d)
  • minimal hatchet-style README — link out, don't inline (b62ed76)
  • next-session brief for design-doc Phase 4 (GitHub PR-review fan-out) (5fec7ba)
  • next-session brief for design-doc Phase 5 (capability & MCP catalog) (357281e)
  • overnight integration review — four branches, independently verified (2e57b5c)
  • phase 4 brief — condense paste block to <=4000 chars (8ce4902)
  • phase 4 brief — emphasize the connector seam (GitHub as first tenant of the five-duty boundary) (4639cbd)
  • phase 4 brief — pin the current pushed HEAD (97b33a6)
  • phase 4 brief — self-stable tree-state wording (code-freeze hash only) (23851e5)
  • phase 5 brief — add MCP-ecosystem research-first step (5915965)
  • Phase 6 codex-harness design (approved) + session brief; phase 4 review record (68bfc56)
  • Phase 6 live bring-up + post-ship review round (HANDOVER rev 11) (3421257)
  • Phase B shipped-surface truth pass (#30) (57be435)
  • Phase C shipped-surface truth pass (#31) (3b14d4c)
  • Phase D session handover (#32) (634ffd6)
  • Phase D truth pass (#32) (b6f832e)
  • Phase E handover — CI fully green (#33) (246450f)
  • Phase E handover — closeout state and corrected lessons (#33) (491d226)
  • Phase E truth pass + handover (#33) (35e8c4d)
  • Phase F handover (#34) (71a4fc5)
  • plans: identity design v2 — incorporate Codex adversarial round 1 (336ad28)
  • plans: identity design v3 — incorporate Codex adversarial round 2 (9df9582)
  • plans: identity design v4 — incorporate Codex adversarial round 3 (563f1dc)
  • plans: identity design v5 — fix the bootstrap RETURNING defect (Codex round 4) (15a30ed)
  • plans: k8s design v1.1 — dual-provider permanence (settled Q17) (c6ab3a5)
  • plans: k8s design v1.2 — operator journeys + per-cloud presets incl. DOKS (56b1565)
  • plans: kubernetes-native execution provider + Helm deployability design (65641b9)
  • plans: multi-user MCP control plane design — FINALIZED v2 (8553873)
  • plans: multi-user MCP v3 — browser-bound OAuth callbacks, 4-state execution claims, missing migrations (76acb01)
  • plans: multi-user v4 re-baseline + IdP-agnostic identity companion design (9bc0a8e)
  • plans: public site + docs platform + WorkOS /app boundary design (a42ea49)
  • point next-phase handover at the user's workspaces/triggers design doc (0b75bd8)
  • policies: close the live-agent gap — real run proves the governance loop (a0342a8)
  • policies: correct two errors in my own validation output (d74755f)
  • policies: design DB-native policies — versioned storage + structured authoring (80c950a)
  • policies: prove the governance loop on Kubernetes too, and explain the EKS 503 (a5681b6)
  • policies: route every unfixed finding to a tracking item (69d0df6)
  • policies: run the loop on REAL EKS; correct my own EKS 503 explanation (31702cf)
  • policies: two-environment validation report + correct the upgrade guidance (0f0f662)
  • professional open-source pass — community health files, templates, metadata (548cacc)
  • readme: lead with the product film instead of a still (881dec4)
  • readme: lead with the product film instead of a still (ec1d301)
  • readme: raise the product film above the fold (c49c526)
  • readme: raise the product film above the fold (01ec08a)
  • record Step 5 review resolution (ACCEPT after 3 passes) (cbb17d2)
  • record the scale job's first real execution (#34) (c005350)
  • record whole-branch review outcome in the handover (#34) (fc3d231)
  • reframe README around the agent control plane (#70) (046c5dc)
  • release: claims matrix, compatibility matrix, upgrade guide, beta package (3a4a5af)
  • release: correct the commit inventory in the readiness report (f59a6ec)
  • release: Linux/amd64 is validated for CI-executed paths (ae12b9e)
  • release: pin the next release to 0.4.0 and explain why (#116) (dcbe1e1)
  • release: v0.1.0 changelog + dashboard screenshot in the README (07749b8)
  • review: adversarial verification of the RC readiness report (6545868)
  • revise automation-contract plan per external review (rev 2) (7db471f)
  • rls: document the runtime-role posture gates, bypass opt-out, and audit tenant floor (#32) (42dffd5)
  • scale job is green — record the fix and what it proved (#34) (e01e4ae)
  • session handover (state, running services, decisions, next steps) (29e2bcd)
  • spec for durable automation API contract + template clarity (96a5224)
  • surface just demo as the no-key first-run path (5b45838)
  • threat-model: two Phase F residuals narrowed, honestly (#34) (c1982d7)
  • update multi-user release README (646ac3d)
  • web: run-composer pickers — unleak connections, one card vocabulary, working + new (#42) (a9ead8f)
v0.4.0-rc.1
2026-07-30 (release candidate, not published)

A release candidate whose headline is a security fix, and whose second headline is that the fix is now provable without spending a cent on a model.

Security

  • The permission gate is now enforced for EVERY tool call on the Claude harness. It was not. canUseTool — the callback the runner had wired correctly the whole time — is not an interception point: the Agent SDK translates it into the Claude Code CLI's --permission-prompt-tool, which the CLI consults only for calls it decides to ask about. Anything it approved first (its read-only and safe-command classifications) executed with the callback never running and zero tool.requested/tool.decision events in the ledger. Measured with an unfabricatable nonce: an agent returned the SHA-256 of a value minted seconds earlier while the governing policy required a human approval that was never requested. Fixed with a mandatory, unscoped PreToolUse hook — upstream's own documented remedy — that answers ask, forcing every call back onto the existing, well-tested gate. A second layer, GateWitness, reconciles observed tool_use blocks against decisions and aborts the run (EXIT_UNGOVERNED_TOOL, no /result) if a result arrives for a call nothing decided.

What this changes for you: the gate now sees tool names it never saw before. See Upgrade notes.

  • **scripts/gate-proof.sh — the acceptance test this defect got past, and it costs nothing to run.** The suites that existed could not have caught it: governance-e2e kills the real runner and drives /permission itself (validates the server, structurally cannot validate the harness), and the only live-agent suite was workflow_dispatch-only and needed model credits. The new proof drives the real runner image and the real pinned CLI against a mock upstream that returns a canned tool_use and a mock control plane whose verdict each scenario chooses — then asserts on evidence that cannot be faked: a real filesystem side effect in the bind-mounted workspace, and, for read-only-classified commands, the digest of a freshly-minted nonce. 14 assertions including allow-path positive controls (without which every "nothing happened" result would be unfalsifiable), a held-verdict ordering proof, and five fail-closed variants. It runs on every pull request.
  • The eval quickstart no longer ships a working admin credential. FLUIDBOX_ADMIN_TOKEN defaulted to fluidbox-eval-only, a literal published in this repository, while the API port was published on all interfaces and the Docker socket was mounted into the server — so any host on the same network segment had full admin authority, and from there host-file disclosure via an operator local_copy workspace. The token is now required: docker compose up refuses to start without one. The dashboard moved to loopback. The API port cannot move to loopback (sandboxes are sibling containers reaching the control plane over host.docker.internal, the host gateway, so a loopback publish breaks every run) — that residual is now stated in the file and in the README instead of being implied away, and FLUIDBOX_EVAL_API_BIND lets you pin the interface. deploy/compose-assertions.sh guards all of it in CI; nothing guarded the compose files before.
  • Kubernetes: the NetworkPolicy enforcement race is closed at both layers. The certification probe sampled its two assertions once, at container start — inside the window where AWS VPC CNI standard mode has not yet programmed policy and fails open. Every retry created a fresh pod that re-entered the same window, so on the configuration scripts/eks-cluster.yaml prescribes the boot gate could never pass and **every POST /v1/sessions returned 503. Worse, the same window applied to real sandbox pods, so an untrusted runner could execute with unrestricted egress for its first seconds. Replaced with a bounded observation protocol (poll once per second; succeed only when positive-reachable and** negative-blocked hold in the same observation; fail closed at the deadline with the original exit-code contract) plus a new netpol-gate init container that is first in every sandbox pod, so the untrusted runner cannot start until the pod's own network is observed enforced. No fixed sleeps anywhere. Validated 12/12 on kind + Calico and 9/9 on real EKS 1.33, including reproducing the vulnerability natively before the fix.

Added

  • **just demo — a five-minute first run with no API key. A deterministic replay drives the real control plane, the real** policy gate, and a real sandbox container, ending in a real diff and cost report. Fully isolated from just dev: its own compose project, ports 19790/19791/15434, its own Postgres volume, state under .demo/. just demo-down removes everything.
  • **just gate-proof** — the gate proof above, runnable locally.
  • 23 tool names registered in the canonical vocabulary, each with an explicit seed-policy disposition (see Changed).
  • DB-native policies (§17 #11) — policies are now versioned, authorable, and attachable from the dashboard. Every edit is an immutable policy_versions row (author, summary, date; migration 0026); publish/revert/clone ride optimistic concurrency (base_version → 409 on a moved head) and a strict parser (an unknown field is a 422, never a silently-weaker policy); history is append-only at the database level (the runtime role can only read and append). The Governance page gains a draft rule editor (ordered rules, path/shell constraints, budgets/approvals/autonomy/egress forms), version history with diff and one-click revert, New policy (clone or blank), and Delete; the run composer gains a policy select and disables autonomy when the policy forbids it. managed_overrides folds into ordinary head rules (verdict-preserving, property-tested); just policy-sync is retired — YAML survives as a boot seed and an idempotent import/export format (POST /v1/policies).
  • **DELETE /v1/policies/{name}** — removes a policy and cascades its version history. Refused (409) while any agent revision names it, including historical revisions, which stay immutable. Runs are never affected: each froze its own policy snapshot.

Changed

  • The seed policy now states an opinion about every tool the pinned CLI advertises. A consequence of making the gate mandatory: 23 advertised tools had no rule, and an unmatched tool falls to defaults.tool_actionapprove, which pauses every supervised run, and deny in autonomous runs. They are now grouped by what they actually do: observational tools (EnterPlanMode, ExitPlanMode, AskUserQuestion, ReportFindings, Monitor, TaskGet/List/Output, CronList) are allowed; effects that outlive the run (CronCreate/Delete, ScheduleWakeup, PushNotification, SendMessage, TaskStop/Update, EnterWorktree/ExitWorktree) ask a human; DesignSync is denied with the other egress tools.
  • Sub-execution is DENIED by the seed policy. Agent, Task, Workflow, Skill, and TaskCreate start execution whose nested tool calls may never surface as top-level blocks — so they would be neither routed by the hook nor caught by the tripwire, which documents itself as a knowingly incomplete detector for exactly this case. approve would mean one human click authorising an unbounded, unobserved tool tree, so the seed fails closed instead. This also removes a latent bypass: the previous seed allowed Task, inert only because this CLI names the tool Agent — an allow-rule waiting for an upstream rename.
  • **just demo fails when the run fails. It exited 0** and printed a success-shaped receipt — including the self-refuting line "every tool call crossed the server-side gate: 0 decisions" — followed by a cheerful next-steps block, on a run that never completed. The watcher's deliberate "non-completed terminal state" code was being accepted as success, along with any unhandled exception in the watcher. It now prints a failure banner, names the terminal state, replaces next-steps with troubleshooting, and exits non-zero. The security receipt reports what was measured and says so loudly when zero decisions were recorded.
  • **just demo and the control plane now agree on which Docker daemon they are using. The preflight used the docker CLI** (which honours docker context); the server uses bollard (which reads DOCKER_HOST and otherwise the default socket, and does not know contexts exist). On any machine whose active context is not the default socket, preflight passed against one daemon and the run then failed against another with No such image. The endpoint is now resolved once and exported, so every docker call and the server inherit the same one.
  • Seed policy files are parsed STRICTLY, and an invalid one can refuse the boot. policies/*.yaml now goes through the same strict parser as the API, so an unknown key is an error rather than a silently-dropped field. The refusal is scoped to where it matters: if the policy does not yet exist in the database, the file is its only source and the server refuses to start (naming the file, the key, and the policy); if the policy already exists, its stored versions govern, the file writes nothing, and the server logs a warning and boots. Upgrading with a hand-edited policies directory: run POST /v1/policies/validate against each file first, or expect a startup warning.
  • **scripts/version-check.sh can express a prerelease.** It extracted the first X.Y.Z from each annotated line and compared exactly, so a canonical version of 0.4.0-rc.1 yielded 0.4.0 and the guard failed on all six annotated sites — i.e. just check and the CI version gate failed on precisely the versions a release is supposed to be staged through. The charset now matches the SemVer gate in release.yml.

Fixed

  • The replay-runner test suite ran in no pipeline (the CI node-test glob is single-level under images/runner-lib/). It now runs on every PR — it covers the driver that just demo puts in front of every new user.
  • Two gate-test mutations that fully restored the bypass passed the suite 12/12; a third, found during this pass, deleted the tripwire's only call site and still passed 14/14 (the assertion tested for the identifier's presence, which the function's own declaration satisfies). All three are closed and red-green verified.
  • .gitignore named .demo-bin/, a directory the demo never creates, leaving the real .demo/ state directory — which holds a live admin token — untracked but visible.

Upgrade notes

  • The seed policy does not re-apply to an existing deployment. seed_policy_if_absent never overwrites a stored policy, so the new rules land only on a fresh database. An existing deployment upgrading to this runner image will start seeing previously-invisible tool names arrive at the gate and fall to its own defaults.tool_action: every supervised run will pause on ordinary agent tooling, and autonomous runs will deny it. Before deploying, add the new rules to your policy — import policies/default.yaml with POST /v1/policies (it appends a version; byte-equal content is a no-op) or edit them on the Governance page.
  • **The eval compose now requires FLUIDBOX_ADMIN_TOKEN.** Any script that relied on the default will fail with a message naming the fix. Generate one: export FLUIDBOX_ADMIN_TOKEN=$(openssl rand -hex 32).
  • Migration 0026 drops policies.{parsed,yaml_source,managed_overrides,version}, so this is stop the old binary, migrate, then deploy (the 0018 posture) and there is no binary rollback past it — a pre-0026 binary refuses to boot against a 0026 database (migration 26 was previously applied but is missing in the resolved migrations), so the failure is loud rather than silent.
  • Helm, default values — no action needed. server.archiveStore: "" (the default) and values/eks.yaml render strategy: Recreate with replicas: 1, which satisfies stop-the-old-binary by construction: the old pod is fully terminated before the new one starts, so 0026 runs only after the old binary is gone. Cost is ~30s of downtime, not a correctness risk.
  • **Helm, server.archiveStore: "s3" — act before upgrading.** Only that configuration (the multi-replica shape) renders RollingUpdate, where old and new pods coexist. Scale the server Deployment to zero, upgrade, then scale back up. Note there is no values knob for this: the strategy is derived from archiveStore alone (values.yaml:57), so it cannot be forced to Recreate for one release, and patching the Deployment does not help because the same helm upgrade rewrites the field. Without the scale-to-zero, surviving old replicas answer policy queries with column "version" does not exist (42703) until they are replaced, and their in-flight transactions can block the migration's ACCESS EXCLUSIVE DDL against its 5s lock_timeout.
  • A managed_overrides entry naming a wildcard tool (e.g. mcp__*) is dropped with a warning rather than folded. Such an entry was unreachable — the retired engine matched overrides by exact name — and folding it into a rule would have matched the whole namespace. The API never allowed one, so this should log nothing.

Known limitations in this candidate

Stated here rather than discovered later. Full detail in [docs/reviews/release-candidate-readiness.md](./docs/reviews/release-candidate-readiness.md).

  • No live-model validation was possible. The available Anthropic key is out of credit (HTTP 400, confirmed directly and through the gateway), so no live Claude run was exercised for this candidate. The gate proof is a stronger witness for the security property and does not need a model, but it is not a substitute for "a real model completes a real task".
  • Nested sub-execution routing is untested, which is why the seed denies it.
  • **An older pinned runner_image on a newer server still routes nothing**, and there is no server-side detection of a terminal run with a non-empty diff and zero tool.decision events.
  • Supply chain unchanged: no lockfile for either runner image, npm install rather than npm ci, no Dependabot npm entry for the runner directories, and release artifacts are unsigned with no SBOM or attested provenance.
  • The two earlier EKS acceptances predate the NetworkPolicy fix and have not been re-run against this candidate.
v0.3.0
2026-07-24

Multi-user MCP control plane. Six phases (A–F) and migrations 00110025 turn fluidbox from a single-admin control plane into one that can host many organizations, many users, and many separately-owned credentials without ever letting a model pick an identity. Every hosted capability is opt-in behind a flag, and the default single-admin Docker deployment is byte-for-byte the same productFLUIDBOX_REQUIRE_SSO unset means today's behavior, unchanged.

The organizing idea: connector definition ≠ credential-bearing connection ≠ agent connection requirement ≠ per-run resource binding. An agent declares what it requires, never whose credential satisfies it. Run creation resolves each requirement to an explicit, frozen authority source before any model spend. The model picks tools; it can never pick an identity.

Highlights:

  • Per-organization, IdP-agnostic identityFLUIDBOX_REQUIRE_SSO=1 confines the admin token to /v1/admin/* as break-glass and introduces three principals: Operator (admin token), User (__Host-fbx_web session cookie), and Pat (fbx_pat_ bearer). Any conformant OIDC issuer is configured per org (issuer + client + sealed secret + claim mappings); logins are two-phase and browser-bound; sessions are server-side with idle/absolute/re-auth windows. No IdP configured ⇒ single-admin mode.
  • Tenant isolation with a database floor — every tenant-owned fluidbox-db method now takes a TenantScope that carries its id into a tenant_id = $n predicate, so isolation is a signature requirement rather than a remember-to-filter convention. Migration 0018 adds the floor underneath: 37 tables ENABLE+FORCE row-level security keyed on a transaction-local fluidbox.tenant_id GUC, with FLUIDBOX_RUNTIME_ROLE=fluidbox_runtime splitting the pool onto a non-owner role holding enumerated per-table grants. Cross-tenant access exists only through a short, named, grep-able set of audited bypasses.
  • Connection ownership and per-run resource bindings — brokered MCP tools moved off capability bundles onto four objects: catalog connector definition → connection (owns the credential, plus append-only tool snapshots) → agent-revision **connection_requirements → per-run run_resource_bindings** (migration 0013), resolved to a tagged authority (connection | subscription_secret | none) across typed slots (mcp | workspace_fetch | result_publish) before provisioning. Connections gained personal vs. organization ownership; a personal-connection approval is decidable only by its owner-who-invoked — no role, admin, or operator override.
  • Versioned envelope sealing with a real key-retirement path — migration 0014 makes every sealed column carry a _key_version companion: 1 is the legacy FLUIDBOX_CREDENTIAL_KEY format, 2 is a per-tenant DEK wrapped by a KEK (FLUIDBOX_KMS_MODE=off|static|aws) with AAD binding fbx:v2:{tenant}:{table.column} so a blob is untransplantable across tenants or columns. Thirteen sealed families; a resumable, CAS-guarded POST /v1/admin/reseal migrates v1→v2; two boot gates fail closed in both directions. Runbook: docs/hosted/kms-operations.md.
  • One hardened egress boundary for all control-plane traffic — two filtering-resolver clients plus a pure admit_url pre-flight that blocks private/loopback/link-local/multicast/reserved and cloud-metadata address classes at every dial site (reqwest dials an IP literal without consulting a resolver, so the pre-flight is what actually stops 169.254.169.254). Broker, delivery callbacks, and both connector-OAuth token legs ride a client that refuses redirects outright. Git gets its own out-of-process policy. FLUIDBOX_EGRESS_ALLOW_CIDRS opts specific CIDRs back in; FLUIDBOX_EGRESS_PROXY re-points everything, including the git subprocess, through one waypoint.
  • **MCP 2025-11-25 conformance, with version drift denying the call** — upstream MCP is now a per-run session (initialize + notifications/initialized before every call, MCP-Protocol-Version on every request, credential re-resolved live on the terminal DELETE). A run's negotiated version must match its frozen surface exactly or the call is denied with a message naming the refresh endpoint. SSE is a real incremental assembler with per-event and total ceilings; outputSchema/structuredContent are preserved.
  • Frozen tool schemas enforced server-side — arguments are validated against the schema photographed at freeze time, with the JSON Schema dialect chosen by the snapshot's protocol version (2025-11-25 ⇒ 2020-12 per SEP-1613, otherwise draft-07). The schema is untrusted input, so it is pre-guarded (size, depth, local-$ref-only) before compilation; a violation makes the tool un-callable rather than being silently ignored. This inserts exactly one new stage into the permission gate and moves nothing else.
  • At-most-once brokered dispatch — migration 0019 wraps every brokered call in a durable four-state execution claim keyed (session, tool_call_id, input_digest). failed_before_send requires positive proof nothing was written and is the only re-claimable state; a definitive upstream response is terminal; timeouts and mid-stream failures are recorded as ambiguous rather than retried. Decision idempotency and execution idempotency are now distinct properties.
  • Audience-scoped sandbox credentials — migration 0020 splits the sandbox's single bearer into four tokens (llm | tool | control | workspace), each checked as the first statement of its handler. Kubernetes ships one Secret with four keys routed per container, so the workspace init container never sees the others.
  • Replica coordination primitives — migration 0021: approval emission rides the deciding CAS inside one transaction (only the winner emits) with cross-replica pg_notify wakeups and the poll floor kept as a missed-notify backstop; sessions carry an orchestrator lease + epoch so a fenced-out driver cannot mutate lifecycle while a user's cancel stays deliberately unfenced; deliveries claim rows FOR UPDATE SKIP LOCKED, and the GitHub double-post window closes by reconcile-before-create on both comments and checks.
  • Durable LLM budget admission — migration 0022 replaces best-effort budget checks with a request-keyed reservation whose primary key is the usage entry's external id, which is what makes a 401 replay and a late drain idempotent. Booking uses a deliberately-high upper bound; release happens only on positively-proven non-dispatch; charging requires a durable usage write before the CAS.
  • Operations — a bounded-cardinality metrics registry at admin-gated GET /v1/admin/metrics (plus optional unauthenticated FLUIDBOX_METRICS_BIND), durable cross-replica egress governance and capacity ceilings (0023), cross-replica MCP session teardown (0024), workload identity (0025), S3-compatible archive storage alongside the filesystem backend, and a guarded load harness (fluidbox-loadgen) with its own manual scale CI job.

Validation: five hermetic acceptance suites green against CI-identical throwaway databases — identity 87/0, bindings 104/0, secrets 128/0, hardening 274/0, scale 18/0 = 611/0 — plus live Docker-provider tiers (demo A, Codex) and a second live EKS acceptance on arm64/Graviton with the runtime-role RLS split active and an AWS-audited zero-orphan teardown (docs/reviews/2026-07-22-eks-acceptance-phase-f.md).

Still deferred: the gated 60/150/300-seat load campaign and the final two rollout gates (owner approval + cost estimate) remain open on #34 — real spend, tracked separately from code. The hosted OAuth Connect flow also carries one documented residual: a deliberately-shared start URL can still route a victim's grant into the initiating connection, closed only by moving the browser-facing leg onto the dashboard origin (full write-up in docs/hosted/threat-model.md).

Added

  • Identity and access — per-org OIDC login (/v1/auth/*), logout, /v1/auth/me, PAT mint/list/revoke, org + IdP-config lifecycle and membership roles (/v1/admin/orgs*), break-glass owner arming, and staged issuer migration. All three token shapes (fbx_sess_, fbx_web_, fbx_pat_) are sha256-only at rest and scrubbed by the ledger redactor.
  • Dashboard SSO modeFLUIDBOX_WEB_MODE=admin|sso (static per deployment). In sso the proxy carries no admin token and forwards the session cookie plus a CSRF header on same-origin non-GETs; apps/web/proxy.ts redirects sessionless browsers to /login?next=… before first paint while authority stays in the control plane.
  • Per-tenant LLM keysFLUIDBOX_LLM_KEY_MODE=tenant (migration 0017) mints a per-tenant LiteLLM virtual key and confines the master key to provisioning; POST /v1/admin/orgs/{slug}/llm-key/rotate. Requires a LiteLLM backed by its own Postgres, so local deployments stay on shared.
  • Connection tool snapshots — a forced-initialize photograph per connection (GET /v1/connections/{id}/tools, POST /v1/connections/{id}/tools/refresh) recording the negotiated protocol version, with cursor caps fail-closed.
  • Hosted operator documentationdocs/hosted/: product compatibility matrix, threat model, network architecture, connector admission policy, rollout gates, and KMS operations runbook. Plus docs/guides/kubernetes.md, a zero-to-certified-cluster guide with real cloud acceptance costs and gotchas.

Changed

  • Brokered tools no longer ride capability bundles. capability_bundles survives for sandbox stdio tools only; registering a class:brokered server is refused with a cutover error. Migration 0013 appends converted agent revisions and repoints pinned subscriptions; a revision still pinning a brokered bundle is refused at run creation. Mixed brokered+sandbox bundles drop whole, with a raise-notice.
  • Custom connector-catalog entries are tenant-scoped. Curated and imported entries stay deployment-global; a tenant's custom entry shadows a same-slug global one. Migration 0013 backfills the single boot tenant, otherwise disabling the row.
  • Connector OAuth is a one-time, browser-bound flow. oauth/start returns only a go_url; navigating it sets a __Host- flow cookie whose hash sits inside the atomic single-use claim, so a leaked authorization URL can neither complete nor burn a flow. Endpoints, resolved client, resource, sealed PKCE verifier, and expected generation are frozen at start and the callback exchanges against that row. Client identities are shared per (issuer, redirect_uri), DCR singleflighted by advisory lock. The stateless seal_state/open_state helpers are gone.
  • **authorization_generation bumps on reconnect of ever-activated OAuth connections**, so stale-generation bindings refuse mid-run. Rotation within a generation is unaffected, and GitHub App lifecycle never bumps.
  • Multi-user boot now refuses a pool role that bypasses RLS (SUPERUSER/BYPASSRLS, e.g. Neon's default owner) unless FLUIDBOX_ALLOW_RLS_BYPASS=1; single-user only warns. just doctor inspects the role the server will actually run as and fails on every unbootable combination.

Security

  • Prompts still never reach the ledger, and the redactor now also scrubs every session, web-session, and PAT token shape.
  • The permission gate grew exactly one stage (frozen-schema argument validation) and reordered nothing: budget → frozen-set availability → schema → trust tier → policy → approvals.
  • Before any brokered secret access, binding status, authorization_generation, and — for personal connections — owner-membership-active are re-verified fail-closed.
  • Both connector-OAuth token legs ride the no-redirect client on purpose: a 307/308 replays the request body, which would forward an authorization code plus PKCE verifier, or a refresh token, to the redirect target. A source-grep test pins this.

Upgrading

Migration 0018 (RLS enforcement) is stop the old binary, migrate, then deploy — not a rolling upgrade. A pre-0018 binary sets no tenant GUC and would therefore see zero rows, and it holds transactions across outbound HTTP that would block the migration's ACCESS EXCLUSIVE locks.

Do not drop FLUIDBOX_CREDENTIAL_KEY when enabling FLUIDBOX_KMS_MODE: run POST /v1/admin/reseal and let boot prove zero remaining v1 rows first. From the moment any v2 row exists, the KEK is the root of custody and losing it is unrecoverable — back it up before enabling.

v0.2.0
2026-07-17

Kubernetes-native execution provider. Runs now execute as bare Pods in a dedicated, zero-egress sandbox namespace — additive to Docker (dual-provider permanence: Docker stays the default and fully supported). Highlights:

  • **FLUIDBOX_PROVIDER=kubernetes** — one Pod per run (init → runner → collector), per-run Secrets with ownerRef GC, UID-preconditioned mutations, immutable workspace archives pulled by the pod, and in-pod diff collection against a pristine .git baseline (agent-mutated git state is never executed).
  • Helm chart on OCIhelm install fluidbox oci://ghcr.io/hrishikeshdkakkad/charts/fluidbox --version 0.2.0 works out of the box: chart appVersion is bound to the release images at package time; digest pinning (images.*.digest) validated at render time; per-cloud presets (values/{eks,gke,aks,doks,kind}.yaml); Ingress routes / → dashboard, /v1 → API.
  • Verified network enforcement, fail-closed — a boot probe (carrying the sandbox's own placement) plus helm test must prove the CNI enforces NetworkPolicy (+:8788 / −:8787) before any run is admitted.
  • Durable finalization — every terminal path funnels through a persisted intent; collection happens before the terminal transition; crash-recovery re-drives interrupted finalizations; /result is no longer lossy. Fixes land on the Docker path too.
  • Self-healing reconciliation — a periodic adopt-or-terminate sweep heals crash windows (orphaned pods, handle-less sessions) in ≤60 s; node loss maps to Unknown instead of live-forever; rolling-deploy-safe strict status parsing.
  • Streaming archives with safety ceilings — pack/serve/download never hold the archive in RAM; FLUIDBOX_MAX_ARCHIVE_BYTES fails oversized runs at zero model spend (malformed caps fail boot); atomic .partial+rename writes; a session-state-aware TTL sweep reclaims leaks.
  • Hardening series — all 30 findings (5 High / 10 Medium / 15 Low) from a three-round joint Claude+Codex review of the epic fixed or explicitly dispositioned (docs/reviews/2026-07-16-pr47-k8s-review-findings.md): symlink-safe extraction with canonicalize as the sole containment authority, integrity-checked exec collection with resume, dual-listener isolation (no /internal on the public plane under K8s), UID-guarded deletes, quiesce replay, and more.
  • New crates/imagesfluidbox-workspace, fluidbox-provider-k8s, workspaced (+ the fluidbox-workspaced image, published multi-arch from this release); kind+Calico CI tier green on fresh installs.

Still deferred: live EKS acceptance + teardown (kind+Calico is CI-proven; one managed cloud remains the epic's acceptance bar).

Added

  • Connector-catalog bulk import (schema + tooling) — the catalog is now import-ready without importing a single row. A provenance column (migration 0009) makes every entry auditable and refreshable; curated seeds carry {"source":"fluidbox"} and can never be clobbered by an import. A new reference-only transport, rest_action, lets an imported entry that has no hosted MCP endpoint to photograph show up as a browsable Store card whose Connect is refused (400, "reference-only"); GET /v1/catalog now derives a connectable flag per entry so the dashboard can badge those cards. An offline dev tool, just catalog-import-registry (crates/fluidbox-catalog-import), imports from two Apache-2.0 sources: the official [MCP Registry](https://github.com/modelcontextprotocol/registry) (primary — real MCP servers; entries with a streamable-http remote import connectable today through the existing broker/photograph path) and [open-connector](https://github.com/oomol-lab/open-connector) (supplement — REST-only reference cards). It pages the Registry live (or from a pinned snapshot), keeps only active/latest servers, merges Registry-wins on slug collision, runs the SAME poison screen as capability registration over every imported string (offenders drop their whole entry), and emits a deterministic, append-only, sorted INSERT … ON CONFLICT migration of untrusted community-tier rows — each provenance-tagged with its source + pinned snapshot/commit. The tool never runs at boot or request time and is not in the server crate graph; attribution is recorded in NOTICE. The actual breadth (the generated import migration) is a separate, legally-gated merge.
  • Bring your own MCP server — a guided "Add your own server" flow on the Capabilities page: paste a URL, and a non-committing probe (POST /v1/mcp/probe) detects whether it needs no auth, an API key, or OAuth and previews its tools without storing anything or sending a secret; one confirm (POST /v1/mcp/servers) registers a tier=custom catalog entry and connects it in a single call, rolling the entry back if the connect fails so no orphan card survives. Bundle rows now expand to show their photographed tools.
  • Server-authoritative harness/model catalogGET /v1/harnesses is the single source of truth for the supported harness + model set; the dashboard's pickers fetch it instead of hardcoding models, and create_agent/add_revision now reject a model that doesn't belong to its harness with a clean 422 at agent-write time instead of a murky failure at the first model call.
  • CI now tells the truth — the rust job runs against a real Postgres service (the DB tests no longer silently self-skip), an e2e job builds both runner images and runs the full no-model acceptance suite (closes the vacuous-green gap of #14), and cargo deny check (advisories/licenses/bans/sources, deny.toml) gates the supply chain. The e2e job is manual-only (workflow_dispatch) — it costs real Actions minutes, so it never runs on a PR or push; the cheap gates (rust/web/deny) still run on every PR. Live model tiers stay local/manual — CI never spends credits. Coverage (lcov artifact) runs on main pushes.
  • Property tests for the policy engine — generated-input invariants in fluidbox-core: an autonomous run can never surface RequireApproval, autonomy rewrites exactly the approval verdicts (original always ledgered), the read-only tier denies any shell metacharacter and any unlisted tool, shell prefixes are token-bounded, first match wins.
  • Try-it-with-Docker distributiondeploy/server.Dockerfile + deploy/web.Dockerfile (Next standalone output), a release workflow publishing multi-arch images to GHCR on version tags or manual dispatch, and deploy/docker-compose.eval.yml: bundled Postgres + LiteLLM + server + dashboard in one docker compose up.
  • User guides (docs/guides/) — writing policies, triggers/schedules/signed results (with the HMAC verification recipe and a pinned test vector), and capabilities (sandbox vs brokered MCP tools, pinning, the connector catalog).
  • **ROADMAP.md** — the public distillation of PLAN.md §7.
  • **just setup** — one-command idempotent bootstrap for a fresh clone: tools check, .env with generated secrets (FLUIDBOX_ADMIN_TOKEN, FLUIDBOX_CREDENTIAL_KEY, LITELLM_MASTER_KEY), dashboard env (apps/web/.env.local) kept in sync, pnpm install, and the sandbox runner image build. Only fills placeholders — never overwrites values you set.
  • **just doctor** — environment preflight (#13): validates every documented gotcha (pooled vs direct DATABASE_URL, loopback FLUIDBOX_BIND, credential key shape, missing runner images, dashboard token drift, missing web deps) and prints the exact fix per failure; exits non-zero only on hard failures, never echoes secret values.

Changed

  • just neon-setup now writes the DIRECT connection string into .env when DATABASE_URL is still the placeholder (an existing value is never clobbered).
  • README quickstart, CONTRIBUTING dev setup, and the dashboard README (apps/web/README.md) rewritten around the just setupjust neon-setupjust dev flow.
v0.1.0
2026-07-12

The first tagged release: the complete governed vertical slice, verified by a 10-phase live-inclusive acceptance suite (468 checks).

Highlights

  • Governed agent runs end to end — frozen RunSpecs, fresh sandboxes, live timelines, policy-gated tool calls with human approvals, and a diff + cost report per run.
  • Two harnesses behind one contract — Claude Agent SDK and Codex, with an in-server LLM facade that meters usage and keeps provider keys out of every sandbox.
  • Borrow the agent, on demand — API triggers, signed webhooks, cron schedules, and GitHub PR fan-out, all converging on one governed run path.

Added

  • Governed runs end to end — versioned agent definitions, immutable per-run RunSpec snapshots (model, prompts, policy, capability pins), fresh Docker sandboxes per run, live SSE event timelines with Last-Event-ID resume, and a final diff + cost report.
  • Policy engine & human approvals — YAML policies evaluated on every tool call (allow / deny / require-approval), idempotent restart-safe approvals with expiry, and an autonomous mode that rewrites approval verdicts to a policy fallback while recording both verdicts.
  • Append-only audit ledger — redaction enforced at the type level; prompts never reach the database, only digests, usage, cost, and decisions, with gapless per-session sequencing.
  • Two agent harnesses — Claude Agent SDK and Codex runner images behind one HTTP runner contract; the LLM facade speaks both the Anthropic Messages and OpenAI Responses dialects.
  • Credential inversion — the sandbox's ANTHROPIC_API_KEY is a session token; an in-server LLM facade validates it, enforces budget stops, meters streamed usage, and swaps in the real upstream credential held only by the LiteLLM gateway.
  • Git workspaces — credentialed fetch/copy happens control-plane-side before the agent starts; sandboxes only ever see a bind-mounted copy and stay egress-free.
  • Triggers — subscription-scoped API tokens, signed webhook ingress with two-level dedup that heals partial fan-outs, cron schedules with exactly-once firing and explicit missed-run/concurrency policies, and HMAC-signed result delivery with retry/backoff.
  • GitHub integration — seamless GitHub App connect (manifest + install flows), PR fan-out with one stable comment per PR and one check per head SHA, and fork PRs frozen to ReadOnly trust with no approval escape.
  • Capability catalog — append-only versioned MCP tool bundles pinned at run creation; sandbox tools run as contained stdio subprocesses while brokered tools execute on the control plane with sealed credentials the sandbox never sees.
  • Connector catalog + OAuth — catalog-driven connect flows with PKCE (S256), RFC 8707 resource indicators, DCR/CIMD client identity, sealed refresh tokens with atomic rotation, and fail-closed error states.
  • Dashboard — Next.js UI (Runs, Agents, Integrations, Automations, Settings); presentation-only, all logic in the Rust API.
  • CLIfluidbox run --repo … --task … to drive runs from the terminal.
  • Opsjust recipes for the full dev loop, an end-to-end acceptance suite (just e2e), Neon setup and DB-cleanup scripts, and CI (fmt, clippy -D warnings, tests, dashboard build).

Changed

  • Dependency refresh: sha2 0.11, hmac 0.13, chacha20poly1305 0.11, jsonwebtoken 10 (pinned to the pure-Rust rust_crypto provider), React 19.2.7, TypeScript 6, and current GitHub Actions. The sealed-credential wire format (nonce ‖ ciphertext) is unchanged — existing sealed credentials open fine.