Skip to content

One relay per release channel, deployed by CI when relay code changes - #2079

Merged
AbirAbbas merged 3 commits into
devfrom
ci/relay-per-channel
Oct 30, 2026
Merged

AbirAbbas merged 3 commits into
devfrom
ci/relay-per-channel

Conversation

@AbirAbbas

Copy link
Copy Markdown
Collaborator

Part of #2072 (item B: client and hosted service drifted apart unnoticed).

What changes

  • One relay per release channel. Stable and rc builds keep https://codeaf.agentfield.ai/fabric. Staging builds sync through caf-fabric-staging, and every other build (dev tags, make build, test binaries) through caf-fabric-dev. A build nobody tagged can no longer write to production. Channels do not sync with each other. CODEAF_SYNC_URL and a pairing's saved relay still win. The three addresses are spelled once, in internal/syncsetup/relayurl.go, and pairing links use the channel's origin (HostedLinkHosts recognises a link from any channel).
  • CI deploys each relay when its code changes. A relay job in release.yml runs after prepare, and publish now needs it. On a dev push it deploys caf-fabric-dev; on a staging push it deploys caf-fabric-staging.
    • scripts/relay-fingerprint.sh hashes the relay's inputs: relay/hosted, the raft wasm build scripts, go.mod/go.sum, and the module-local packages compiled into the wasm, third_party/raft included.
    • The deploy is tagged with that hash and skipped with one line when the live version already carries it.
    • The channel's R2 bucket is created if missing.
    • After a deploy, the hosted conformance tests (TestAHostedRelay*) and codeaf-release service --url must pass. Otherwise the Worker is rolled back to the recorded version and the job fails, which stops that channel's publish.
  • Production stays off. caf-fabric deploys only when the repository variable RELAY_DEPLOY_PRODUCTION is true. It is not set, so the main and stable runs print one line and publish against the relay that is live today.
  • The per-channel Wrangler config is generated (scripts/relay-wrangler-config.sh) from relay/hosted/wrangler.toml, which stays the one source of bindings and migrations.
    • Only the name, account, bucket, BASE_PATH, routes/workers_dev and the rate-limit namespace ids change per channel. Rate-limit counters are per account, so staging adds 100 and dev adds 200.
    • Observability stays off and preview URLs stay off, matching the live production Worker. Compared with the live production config, the generated one differs only by the bindings dev has added since (Reach a paired computer through the relay sync uses #2073's machine doors and fix(relay): harden the machine doors #2073 put on the hosted relay #2076's rate limits).
    • The workflow and generator only ever name caf-fabric, caf-fabric-staging and caf-fabric-dev, never the test rig Workers.

Proof

  • Script tests: scripts/relay-fingerprint_test.sh, scripts/relay-wrangler-config_test.sh (parsed with Wrangler's own config reader), and scripts/relay-deploy_test.sh (mocked Cloudflare API: skip on a matching tag, deploy on a missing Worker, bucket already exists, rollback on a failed proof, production off).
  • wrangler deploy --dry-run of the generated dev config bundles every binding (2.6 MiB gzip).
  • internal/release workflow tests pin the relay job, the production switch and its default, and publish needing relay.
  • Focused internal/syncsetup, internal/pair, internal/atlas, cmd/codeaf-release, internal/manual, and make test-laws.

After merge, the first dev push creates caf-fabric-dev and its bucket. I'll watch that run.

🤖 Generated with Claude Code

AbirAbbas and others added 3 commits October 30, 2026 18:49
Stable and rc builds keep syncing through https://codeaf.agentfield.ai/fabric;
staging builds use the staging relay and every other build (dev tags, make
build, tests) the dev relay, so a build nobody tagged never writes to
production. CODEAF_SYNC_URL and a pairing's saved relay still win. The three
addresses are spelled once, in internal/syncsetup/relayurl.go, and pairing
links follow the channel's origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A relay job in the release workflow deploys caf-fabric-dev on dev pushes and
caf-fabric-staging on staging pushes, only when the relay's input fingerprint
differs from the live version's tag, and proves the deploy with the hosted
conformance test and codeaf-release service, rolling back on failure. The
production relay deploys only once the repository variable
RELAY_DEPLOY_PRODUCTION is true. The relay keeps no logs and publishes no
preview URLs, matching the live production Worker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas merged commit 72a2792 into dev Oct 30, 2026
7 checks passed
@AbirAbbas
AbirAbbas deleted the ci/relay-per-channel branch October 30, 2026 22:58
AbirAbbas added a commit that referenced this pull request Oct 31, 2026
The link check recognises this build's own hosted origin however it is
spelled, and leaves pair.LinkRelay exactly as #2079 wrote it. The rejoin test
writes its device record itself, because syncsetup now imports pair. The
front-page help is back under its line cap: codeaf relay keeps its own --help
and the manual no longer says the front page lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 31, 2026
The link check recognises this build's own hosted origin however it is
spelled, and leaves pair.LinkRelay exactly as #2079 wrote it. The rejoin test
writes its device record itself, because syncsetup now imports pair. The
front-page help is back under its line cap: codeaf relay keeps its own --help
and the manual no longer says the front page lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 31, 2026
The link check recognises this build's own hosted origin however it is
spelled, and leaves pair.LinkRelay exactly as #2079 wrote it. The rejoin test
writes its device record itself, because syncsetup now imports pair. The
front-page help is back under its line cap: codeaf relay keeps its own --help
and the manual no longer says the front page lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 31, 2026
The link check recognises this build's own hosted origin however it is
spelled, and leaves pair.LinkRelay exactly as #2079 wrote it. The rejoin test
writes its device record itself, because syncsetup now imports pair. The
front-page help is back under its line cap: codeaf relay keeps its own --help
and the manual no longer says the front page lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 31, 2026
The link check recognises this build's own hosted origin however it is
spelled, and leaves pair.LinkRelay exactly as #2079 wrote it. The rejoin test
writes its device record itself, because syncsetup now imports pair. The
front-page help is back under its line cap: codeaf relay keeps its own --help
and the manual no longer says the front page lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 31, 2026
The link check recognises this build's own hosted origin however it is
spelled, and leaves pair.LinkRelay exactly as #2079 wrote it. The rejoin test
writes its device record itself, because syncsetup now imports pair. The
front-page help is back under its line cap: codeaf relay keeps its own --help
and the manual no longer says the front page lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Oct 31, 2026
…ore removing (#2087)

* Name the sync service in its own failures, and keep each error's class

A relay that is down, slow or full now says so in one sentence family
(internal/syncwords) instead of blaming the person's network or other
machines. Status classes and causes stay exactly as before; only the words
a person reads change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Pair through the right sync service, and let an unused computer join

Every printed codeaf pair command carries --via on a self-hosted service.
A computer whose only chats never had a turn sent can join without
--replace; one with real chats is told how many and what --replace does.
A removed computer can come back with --replace and a fresh device key.
Removal by name refuses rather than guess when a roster cannot be read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say a quit saved here when only the upload is still pending

A close that saved on this computer but could not reach the sync service
prints that it is saved here and exits 0. A failed local write still says it
could not save. The receipt is negotiated in the hello, so mixed old and new
builds keep their former words and exit codes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ask before removing a device, and draw device marks through the glyph door

/devices asks once before removing a computer (y removes, any other key
keeps it), lists this device and an Add another device row, and Home stays
Home for /devices, /pair and /settings. Platform and presence marks come
from tokens slots. A refused send keeps the person's words in the box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Name cluster members by device on an opt-in status header

Services that predate Codeaf-Cluster-Names send no labels, and the command
then says another computer rather than claiming one was removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bring the manual and the end-to-end words in step with pairing and sync wording

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fit pairing's service check and the help page to per-channel relays

The link check recognises this build's own hosted origin however it is
spelled, and leaves pair.LinkRelay exactly as #2079 wrote it. The rejoin test
writes its device record itself, because syncsetup now imports pair. The
front-page help is back under its line cap: codeaf relay keeps its own --help
and the manual no longer says the front page lists it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Change entry for #2087

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the #2087 change title under one line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say "that chat stays" when the refusal counts one chat

The hand walk read "those 1 chat stay on this computer but no longer sync".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep cluster, devices and terminal approval on the service this computer uses

Cluster and device errors name the service the command used, never this
build's hosted default. Verbose cluster status prints its membership labels as
before. Roster names stay plain so a reused devbox name still finds the joined
computer; duplicate tails are added only when the list is drawn. A terminal
approving a self-hosted page's link keeps that page's service when it matches
this computer's, and a link on another service is still refused before its code
is sent. Existing tests change only where a deliberate L4 sentence or the solo
device list (this device is listed) changed what they read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep Home's whole credit warning beside its message

Moving the account notice off the key-hint line left the credit warning beside
the footer's project, where it was cut off. It now sits once at the right of
Home's message row, as on dev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Let the first-start fixture wait for its knowledge worker without spinning

The wait read the worker's database in a tight loop, which starves the writer
it waits for on a loaded machine. It now polls every 10 ms, and its ceiling is
for setup, not a speed claim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A window opened once with a provider connected can still pair without --replace

The picker's model list is cached as models-<key>.json for a non-default
service, and the empty-computer rule knew only models.json, so a computer that
had opened codeaf once with Ollama connected was told it had local work.
internal/catalog now owns that file name, the window builds its path through
it, and pairing accepts exactly those names. A keyless end-to-end test opens the
real window with a non-default service, sends nothing and pairs, and a second
window that sends one turn is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refuse a used, never-paired computer before the pairing code is exchanged

The code road refused only after the exchange, so the approving computer had
already said the new device was paired. A solo computer whose work rules out a
merge now refuses before anything is sent. Chats written before any identity
existed keep dev's behaviour: they come along and sync, which two tests pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Wait for Home by the tuiwords needle, not a bracketed tab

The selected tab reads [Home] only in some terminal modes, so the new window
test timed out on a Home that was already drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say when a pre-pairing chat --once chat reaches the other computers

Hello's check on a Mac found that such a chat is kept at pairing but is
published the next time codeaf's window opens on that computer, in place rather
than as a copy. The manual now says that instead of implying it syncs at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Import devname once in the devices card after the rebase onto #2085

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Lead serve's line with the machine name chat --at accepts

serve printed the device name first (`reachable as  beta (canoe-paddle-65)`),
and `codeaf chat --at beta` is refused by the name check. The machine name
comes first again, as on dev, and the device name follows only as a hint;
every instruction keeps the bare machine name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Ask remote-work approval with dev's question, not a name guess

"has the same name as one of your devices" matched only the asker's label,
and the approving computer has no verified link from that request to a paired
device, so it misdescribed a computer that is paired. The question is dev's
again; the weight line stays on the approving side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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