Skip to content

refactor: rename region → siteId across DB, API, daemon, and client - #118

Merged
guohai merged 8 commits into
mainfrom
feat/region-to-siteid-refactor
Aug 23, 2026
Merged

refactor: rename region → siteId across DB, API, daemon, and client#118
guohai merged 8 commits into
mainfrom
feat/region-to-siteid-refactor

Conversation

@guohai

@guohai guohai commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Completes the region → siteId terminology refactor: the DB columns, server responses, daemon, and client all now call an exact site ID (<base>-<NN>, e.g. na-us-seattle-01) what it is, ending the region-means-two-things overload.

  • DB: 9 columns renamed to site_id + 2 indexes (migration 0025, v34, data-preserving).
  • Server: schema, storage, routes, and the v1 API consume/emit siteId; the eval-jobs list filter is ?siteId=.
  • Daemon: vox_eval_agentd consumes siteId job rows; the ignored job-poll param is gone.
  • Client: formatRegion/formatSite split; all hand-rolled interfaces and call-sites read siteId (including the silent non-tsc cases: eval-agents, leaderboard, org-settings schedules, run-your-own jobs, clash pages).
  • OpenAPI: 12 field renames; stale [na,apac,eu,sa] enum removed everywhere.
  • Fixes folded in from the full local test gate (now fully green — unit 1556/1556, audio 12/12, E2E 133/0):
    • clash-detail.tsx called useQuery after early returns — the match page crashed once data loaded.
    • Fixture drift in DB-level suites (site_id NOT NULL), stale E2E specs (admin redirect since feat: admin edits user role/plan; move User Management + Providers under /console #52, renamed tabs, strict-mode locators), flaky networkidle waits replaced with deterministic ones.
    • full-tests-run.sh defaults DATABASE_URL so DB-level suites can't silently skip.
    • Dead code removed (mockData.ts, dead match.region displays), stale region-wording comments swept.
  • New: tests/site-id-wire.test.ts locks the wire contract (renamed surfaces emit siteId, never region; ?siteId= validates; token endpoints keep the documented back-compat region key).

Known scope decision (documented, not a bug)

A few surfaces still use the region JSON key carrying a site-id value (eval-agent-token responses, run-targets agents, clash-runner admin lists, several POST request bodies). Server ↔ client are internally consistent and the wire test pins the contract; closing this fully is a follow-up.

Test plan

Generated with SMT smt@agora.build

guohai added 8 commits August 23, 2026 08:18
Reserve `region` for the real region (na-us-seattle / "Seattle") and
rename the sequenced site identifier every job/agent/schedule stores
(na-us-seattle-02) to `siteId`, across DB (9 columns), wire protocol
(hard cut), public API + OpenAPI, and client helpers (clean split:
formatRegion→formatSite, new formatRegion(region)/regionOf). Region-scope
filters and AWS S3 region are explicitly out of scope.

🤖 Built with SMT <smt@agora.build>
4-task plan (DB+schema+server / daemon / client / openapi) implementing
designs/2026-08-22-region-to-siteid-refactor-design.md.

🤖 Built with SMT <smt@agora.build>
The `region` column on nine tables stored a full sequenced site id
(na-us-seattle-02), not a region. Rename column + field + isAllocatedSite
helper to siteId; drop the ignored job-poll ?region= param; keep genuine
region-scope filters untouched. Hard cut, no wire alias.

🤖 Built with SMT <smt@agora.build>
Consume Core's siteId wire fields; poll /api/eval-agent/jobs with no site
query (Core derives it from the token). S3Config.region untouched.

🤖 Built with SMT <smt@agora.build>
formatRegion now formats a region ("Seattle"); the old site-label behavior
("Seattle 02") moves to formatSite; add regionOf(). Rename useRegionOptions
-> useSiteOptions, REGIONS -> SITES, and read siteId from the renamed API
responses (eval agents, jobs, schedules, metrics/leaderboard, clash events).
Also move the eval-jobs list filter param region -> siteId across server,
storage, client, and its api.test case.

🤖 Built with SMT <smt@agora.build>
The exact-site fields are site ids; rename to siteId and remove the
pre-0023 enum [na, apac, eu, sa] that no longer exists in data.

🤖 Built with SMT <smt@agora.build>
…g sweep

- clash-detail: move the streamInfo useQuery above the early returns —
  called conditionally, it changes hook order between renders and crashes
  the match page once data loads (react-hooks/rules-of-hooks).
- clash-detail: remove the dead match.region displays (clash_matches never
  had a region/site column; formatSite(undefined) rendered blanks).
- delete client/src/lib/mockData.ts (no importers).
- full-tests-run.sh: default DATABASE_URL to the local dev DB so the
  DB-backed storage-level suites actually run instead of silently skipping.
- reword stale "region" comments/log lines that now mean site (storage,
  index, routes-api-v1, schema) — SQL and logic untouched.

🤖 Built with SMT <smt@agora.build>
Full local gate now green: unit 1556/1556 (85 files), audio 12/12,
E2E 133 passed / 0 failed.

- siteId drift in DB-level fixtures (site_id NOT NULL violations once
  DATABASE_URL reaches vitest): shared-agents-reap-query,
  session-secrets-class, tier-classification now insert siteId.
- clash.test: assert event.siteId (raw rows carry siteId; one stale
  .region assert cascaded into 7 failures via undefined eventId).
- dispatch-integration: agent jobs listing rows carry siteId.
- s3.test: /api/eval-jobs returns {data,total}, not a bare array
  (pre-existing, surfaced once R2 creds were configured).
- e2e/admin.spec: admin login lands on /console/users since #52.
- e2e/clash.spec: role-based locators (bare text= hits nav links,
  badges, and event names — strict-mode violations).
- e2e/run-your-own: wait for the page heading instead of networkidle
  (flaky against the Vite dev server); site-placeholder + renamed-tab
  assertions match the current UI.
- e2e/workflow-provider: domcontentloaded instead of networkidle.
- e2e/console.spec: poll for the client-side auth redirect instead of
  racing it with a one-shot content snapshot.
- eval-agent-daemon: drop the redundant extraneous-query-params copy of
  the fetch-jobs test (flagged in the final branch review).
- NEW tests/site-id-wire.test.ts: locks the wire contract the refactor
  defines — eval-agents/eval-jobs/leaderboard emit siteId (never
  region), ?siteId= filters and validates, and eval-agent-token
  create/list keep the documented back-compat region key carrying a
  site-id value.

🤖 Built with SMT <smt@agora.build>
@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

Findings

  • Medium: docs/openapi.yaml:1358 documents POST /eval-agent-tokens as requiring siteId, and docs/openapi.yaml:1386 documents a siteId response, but the server still accepts regionLocationBaseId/region and returns region for this back-compat surface at server/routes.ts:2663 and server/routes.ts:2710. Generated clients will send the documented body and get 400 "Name and region location required", and readers expecting siteId will see undefined. Either update OpenAPI to match the intentionally retained region contract, or change the route and client/test contract together.
  • Medium: The daemon/server protocol has no rolling-upgrade compatibility. New daemons require agent.siteId from registration at vox_eval_agentd/vox-agentd.ts:336 and then stop polling if it is absent at vox_eval_agentd/vox-agentd.ts:408; the server now returns only siteId at server/routes.ts:3172. That means upgrading daemon before server, or server before existing deployed daemons that still read region, strands agents as idle/no-job. Consider emitting both siteId and region for one release and making the daemon accept agent.siteId ?? agent.region.

No other security, authorization, injection, or performance issues stood out in the reviewed refactor paths. I did not run the test suite in this read-only environment.

@github-actions

Copy link
Copy Markdown

🤖 Claude Code Review

Reviewed the full diff against 047f0b5. The mechanical rename is careful and well-executed — migration registered in server/migrate.ts (v34), index rename targets match the exact names from 0000_opposite_hobgoblin.sql, snakeToCamel() correctly maps site_idsiteId on the three raw-SQL SELECT * paths, and tests/site-id-wire.test.ts is a good addition that locks the JSON keys where hand-rolled client interfaces can't. Findings below.

Deployed eval-agent daemons will silently stall

POST /api/eval-agent/register now returns siteId (server/routes.ts:3170) where it previously returned region. The daemon shipped in the field does:

this.region = agent.region;            // vox-agentd.ts:336 (old)
...
if (!this.region) return [];           // vox-agentd.ts:408 (old)

So after this deploys, every unupgraded agent registers successfully, heartbeats successfully, shows online/idle in the console — and never fetches a job again. No error is logged on either side; the only symptom is jobs aging out through failPendingJobsWithNoAgent, which reports "No eval agent available" even though the agent is visibly online. Given scripts/vox-upgrade.sh exists, there is a fleet out there that upgrades on its own schedule.

The reverse also breaks: the new daemon reads agent.siteId, so a new agent against a not-yet-deployed Core hits the same dead end.

Cheapest fix is to emit both keys from register for one release:

res.json({ id: agent.id, name: agent.name, siteId: agent.siteId, region: agent.siteId, state: agent.state, leaseId });

Dropping ?region= from fetchJobs is fine on its own — the route authenticates by token and never read the query param.

Breaking change to public API v1 without back-compat

server/routes-api-v1.ts renamed the request body field on POST /api/v1/workflows/:id/run from region to siteId, and the region query filter to siteId, with no alias. This is the vox_live_-keyed external API — existing integrations get a hard 400 ("An exact site ID is required") on the next deploy. Given the rest of the PR deliberately preserves back-compat elsewhere (eval-agent-tokens keeps the region key), accepting the old name for a deprecation window seems more consistent:

const siteId = req.body.siteId ?? req.body.region;

In-app API docs now show a request that 400s

client/src/pages/run-your-own.tsx:596 still renders:

-d '{"region": "na-us-seattle-01"}' \
  .../api/v1/workflows/1/run

Copy-pasting that from the UI now fails. Needs siteId (or the alias above).

Minor

  • Half-migrated wire contract. /api/eval-agents/dispatchable maps siteIdregionsiteId across three lines (server/routes.ts:3050-3065) because DispatchableAgentRow/DispatchToken in dispatch.ts/permissions.ts still declare region: string. Correct, but formatSite(token.region) and formatSite(agent.siteId) now sit ~250 lines apart in console-eval-agents.tsx. The split is documented in the wire test and clash surfaces are internally consistent, so it holds — worth a tracking issue rather than a change here.
  • formatRegion / regionOf have no production callers (client/src/lib/utils.ts:85,91) — only tests/regions.test.ts exercises them. Also note the rewrite dropped the legacy macro-code mapping (na → "North America"); I found no caller passing a macro code, so this looks intentional, but the new formatRegion only matches exact baseIds.
  • as any casts carried forward at server/storage.ts:2313 and the createClashRunner insert. Both columns are plain varchar now, so the casts are dead weight suppressing real type checking on a SQL-bound value (parameterized, so not an injection risk).
  • failPendingJobsWithNoAgent error text still reads "No eval agent available for region " while the surrounding logs were reworded to "site" (server/index.ts). Cosmetic, but it's the string users actually see on a failed job.
  • Rollout ordering: RENAME COLUMN is metadata-only and fast, but not backward compatible — any old app instance still serving during the swap errors on region. Fine for a stop-then-start Coolify deploy; worth confirming that's what's configured.

I could not run npm run check or the test suite (both needed approval), so the type-level claims above are from reading; the two remaining .region reads I found (dispatch.ts:59, routes.ts:3065) are both on internal types that still declare the field and are populated from siteId at the call site, so they're correct.

@guohai
guohai merged commit 48d8863 into main Aug 23, 2026
7 checks passed
@guohai
guohai deleted the feat/region-to-siteid-refactor branch August 23, 2026 08:25
guohai added a commit that referenced this pull request Aug 23, 2026
Finishes what #118 enumerated: no public response surface uses the
`region` JSON key for a site-id value anymore.

- Responses renamed outright (precedent #118): eval-agent-tokens
  list/create (+ legacy admin routes), run-targets agents,
  eval-agents/dispatchable shared rows (seam key no longer leaks),
  clash-runner tokens/list.
- Clash-runner daemon wire dual-keys `siteId` + `region` (register +
  assignment payloads) — deployed runners still read `region`; drop the
  alias after the next runner redeploy (vox-upgrade.sh).
- Request bodies canonically take `siteId`; legacy `region` stays
  ACCEPTED as an alias (eval-schedules, workflow run, clash event/
  token/schedule) so scripted API users don't break. Existing tests
  keep sending the alias — continuous coverage of that path; the
  canonical key + alias are pinned in site-id-wire.test.ts.
- Client writers/readers flipped to siteId on console-eval-agents,
  console-clash, console-evalsets, console-workflow-detail,
  run-your-own (incl. the curl doc example).
- Internal/seam DTOs (dispatch.ts, permissions.ts, marketplace
  AgentSummary/setListing context) intentionally untouched — internal
  naming debt only, no wire impact.

Gate: tsc clean, unit 1557/1557, audio 12/12, E2E green (one known
rare admin-login flake under full-suite parallelism, 19/19 x3
standalone; agora-e2e 13/13 after a lingering remote ConvoAI session
timed out).

🤖 Built with SMT <smt@agora.build>
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