Skip to content

release: domain migration prep + connector self-update 0.1.27 + bridge-protocol hardening#187

Merged
haowei2000 merged 15 commits into
mainfrom
develop
Jul 14, 2026
Merged

release: domain migration prep + connector self-update 0.1.27 + bridge-protocol hardening#187
haowei2000 merged 15 commits into
mainfrom
develop

Conversation

@haowei2000

Copy link
Copy Markdown
Collaborator

What's shipping (15 commits)

Safety

  • No DB migrations — rollback is just the previous image
  • Wire compat live-verified: a 0.1.22 connector reconnects fine against the new gateway; deng (0.1.26) needs no coordinated upgrade
  • CD adapted: root-context gateway build (context: . + file: ./server/Dockerfile); first build has a cold cache

Post-deploy watchlist

  • https://$APP_DOMAIN/health (CD does this automatically)
  • Gateway logs: bot ready version=... on connector reconnects, no unsupported bridge protocol (4400)

🤖 Generated with Claude Code

haowei2000 and others added 15 commits July 14, 2026 11:37
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
@haowei2000
haowei2000 merged commit 79323d7 into main Jul 14, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant