feat(api): count a repo's env vars, and pin WorkerDTO's two declarations #115

Merged
lz merged 1 commit from feat/env-var-count into rc/sidebar-96 2026-09-04 16:53:51 +02:00
Owner

Fourth of four addressing sidebar-issues. Stacked on #114 — merge that first.

Split out of the restyle deliberately: sidebar-issues asks for a chip showing "num. of env vars", but delivering it needs a DTO field, a new query, a signature change threaded through two call sites and a parity test. That is backend work, and it was three quarters of the restyle's line count while having nothing to do with styling. #114 is net −13 without it.

The field

env_var_count on WorkerDTO, feeding an N env chip alongside the mount count the sidebar already showed.

repo_env_vars is keyed on repo_full_name, so this is one grouped query for the whole list rather than one per workspace, and it selects counts only — encrypted_value is never read, so it needs no master key and works while the vault is locked. Two workspaces on one repo therefore report the same number, which is what the field means.

Two things worth review attention

workerRowToDTO's new parameter is required, not defaulted to 0. A default lets a future call site compile while reporting "no env vars" for a repo that has them — the same silent-wrong-value trap the mounts_json restatement in spawnWorker already carries a comment about, and spawnWorker is exactly where it would have bitten: a new workspace inherits its repo's env immediately.

WorkerDTO is declared on both sides of the wire, like MountSpec — and unlike MountSpec it had no parity guard. Only one drift direction is caught by the compiler: workerRowToDTO constructs the server type, so a field the server lacks fails the build; a field the client declares and the server never sets fails nothing and arrives undefined through a non-optional type. I hit exactly that while writing this.

worker-dto-parity.test.ts asserts mutual assignability, so either direction is now a compile error. Mirrors mount-parity.test.ts, which AGENTS.md names as what makes that duplication safe.

Negative-controlled: planting a client-only field made the assertion fail and name the property. A guard that cannot fail is worse than none.

Verification

env_var_count confirmed reaching GET /api/workers against a seeded instance (4 env vars on one repo, 0 on the others) and rendering as a 4 env chip.

Gates: typecheck 4898 files / 0 errors, lint clean, 121 files / 1201 tests.

Fourth of four addressing `sidebar-issues`. **Stacked on #114 — merge that first.** Split out of the restyle deliberately: `sidebar-issues` asks for a chip showing "num. of env vars", but delivering it needs a DTO field, a new query, a signature change threaded through two call sites and a parity test. That is backend work, and it was three quarters of the restyle's line count while having nothing to do with styling. #114 is net −13 without it. ## The field `env_var_count` on `WorkerDTO`, feeding an `N env` chip alongside the mount count the sidebar already showed. `repo_env_vars` is keyed on `repo_full_name`, so this is **one grouped query for the whole list** rather than one per workspace, and it selects counts only — `encrypted_value` is never read, so it needs no master key and works while the vault is locked. Two workspaces on one repo therefore report the same number, which is what the field means. ## Two things worth review attention **`workerRowToDTO`'s new parameter is required, not defaulted to 0.** A default lets a future call site compile while reporting "no env vars" for a repo that has them — the same silent-wrong-value trap the `mounts_json` restatement in `spawnWorker` already carries a comment about, and `spawnWorker` is exactly where it would have bitten: a new workspace inherits its repo's env immediately. **`WorkerDTO` is declared on both sides of the wire, like `MountSpec` — and unlike `MountSpec` it had no parity guard.** Only one drift direction is caught by the compiler: `workerRowToDTO` constructs the *server* type, so a field the server lacks fails the build; a field the *client* declares and the server never sets fails nothing and arrives `undefined` through a non-optional type. I hit exactly that while writing this. `worker-dto-parity.test.ts` asserts mutual assignability, so either direction is now a compile error. Mirrors `mount-parity.test.ts`, which AGENTS.md names as what makes that duplication safe. **Negative-controlled**: planting a client-only field made the assertion fail and name the property. A guard that cannot fail is worse than none. ## Verification `env_var_count` confirmed reaching `GET /api/workers` against a seeded instance (4 env vars on one repo, 0 on the others) and rendering as a `4 env` chip. Gates: typecheck 4898 files / 0 errors, lint clean, 121 files / 1201 tests.
feat(api): count a repo's env vars, and pin WorkerDTO's two declarations
All checks were successful
ci / nexus (pull_request) Successful in 8m51s
pr-image-cleanup / delete-pr-images (pull_request) Successful in 8s
ci / images (pull_request) Successful in 15m21s
37406d63e7
Adds `env_var_count` to WorkerDTO so the sidebar can chip a workspace with the
operator state it carries, alongside the mount count it already showed.

repo_env_vars is keyed on repo_full_name, so this is ONE grouped query for the
whole list rather than one per workspace, and it selects counts only —
`encrypted_value` is never read, so it needs no master key and works while the
vault is locked. Two workspaces on one repo therefore report the same number,
which is what the field means.

`workerRowToDTO`'s new parameter is REQUIRED rather than defaulting to 0. A
default lets a future call site compile while reporting "no env vars" for a repo
that has them — the same silent-wrong-value trap the mounts_json restatement in
spawnWorker already carries a comment about, and spawnWorker is exactly where it
would have bitten: a new workspace inherits its repo's env immediately.

WorkerDTO is declared on BOTH sides of the wire, like MountSpec, and only one
drift direction is caught by the compiler: workerRowToDTO constructs the SERVER
type, so a field the server lacks fails the build, while a field the CLIENT
declares and the server never sets fails nothing and arrives `undefined` through
a non-optional type. worker-dto-parity.test.ts asserts mutual assignability, so
either direction is now a compile error. Mirrors mount-parity.test.ts, which
AGENTS.md names as what makes that duplication safe.

Negative-controlled: adding a client-only field made the parity assertion fail
and name the property.

Split out of the sidebar restyle, which is styling and should not carry a
backend field, a new query and a parity test to render one chip.
lz changed target branch from feat/sidebar-restyle to rc/sidebar-96 2026-09-04 16:53:41 +02:00
lz merged commit b07d86f7ac into rc/sidebar-96 2026-09-04 16:53:51 +02:00
Sign in to join this conversation.
No reviewers
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lz/agent-nexus!115
No description provided.