Follow-up to #66 and PR #67.
PR #67 adds the Gateway bridge (bridge/discord-gateway/), the signed forward route (agent/channels/discord-mentions.ts), the runner (scripts/zo-deploy.ts), and a deploy workflow (.github/workflows/deploy.yml). Two steps stay manual by design: register the computer-discord-bridge service on the Zo host, and set the ZO_API_KEY repository secret. Everything below is about what happens after those exist.
1. The deploy races CI on main
Expected: a revision that failed verify never reaches the host.
Actual: verify.yml and deploy.yml both trigger on push to main and start in parallel. The deploy fast-forwards the live checkout and restarts the service without consulting verify, so a merge that breaks tsc or the test suite deploys anyway and the host then runs it.
Change: gate the deploy on verify — trigger it from workflow_run (completed + success + main), or fold both into one workflow with needs: [verify].
2. Revision drift is silent
scripts/zo-deploy.ts takes --expect-sha from GITHUB_SHA but only prints a note when the live checkout's HEAD differs (scripts/zo-deploy.ts:208). If a second push lands mid-deploy, the workflow is green while the host runs a revision nobody asked for.
Change: fail on mismatch, or at least emit a workflow warning annotation, so the job's status reflects what is actually live.
3. Every denial is invisible
Both halves fail closed, and neither says so.
- The bridge drops a message locally when
resolveDiscordMentionAdmission returns null and logs nothing (bridge/discord-gateway/index.ts, handleMessage).
- The forward result is discarded:
drainQueue passes forward()'s return value nowhere, so 202 {"dispatched": false, "reason": "denied"} — the route's answer when the deployment's allowlists disagree with the bridge's — is indistinguishable from a successful dispatch.
An operator therefore cannot tell "nobody mentioned Computer" from "mentions are being discarded". This is the likeliest real-world failure, because the allowlists and the bridge secret are configured twice, once in the Zo service env and once in the deployment env.
Change: log admission decisions at info level with the reason, and log the route's dispatched/reason for each forward.
4. Nothing checks the deployment the forwards land in
deploy.yml verifies the bridge process only; the eve app deploys through Vercel's Git integration with no post-deploy check.
Change: after the restart, assert GET /eve/v1/health reports ready and an unsigned POST /eve/v1/discord-mentions returns 401. That catches a missing or mismatched DISCORD_BRIDGE_SECRET before the first real mention does instead.
Notes
- Item 1 is worth doing before the first merge to
main that touches the bridge, since that merge is also the first deploy.
- The local suite already covers the parsers, the admission matrix, the HMAC primitives, and the deploy CLI end-to-end against a fake Zo MCP server (
lib/zo-deploy-cli.test.ts); none of these four items are covered today.
Follow-up to #66 and PR #67.
PR #67 adds the Gateway bridge (
bridge/discord-gateway/), the signed forward route (agent/channels/discord-mentions.ts), the runner (scripts/zo-deploy.ts), and a deploy workflow (.github/workflows/deploy.yml). Two steps stay manual by design: register thecomputer-discord-bridgeservice on the Zo host, and set theZO_API_KEYrepository secret. Everything below is about what happens after those exist.1. The deploy races CI on
mainExpected: a revision that failed
verifynever reaches the host.Actual:
verify.ymlanddeploy.ymlboth trigger onpushtomainand start in parallel. The deploy fast-forwards the live checkout and restarts the service without consultingverify, so a merge that breakstscor the test suite deploys anyway and the host then runs it.Change: gate the deploy on
verify— trigger it fromworkflow_run(completed+success+main), or fold both into one workflow withneeds: [verify].2. Revision drift is silent
scripts/zo-deploy.tstakes--expect-shafromGITHUB_SHAbut only prints a note when the live checkout'sHEADdiffers (scripts/zo-deploy.ts:208). If a second push lands mid-deploy, the workflow is green while the host runs a revision nobody asked for.Change: fail on mismatch, or at least emit a workflow warning annotation, so the job's status reflects what is actually live.
3. Every denial is invisible
Both halves fail closed, and neither says so.
resolveDiscordMentionAdmissionreturnsnulland logs nothing (bridge/discord-gateway/index.ts,handleMessage).drainQueuepassesforward()'s return value nowhere, so202 {"dispatched": false, "reason": "denied"}— the route's answer when the deployment's allowlists disagree with the bridge's — is indistinguishable from a successful dispatch.An operator therefore cannot tell "nobody mentioned Computer" from "mentions are being discarded". This is the likeliest real-world failure, because the allowlists and the bridge secret are configured twice, once in the Zo service env and once in the deployment env.
Change: log admission decisions at info level with the reason, and log the route's
dispatched/reasonfor each forward.4. Nothing checks the deployment the forwards land in
deploy.ymlverifies the bridge process only; the eve app deploys through Vercel's Git integration with no post-deploy check.Change: after the restart, assert
GET /eve/v1/healthreports ready and an unsignedPOST /eve/v1/discord-mentionsreturns 401. That catches a missing or mismatchedDISCORD_BRIDGE_SECRETbefore the first real mention does instead.Notes
mainthat touches the bridge, since that merge is also the first deploy.lib/zo-deploy-cli.test.ts); none of these four items are covered today.