Skip to content
Open
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
10 changes: 9 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ conformance tests on BOTH sides. Never hand-edit one side of a contract.
| lody local-project registration | box node `box/rootfs/usr/local/libexec/blitz-lody-projects` (registers each `/workspace/<repo>` clone) ↔ browser `webapp/src/lody/local-bridge.ts` + `rpc-client.ts` + `local-projects.ts` (`registerWorkspaceRepositories`, the same sweep driven from the tab) ↔ the `lody` daemon's `/project-control` (not in this tree). The SCHEMA stays Lody's (`vendor/lody/packages/shared/src/message-schemas.ts`, `LocalProjectControlRequest`/`Response`); what is ours is that two BlitzOS producers keep agreeing with it | `fixtures/lody-project-registration/` (responses captured from a real `lody@0.88.1`) | `box/guest-tests/test/lody-projects-registration.test.ts` (runs the real registrar against a stand-in daemon socket), `webapp/test/lody-project-control-frames.test.ts` (browser producer/parser) |
| lody session-control stream | browser `webapp/src/lody/rpc-client.ts` (`sendSessionControl`) ↔ node `box/rootfs/usr/local/libexec/blitz-lody-bridge` ↔ the `lody` daemon's `/session-control` (not in this tree). The daemon picks NDJSON-per-response or one buffered envelope from the request's `Accept`; ours is the browser that negotiates and reads it frame by frame, and the bridge decision that carries the negotiation upstream. The FRAME UNION stays Lody's (`vendor/lody/packages/shared/src/node/local-ipc.ts:80`, `{kind:'response'\|'complete'\|'error'}`) — it is not exported and its module is node-only, so `rpc-client.ts` re-states it and the corpus keeps the copy honest | `fixtures/lody-session-control-stream/` (bodies captured from a real `lody@0.88.1` through the real bridge) | `webapp/test/lody-session-control-stream.test.ts` (browser consumer: frames emitted before the promise settles, at adversarial chunk boundaries), `box/guest-tests/test/lody-bridge-control-stream.test.ts` (runs the real bridge against a stand-in daemon that holds its stream open), `webapp/test/lody-acp-authentication.test.ts` (whole chain against a real daemon; skips without the bundle) |
| lody share claim | Go gateway `box/gateway/main.go` (verifies the webApp ticket's `share` claim and forwards it on `X-Blitz-Lody-Share`, stripping any inbound copy) ↔ node `box/rootfs/usr/local/libexec/blitz-lody-bridge` (room ACL on `/sync`, session scoping on `/rpc` and `/project`, `/control` refused, `/platform` narrowed). The claim's OWN wire format is pinned by the webApp-ticket corpus on three runtimes; what this pins is the hand-off and the decisions the bridge makes from it | `fixtures/lody-share-claim/` | `gateway/main_test.go` (producer: the header bytes + the path allowlist), `box/guest-tests/test/lody-bridge-share.test.ts` (consumer: runs the real bridge against a stand-in daemon over the whole decision table) |
| box config v1 | CP `core/box-config.ts` producer (`GET /workspaces/self/box-config`) and consumer (`POST /workspaces/self/box-update-result`) ↔ host updater bash/python emitted by `core/bootstrap.ts` (`blitz-box-update`; cloud-VM path only — the microVM provider has its own guest lifecycle and no update path yet) | `fixtures/box-config/` | `test/box-config-conformance.test.ts` (CP), `test/box-update-conformance.test.mjs` (runs real `python3` over the emitted parser/producer, `bash -n` over the emitted scripts), `test/box-update-host.test.mjs` (runs the emitted updater in real bash against a live CP over real curl) |
| box config v1 | CP `core/box-config.ts` producer (`GET /workspaces/self/box-config`) and consumer (`POST /workspaces/self/box-update-result`) ↔ host updater bash/python emitted by `core/bootstrap.ts` (`blitz-box-update`, which shells out to the emitted `blitz-box-image` for the manifest install; cloud-VM path only — the microVM provider has its own guest lifecycle and no update path yet). The config carries `boxImageSha256`, the deployment's own pin of the whole archive, so the host's check does not rest on the digest the manifest declares about itself. The result body carries an optional `tag`: the CONCRETE image the container runs once the attempt settled, because `ref` alone cannot answer "is an update available" under a manifest pin whose URL never changes between rebakes | `fixtures/box-config/` | `test/box-config-conformance.test.ts` (CP), `test/box-update-conformance.test.mjs` (runs real `python3` over the emitted parser/producer, `bash -n` over the emitted scripts), `test/box-update-host.test.mjs` (runs the emitted updater in real bash against a live CP over real curl, including the manifest install and the credential rotation) |

Retired 2026-08-29: the `ACP` contract (box actor ↔ ui chat reducer,
`fixtures/acp/`). The native-chat surface, the box actor on port 7444, its
Expand Down Expand Up @@ -203,6 +203,14 @@ The four rules a change must not break:
read files at runtime. Its emitted bytes are a contract pinned by tests.
Do not edit the emitted script casually. Extraction to build-time text
imports is an approved future direction (see issue #1 discussion).
- **The emitted script has a hard 32 KiB budget.** Hetzner caps cloud-init
user-data at `HETZNER_USER_DATA_MAX_BYTES` and does not compress (AWS
gzips and has room to spare), so every byte of emitted bash costs. A heavy
manifest-mode create is ~30.4 KiB of that today and `test/bootstrap.test.ts`
pins a 2 KiB floor. Reasoning belongs in the TS comment, which never ships;
the emitted script carries only what bash must read. Buying real headroom
back means shipping the host scripts in the box image instead of in
user-data — see `plans/MEMBER-MACHINES.md` §5a.

## Box image: canary from R2, client prod from GHCR

Expand Down
Loading
Loading