release: domain migration prep + connector self-update 0.1.27 + bridge-protocol hardening#187
Merged
Conversation
Switch the production TLS edge from manual cert files to automatic ACME so a public domain can be changed without hand-provisioning certificates, and serve a primary + legacy host in parallel during a cutover. - docker/Dockerfile.caddy: custom Caddy build with the Cloudflare DNS plugin, for the ACME DNS-01 challenge (works behind Cloudflare's proxy, auto-renews). - docker/Caddyfile: shared reverse-proxy snippet imported by an APP_DOMAIN + optional APP_DOMAIN_LEGACY site block; tls now uses dns cloudflare instead of hardcoded cert files. - docker-compose.production.tls.yml: tls-edge builds the custom image; drop the ./certs mount and TLS_CERT_FILE/KEY env; add APP_DOMAIN_LEGACY, ACME_EMAIL, CF_API_TOKEN. - docs: document the DNS-01 flow, the dual-domain migration vars, Cloudflare Full(strict), and the grey-cloud/stock-image fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…auto-rollback)
Connector (0.1.27):
- New [update] TOML section, default OFF — executing downloaded code is the
host owner's explicit call. CHEERS_ACP_NO_SELF_UPDATE=1 force-disables;
containers never self-update (image rebuilds own that path).
- self_update module: on a gateway hello advertising a newer release, download
connector-manifest.json(.sig) through the gateway's release proxy, verify the
manifest's ed25519 signature against the release key compiled into the binary
(public_key_file override for forks), verify each binary's sha256, stage,
drain in-flight prompt turns (BusyGuard in run_task), atomically swap the
connector + sibling cheers-mcp-server, and re-exec with the same argv/PID.
- Rollback rail: previous binary kept as <exe>.old; a marker counts boots of
the new binary and startup restores the old one after 3 boots without a
healthy bridge connection, blocking that version from retry.
Gateway:
- hello server_capabilities now advertises latest_connector_version (from
CHEERS_CONNECTOR_RELEASE_VERSION).
- ready handler persists the connector's reported version; GET /bots/{id}/status
returns connector_version / latest_connector_version / update_available.
- Download proxy allowlists connector-manifest.json(.sig).
- Enrollment-rendered TOML carries a commented [update] hint (older connectors
deny unknown config sections).
CI: new manifest job on connector-v* tags — sha256s all release assets, signs
the manifest with the CONNECTOR_SIGNING_KEY repo secret (ed25519; public half
committed at packages/cheers-acp-connector-rs/release-signing-pubkey.pem).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A gateway-advertised newer release is now persisted to self-update/available.json, so the owner is told even without auto-update: - warn-level log with concrete instructions (download URL for this platform, restart command, or how to enable [update] auto = true) on hello AND on every startup — once per version per process, no reconnect spam - containers get image-rebuild wording instead of a binary swap hint - `cce-acp-connector status` prints the pending version + download URL - the notice self-clears once the running binary catches up Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…validation Phase 0 of the bridge-protocol hardening plan (shippable alone): - 44 golden fixtures at packages/cheers-acp-connector-rs/bridge-protocol/ fixtures/ pin the CURRENT wire bytes of every Agent Bridge frame. to_connector ones are regenerated from the gateway's constructors, to_gateway ones from the connector's typed frames (CHEERS_REGEN_FIXTURES=1 cargo test); a fixture diff in a PR requires a wire-safety proof. tolerance/ pins unknown-type/unknown-field behavior, compat/ freezes the legacy TS ready (plugin_version) shape. - gateway: the 19 scattered json! frame sites now go through named constructors in gateway/bridge_frames.rs, each pinned to its fixture by a plain `cargo test` unit test (task frame ignores the volatile enqueued_at). - connector: fixture contract tests prove every to_connector fixture parses to the expected typed variant (not Unknown) and every to_gateway fixture round-trips byte-identically. Ready gains parse-only legacy fields (connector_version now Option + plugin_version alias) so a future typed gateway parse keeps accepting the retired TS connector. - gateway auth now validates the connector's bridge_protocol_version before touching the DB: mismatch closes 4400 with a "supported: 1" reason. - ci: gateway job re-runs when bridge-protocol fixtures change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…workspace) Phase 1: the Agent Bridge frame types (4 enums + 19 data structs + protocol constants) move out of src/bridge.rs into a nested sub-crate at bridge-protocol/, with the connector dir becoming a 2-member cargo workspace. bridge.rs keeps only the transport (BridgeWebSocket, reconnect helpers) and re-exports the crate, so every `crate::bridge::X` import compiles unchanged. Nesting keeps cross/--locked release builds and the bot images' wholesale `COPY packages/cheers-acp-connector-rs` working with zero changes; the gateway path-deps into it in Phase 2. Deliberate additive changes, each proven wire-safe: - skip_serializing_if on all 216 Option fields: absent instead of explicit null (both ends already treat the two identically — connector via #[serde(default)], gateway via .get().and_then()); affected to_gateway fixtures regenerated in this commit. - #[serde(other)] Unknown added to ControlOutbound/DataOutbound so the gateway's Phase-3 typed parse is tolerant of newer connectors. - WS_CLOSE_UNSUPPORTED_PROTOCOL=4400 added and made FATAL: a version-rejected connector stops instead of reconnect-looping. - ConnectorInfo::default() no longer lies via env!(CARGO_PKG_VERSION) (which would report the protocol crate's version once shared): the binary passes its real identity through local_connector_info() explicitly, and Default is an honest "unknown"/"0.0.0". CI: plugin job runs cargo check/test with --workspace. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Own messages render flex-row-reverse, putting the sender header on the right — where the absolutely-positioned ActionBar (right-4) also sat, so its buttons were covered by the name/timestamp/avatar. Anchor the toolbar to the opposite side (left-4) on reversed rows. Also swap the Reply arrow for the MessageCircleMore bubble icon. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cker build Phase 2: server/ now depends on cheers-bridge-protocol and every outbound Agent Bridge frame except the two hellos is CONSTRUCTED from the shared typed enums (serialized at the existing ws_send/dispatch_task/send_data Value seams via bridge_frames::frame_value). A renamed or mistyped field between gateway and connector is now a compile error; the bytes stay pinned by the golden fixtures. The hellos keep raw-Value passthrough for acp_security / connector_config / server_capabilities (typed round-trip could drop unknown keys); they remain fixture-pinned. workspace_req's spliced op-specific keys became the typed WorkspaceReqExtra (deny_unknown_fields — a typo'd caller key is a loud 400, not a silently dropped field). Also converts the three api sites (approval/files/workspace) a Phase-0 script abort left inline. Docker build context moves to the repo root (the path dep lives under packages/): server/Dockerfile COPYs get server/ prefixes + the crate, cd.yml/redeploy.sh/CLAUDE.md/helm README commands updated. Verified: `docker build -f server/Dockerfile .` succeeds; root .dockerignore already covered target/ (bot images build root-context daily). Wire deltas in this commit, all in the proven-safe null→absent class (both ends treat missing == null): task.session_id / session.id / cwd and workspace_req.if_etag/root/content_b64 no longer emit explicit nulls; fixtures regenerated accordingly. Also fixes 3 Option fields the Phase-1 skip_serializing_if regex missed (image_b64/audio_b64/content_b64 — digits in field names). Gateway close codes + BRIDGE_PROTOCOL_VERSION now re-export from the shared crate — one source of truth for 4400/4401/4402/4403. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rlap-406552 fix(chat): stop hover toolbar overlapping own-message header
Phase 3: handle_control_frame parses each frame into the shared proto::ControlOutbound enum before dispatch — the exact bug this plan exists for (gateway reading `plugin_version` while the connector sent `connector_version`) is now impossible to reintroduce: both spellings are first-class fields on the shared Ready variant (legacy shape frozen in fixtures/compat/), and any renamed field is a compile error. ready and config_options handle fully typed; runtime_session_control_ack and config_status/config_option_status keep the raw Value for their payloads (top-level-session legacy tolerance / persist-unknown-fields semantics). Ready.runtime loosened to Option for parse tolerance (connector always sends it). The DATA stream deliberately stays string-routed, now documented in place: delta|content, content|text and the retired `reply` frame are wire-compat aliases a strict typed parse would reject. Coverage instead comes from a new gateway test that parses every to_gateway fixture through the shared enums (non-Unknown), so the gateway's inbound view can never lag the connector's outbound frames silently. Live-verified on the kind stack: gateway redeployed via scripts/redeploy.sh, in-cluster bot running connector 0.1.22 reconnected through the new auth version validation and its ready parsed + persisted (version=0.1.22). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…changes The gateway image now compiles packages/.../bridge-protocol (path dep), so a protocol-crate-only change must trigger build-gateway — otherwise prod pulls a stale image that predates the frame change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without the passthrough, setting CHEERS_CONNECTOR_RELEASE_VERSION in the box .env never reaches the gateway container, so production connectors would never see an update advertisement. Empty values are filtered by config.rs, so the :-} default is safe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ops(tls): auto-ACME + dual-domain Caddy edge for domain migration
feat(connector): signed opt-in self-update (ed25519 manifest, drain, auto-rollback)
…allowed Root .gitignore's `data/` pattern (meant for the repo-root data dir) matches ANY directory named data at any depth, so bridge-protocol/fixtures/data/ never made it into a commit — local tests stayed green off the worktree files while CI failed on the missing fixtures. Anchor the pattern to /data/ and commit the regenerated files (both regen paths are deterministic: to_connector from the gateway constructors, to_gateway from the connector's typed frames). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(protocol): shared bridge-protocol crate, golden fixtures, version validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's shipping (15 commits)
update_availablecheers-bridge-protocolcrate, 52 golden fixtures, typed gateway frames, auth version validation, gateway image now builds with repo-root contextSafety
context: .+file: ./server/Dockerfile); first build has a cold cachePost-deploy watchlist
https://$APP_DOMAIN/health(CD does this automatically)bot ready version=...on connector reconnects, nounsupported bridge protocol(4400)🤖 Generated with Claude Code