Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ body:
- control-plane
- webapp
- box
- broker
- schema / fixtures
- deploy tooling
- docs
Expand Down
1 change: 0 additions & 1 deletion .github/ISSUE_TEMPLATE/feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ body:
- control-plane
- webapp
- box
- broker
- schema / fixtures
- deploy tooling
- docs
Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
- [ ] `npm test` passes
- [ ] `lint-baseline.json` was not raised (and was lowered if this change removes findings)
- [ ] Any new payload crossing a runtime boundary has fixtures under `packages/schema/fixtures/` and conformance tests on both sides
- [ ] Go changes: `go test ./...` passes in the affected module(s) (`packages/broker`, `packages/box/gateway`)
- [ ] Go changes: `go test ./...` passes in each affected box Go module
- [ ] Docs updated where behavior or setup changed (`docs/`, package READMEs)
2 changes: 1 addition & 1 deletion .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ jobs:

# Feature flags are deployment config, not image inputs. The repository
# root remains the build context because the Dockerfile also copies the
# broker and schema fixtures.
# credential helper and schema fixtures.
- name: Build the box image
if: steps.plan.outputs.published == 'false'
env:
Expand Down
21 changes: 9 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,25 +129,22 @@ jobs:
env:
BLITZDEV_MANAGED: "1"

broker:
name: Go broker
credential-helper:
name: Go box credential helper
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: packages/broker/go.mod
cache-dependency-path: packages/broker/go.mod
go-version-file: packages/box/credential-helper/go.mod
cache-dependency-path: packages/box/credential-helper/go.mod
- run: test -z "$(gofmt -l .)" || { gofmt -d .; exit 1; }
working-directory: packages/box/credential-helper
- run: go test ./...
working-directory: packages/broker
working-directory: packages/box/credential-helper

# The gateway's Go suite had been RED and nobody saw it: this job runs
# `go test` for `packages/broker` only, and the gateway is compiled by the
# box-image build, which does not run its tests. Phase 6 fixed the suite in
# passing; this job is what stops it happening again. It also holds one half
# of two cross-runtime contracts — the webApp ticket and the lody share claim
# — so a fixture added on the TS side and missed here fails in CI rather than
# on a box.
# The gateway has a separate test job because an image build only compiles it.
# This job also checks the webApp ticket and Lody share contracts.
gateway:
name: Go box gateway
runs-on: ubuntu-latest
Expand Down
16 changes: 0 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ jobs:
runs-on: ubuntu-latest
outputs:
box-digest: ${{ steps.box.outputs.digest }}
broker-digest: ${{ steps.broker.outputs.digest }}
image-owner: ${{ steps.namespace.outputs.owner }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
Expand Down Expand Up @@ -63,19 +62,6 @@ jobs:
ghcr.io/${{ env.IMAGE_OWNER }}/blitz-box:${{ github.ref_name }}
ghcr.io/${{ env.IMAGE_OWNER }}/blitz-box:latest

- name: Build and push broker
id: broker
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: .
file: packages/broker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
provenance: true
tags: |
ghcr.io/${{ env.IMAGE_OWNER }}/blitz-broker:${{ github.ref_name }}
ghcr.io/${{ env.IMAGE_OWNER }}/blitz-broker:latest

# Deploys the control plane pinned to the box image this run just pushed, so
# a version tag is a complete release with no manual publish step. Opt-in via
# two repository secrets (docs/BOX-IMAGE.md § Automatic releases); without
Expand Down Expand Up @@ -217,7 +203,6 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BOX_DIGEST: ${{ needs.images.outputs.box-digest }}
BROKER_DIGEST: ${{ needs.images.outputs.broker-digest }}
IMAGE_OWNER: ${{ needs.images.outputs.image-owner }}
steps:
- name: Create or update release notes
Expand All @@ -239,6 +224,5 @@ jobs:
printf '%s\n\n' "$kept"
printf '## Container image digests\n\n'
printf -- '- `ghcr.io/%s/blitz-box:%s` — `%s`\n' "$IMAGE_OWNER" "$tag" "$BOX_DIGEST"
printf -- '- `ghcr.io/%s/blitz-broker:%s` — `%s`\n' "$IMAGE_OWNER" "$tag" "$BROKER_DIGEST"
} >"$RUNNER_TEMP/release-notes.md"
gh release edit "$tag" --repo "$GITHUB_REPOSITORY" --notes-file "$RUNNER_TEMP/release-notes.md"
52 changes: 29 additions & 23 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ npm test # control-plane, box guest tests, ui, guest node:test,

## Known debt (as of 2026-08-18)

- 66 anti-slop findings remain, all Tier C: external-boundary code that
needs real parsers (23 no-unknown-parameters, 27 no-runtime-typeof in
plain JS, 12 no-unsafe-dictionary-type, 4 no-unknown-returns). The counts
fell from 74 (31/27/12/4) on 2026-09-02 when the box credential wire and
the workspace credential store were deleted (plans/ORG-CREDENTIALS.md),
and before that from 102 (47/27/22/6) on 2026-08-29 when the native-chat
surface and the box actor were deleted; the baseline moved with them. Fixing one
requires characterization tests FIRST — these fixes can change accepted
inputs. Plan and history: GitHub issue #1.
- 42 anti-slop findings remain. All are Tier C external boundaries that need
real parsers (11 no-unknown-parameters, 21 no-runtime-typeof in plain JS,
8 no-unsafe-dictionary-type, 2 no-unknown-returns).
The count fell from 43 when the control-plane broker registry was deleted.
Earlier credential changes lowered the count from 74 on 2026-09-02.
Native chat deletion lowered it from 102 on 2026-08-29.
The baseline moved with each change. Add characterization tests before a
fix because these fixes can change accepted inputs.
Plan and history: GitHub issue #1.
- 6 `TODO(deslop-tier-c):` markers flag type assertions whose invariant is
not actually enforced today (latent-bug candidates). Grep for the marker.
- `TODO(house-canon):` markers flag direct fetch/console sites awaiting
Expand Down Expand Up @@ -93,7 +93,17 @@ tests are deleted. Restoring recipes means rebuilding that delivery, not
just remounting the routes. The box's device-code `enroll` service and the
`blitz-cred enroll` verb went in the same change: every provisioned box gets
its credential from phone-home before the container starts, so the service
had no path left to run (the broker VM keeps the shared device-flow client).
had no path left to run.

Retired 2026-09-05: the credential broker. Its daemon, image release, SSH
custody, and box `register`, `token`, and `watch` paths are deleted.
The control-plane registry routes and broker wire types are also deleted.
Migration 0053 drops `broker_keys`, `broker_members`, and `broker_boxes`.
It also drops `boxes.is_broker`, `boxes.broker_box_id`, and
`machines.broker_box_id`.
Claude and Codex now use their native authentication stores.
The box-owned `blitz-cred api-token` survives with machine token families and
the `/agent/*` plane. Restoring the broker means rebuilding it, not re-enabling it.

Retired 2026-09-05: the Org Drive and usage-capture surfaces. Their D1 tables,
R2 object flows, WebDAV synchronizer, cron, schemas, routes, and webApp screens
Expand All @@ -107,8 +117,10 @@ deleted. Workspace repository cloning remains under workspace-repository names.
Retired 2026-09-05: permissive create-workspace and phone-home compatibility.
Create requests now reject legacy machine, template, SSH, environment, and
folder fields. Phone-home accepts canonical fields and returns only box and
token fields. Deployed-box token families, `/boxes/:id/feed`, the constant
workspace environment route, box-config v1, tunnel access, and port 7444 remain.
token fields. Deployed-box token families, box-config v1, tunnel access and
port 7444 remain. `/boxes/:id/feed` and the constant workspace environment
route were still listed here on 2026-09-05; the credential-broker retirement
below deleted both, because the broker was the only caller of either.

Retired 2026-09-05: the `/integrations` API and `/settings/integrations` UI
aliases. Canonical connection routes remain.
Expand Down Expand Up @@ -185,17 +197,11 @@ agent must not undo:
that used to hold it pinned the workspace owner, and that is the bug the
structure now prevents.

Three compatibility surfaces are load-bearing and have no expiry date yet:
`GET /boxes/:id/feed` (served from `machines`),
`GET /workspaces/:id/environment` (a constant `{env:{}, startupScript:null,
filesReady:true}`, because deployed brokers poll it every second at boot and
wait for exactly those three fields), and the token families migration 0041
copied hash-for-hash so no deployed guest had to re-enrol.

The `workspace environment` cross-runtime contract is retired with its fixtures
and both conformance tests: no runtime reads the route any more, so what remains
is that constant three-field shim, pinned alone by
`control-plane/test/workspace-environment.test.ts`.
The token families migration 0041 copied each hash without changes.
This kept deployed guests enrolled during the member-machines change.

The workspace environment contract, route, fixtures, and conformance tests are
deleted. No live box needs that compatibility path.

Every field of `WorkspaceView` is required, including `members`,
`credentials`, `myRole`, `defaultMachineTypeId` and `autoProvision`. The only
Expand Down
16 changes: 8 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ CLAUDE.md wins.
## Setup

- Node.js 22.13 or newer (`engines` in the root `package.json`) and npm.
- Go 1.26+ for the Go components (broker and box gateway).
- Go 1.26+ for the box credential helper and gateway.
- Docker for box-image work.

```sh
Expand Down Expand Up @@ -50,11 +50,11 @@ pin which boundary, and which tests enforce them — is in

## Go components

Two Go modules sit outside the npm workspace graph and are not touched by
`npm test`. Test them directly:
Two Go modules sit outside the npm workspace graph. `npm test` does not run them.
Test them directly:

```sh
(cd packages/broker && go test ./...)
(cd packages/box/credential-helper && go test ./...)
(cd packages/box/gateway && go test ./...)
```

Expand All @@ -81,13 +81,13 @@ subsystem when one applies.
`.github/workflows/ci.yml`, on every push and pull request:

- **JavaScript**: `npm ci`, then the three gates.
- **Go broker**: `go test ./...` in `packages/broker`.
- **Go box credential helper**: `go test ./...` in `packages/box/credential-helper`.
- **Box image**: an amd64 `docker build` of `packages/box/Dockerfile` as a
build check (no push).

Pushing a `v*` tag runs `.github/workflows/release.yml`, which builds and
publishes the box and broker images for amd64 **and** arm64 — so an
amd64-only CI pass does not guarantee the arm64 release build.
Pushing a `v*` tag runs `.github/workflows/release.yml`.
It builds the box image for amd64 and arm64.
An amd64-only CI pass does not guarantee the arm64 release build.

## Design records

Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ Follow the [self-host guide](docs/SELF-HOST.md).
- [`box`](packages/box/README.md) the complete workspace runtime: SSH, Docker, agent harnesses, terminal, files, and previews.
- [`control-plane`](packages/control-plane/README.md) workspaces, member machines, roles, credential injection, volumes, and compute providers.
- [`webApp`](packages/webapp/README.md) the browser webApp for creating, configuring, sharing, and working inside workspaces.
- [`broker`](packages/broker/README.md) short-lived Claude and Codex credential delivery for workspace fleets.
- [`schema`](packages/schema/README.md) shared wire types and cross-runtime conformance fixtures.

## Docs
Expand All @@ -65,7 +64,7 @@ Follow the [self-host guide](docs/SELF-HOST.md).
- [Box image](docs/BOX-IMAGE.md) build, publish, and upgrade the workspace image.
- [Contributing](CONTRIBUTING.md) the three gates, the lint ratchet, fixtures, commit style.
- [Security](SECURITY.md) reporting, secret blast radius, the workspace trust model.
- Packages: [box](packages/box/README.md) · [control-plane](packages/control-plane/README.md) · [webapp](packages/webapp/README.md) · [broker](packages/broker/README.md) · [schema](packages/schema/README.md)
- Packages: [box](packages/box/README.md) · [control-plane](packages/control-plane/README.md) · [webapp](packages/webapp/README.md) · [schema](packages/schema/README.md)

## Roadmap

Expand Down
8 changes: 4 additions & 4 deletions docs/BOX-IMAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ the `canary` environment:

The base release id deliberately excludes payload-owned files, the gateway
binary, the daemon, and the repository `env.defaults`. It includes the
base-owned credential broker sources. The Dockerfile owns the box defaults and
base-owned credential helper sources. The Dockerfile owns the box defaults and
writes a comment-only `/etc/blitz/env.defaults` for deployed hosts that still
pass it with `--env-file`. A payload-only merge therefore reuses the current image. Its baked stamp
names only the bytes in the baked payload and may name the payload current when
Expand Down Expand Up @@ -194,9 +194,9 @@ Hetzner types only. Adding an arm type means revisiting this.

## Mode A: publish to a registry

Pushing a git tag `v*` runs `.github/workflows/release.yml`, which builds
`blitz-box` (and `blitz-broker`) for `linux/amd64` and `linux/arm64` and
pushes them to GHCR under your repository owner:
Pushing a git tag `v*` runs `.github/workflows/release.yml`.
It builds `blitz-box` for `linux/amd64` and `linux/arm64`.
It pushes the image to GHCR under your repository owner:

```sh
git tag v0.1.0
Expand Down
2 changes: 1 addition & 1 deletion docs/DEPLOY-RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ npx wrangler deployments list --config packages/control-plane/wrangler.toml
A `v*` tag is the only path. `.github/workflows/release.yml` holds the
credentials and does the whole job, in this order:

1. Builds and pushes the box and broker images for amd64 and arm64.
1. Builds and pushes the box image for amd64 and arm64.
2. Waits for a human to approve the `production` environment.
3. Writes the deployment config from a repository secret.
4. Pins the box image digest it just built.
Expand Down
2 changes: 1 addition & 1 deletion docs/LODY-MERGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ cd ../..
npm run typecheck
npm run lint:gate
npm test
( cd packages/broker && go test ./... )
( cd packages/box/credential-helper && go test ./... )
( cd packages/box/gateway && test -z "$(gofmt -l .)" && go test ./... )
git diff --check
```
Expand Down
14 changes: 6 additions & 8 deletions docs/LODY-MODELS.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,18 @@ vendored static list was never consulted.

### What was blocking it (removed 2026-09-01)

`DISABLE_AUTOUPDATER=1` had been set in four places — the image-wide `ENV` in
`DISABLE_AUTOUPDATER=1` had been set in four places.
Three box places were the image-wide `ENV` in
`packages/box/Dockerfile`, the PATH shim `rootfs/usr/local/bin/claude`,
`rootfs/etc/profile.d/blitz-npm.sh`, and `broker/internal/vendor/vendor.go`,
which stripped any inbound value and force-appended `=1` (asserted by a test in
`roaming_test.go`). Four sites because they are four different process-spawn
paths: s6 daemons inherit the image ENV, login shells rebuild from
`/etc/profile`, the shim covers any invocation, and the broker constructs the
child environment from scratch rather than inheriting it.
and `rootfs/etc/profile.d/blitz-npm.sh`.
These sites cover s6 daemons, login shells, and direct commands.
The now-retired broker set the fourth site in its spawn environment.

The flag gated the **background** update check only — the explicit `claude
update` subcommand ignored it, which is why the run above worked with the flag
live in the environment.

All four are gone, `codex`'s shim now passes
All four are gone. `codex`'s shim now passes
`-c check_for_update_on_startup=true`, and `@anthropic-ai/claude-code` is
installed `@latest` at build time rather than pinned. Nothing holds a CLI
version anymore.
Expand Down
5 changes: 0 additions & 5 deletions env.defaults
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# broker

# absolute path: Persistent state directory used by the broker daemon.
BLITZ_BROKER_STATE_DIR=/var/lib/blitz-broker

# control-plane (Cloudflare Worker vars are documentation-only here; wrangler.toml owns their values.)

# ALLOWED_EMAIL_DOMAINS (string): Comma-separated bare email domains; empty allows any domain on every sign-in; wrangler.toml supplies the runtime value.
Expand Down
2 changes: 1 addition & 1 deletion lint-baseline.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"anti-slop/no-unknown-parameters": 12,
"anti-slop/no-unknown-parameters": 11,
"anti-slop/no-runtime-typeof": 21,
"anti-slop/no-unsafe-dictionary-type": 8,
"anti-slop/no-unknown-returns": 2,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"provider:check": "npm run provider:check --workspace @blitzos/control-plane --",
"openapi:generate": "npm run openapi:generate --workspace @blitzos/control-plane",
"typecheck": "npm run typecheck --workspaces --if-present",
"test": "npm run test --workspaces --if-present && node --test tools/oxlint/blitz-house/*.test.mjs && sh packages/broker/deploy/provision-broker.test.sh"
"test": "npm run test --workspaces --if-present && node --test tools/oxlint/blitz-house/*.test.mjs"
},
"devDependencies": {
"@oxlint/plugins": "1.79.0",
Expand Down
18 changes: 8 additions & 10 deletions packages/box/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
FROM --platform=$BUILDPLATFORM golang:1.26.5-bookworm@sha256:53eeac89074db483fdf0ab3be1df32bf6e47562263d2d0d6baa7f26acb4957dd AS cred-build

ARG TARGETARCH
WORKDIR /src/packages/broker
COPY packages/broker/go.mod ./
COPY packages/broker/cmd/blitz-cred ./cmd/blitz-cred
COPY packages/broker/internal ./internal
WORKDIR /src/packages/box/credential-helper
COPY packages/box/credential-helper/go.mod ./
COPY packages/box/credential-helper/cmd/blitz-cred ./cmd/blitz-cred
COPY packages/box/credential-helper/internal ./internal
RUN CGO_ENABLED=0 GOOS=linux GOARCH="$TARGETARCH" go build -buildvcs=false -trimpath -ldflags="-s -w" -o /out/blitz-cred ./cmd/blitz-cred

FROM --platform=$BUILDPLATFORM golang:1.26.5-bookworm@sha256:53eeac89074db483fdf0ab3be1df32bf6e47562263d2d0d6baa7f26acb4957dd AS gateway-build
Expand Down Expand Up @@ -194,19 +194,17 @@ RUN set -eux; \
ln -s /workspace /srv/blitz-files/workspace; \
chown -R blitz:blitz /opt/blitz/npm

# /usr/local/bin comes FIRST, ahead of the npm prefix, so the PATH shims at
# /usr/local/bin/claude and /usr/local/bin/codex win over the real binaries they
# exec — and so a copy a member installs into their own writable prefix cannot
# shadow them. That shadowing is the single most common way a terminal ends up
# signed out while the same box holds a valid credential.
# /usr/local/bin comes first, so the stable Claude and Codex entry points win.
# The Codex shim selects native login paths.
# The Claude shim starts the native CLI without changing authentication.
#
# THE VENDOR CLIs UPDATE THEMSELVES, and that is the point. A new Anthropic
# model reaches the Lody composer only when the `claude` binary is new enough to
# report it in its ACP `initialize` response — every layer above is passthrough
# (docs/LODY-MODELS.md). Holding the CLI at a build-time version therefore held
# the model list at build time too, and made a box-image rebake the delivery
# mechanism for models. It no longer is: `DISABLE_AUTOUPDATER` is gone from the
# image, the shims, the profile and the broker, and codex's shim now leaves its
# image, the shims, and the profile. Codex's shim now leaves its
# startup update check on.
#
# What still protects the shims is the PATH order above, not a pin: an update
Expand Down
Loading
Loading