fix(server): align serve backend selection - #195
Merged
Conversation
`emails-serve` no longer has a mode. It has an internal store, and the store follows `EMAILS_DATABASE_URL` alone: set means the operator-owned PostgreSQL `/v1` API, unset means the local SQLite dashboard. WHY THIS HALF FIRST. The deployment word had two independent definitions of "am I the second arm?", one per shipped binary, and they meant opposite things. In the `emails` CLI `self_hosted` means "become an HTTP client of somebody else's server"; in `emails-serve` it meant "become a PostgreSQL server". One variable, two contradictory semantics, so a deployment that set it for one binary silently reconfigured the other. Storage configuration cannot contradict itself that way. The word is REFUSED, not ignored. Deleting a variable's last reader and leaving the variable accepted removes the word and keeps the hole: the next operator sets it on a PostgreSQL deployment, nothing reads it, nothing complains, and they believe they configured something. The refusal names the setting to delete and the setting to use, and quotes no value, because `EMAILS_DATABASE_URL` carries a password. Fixes a latent defect found on the way: the container HEALTHCHECK keyed on the deployment word and read its ABSENCE as the PostgreSQL arm (`/ready`), while `src/server/index.ts` read the same absence as the SQLite arm, which serves no `/ready`. A container started without the word ran the dashboard and was probed for a route it does not have — permanently unhealthy, with no configuration error to explain it. Both now read one setting. The ECS task definition stops setting the variable in the same change, so the image and the environment that must agree move in one task-definition revision. Ratchet re-pinned to the measured tree, zero slack on all eleven: isSelfHostedMode 38 -> 35, resolveEmailsMode 65 -> 64, mode-env 219 -> 209. The sixteen client families still read the word; none is collapsed here. Agent: Silvanus
…at the backend An unused exported type alias keeps the retired vocabulary reachable for the next module that imports it. Nothing outside bind-options.ts referenced it, so it goes rather than being redirected. Agent: Silvanus
The full-suite comparison against a pristine origin/main worktree caught this: the first version refused the retired deployment setting outright, and that aborted the branch suite at src/cli/commands/inbox-explain.test.ts. The cause was not that test — it was the harness change that came with the refusal. WHY A FLAT REFUSAL IS WRONG WHILE THE CLIENT HALF IS LIVE. Sixteen `emails` CLI families still route on this word, so one shell legitimately exports it for the client and runs the server from the same place. Three instances of exactly that shape exist in this repository today: the hermetic harness exports it for every test and several of those tests spawn `emails-serve` with the inherited environment; the container runtime smoke did the same; and docs/SELF_HOSTED_RUNTIME.md shows a client block and a service block an operator would paste into one shell. Refusing breaks all three — which is not failing closed, it is failing on a configuration that works. So the server now: refuses a value that never selected anything here; refuses a value that CONTRADICTS the storage configuration, naming both settings and no value; and tolerates a value that AGREES while announcing, once per process, that it is ignored and that EMAILS_DATABASE_URL is what decides. An unannounced ignore would be the same hole with a smaller symptom. The tolerance is deleted with the client families. This also reverts the harness edit entirely: scripts/run-hermetic-tests.sh and scripts/prepublish-local-test.mjs are byte-identical to main again, so this PR no longer changes what any other suite runs under. The one place that still needs the setting gone from a child environment — the startup-contract case that configures PostgreSQL, where an inherited local value is a genuine contradiction — deletes it by ROLE through the owning module's constant. Ratchet re-measured on the corrected tree, zero slack: mode-env 219 -> 206 (fifteen spellings out, two back in for the exported retired-settings list). The new suite contributes zero to that counter: written naturally it spelled the variable nine times, and it now reads the name by role instead. Agent: Silvanus
Agent: Silvanus
Agent: Silvanus
Route the public serve command through the server storage resolver and cover the shipped CLI path. Agent: unresolved-account003
Contributor
Author
|
[REVIEW] GO — #195 @ 51a042e — lens: correctness+security+gates, reviewer unresolved-account003 (1 of 1) Focused remediation review:
Gates:
Blocking P0/P1 findings: none. The named wrong-store P1 is fixed, and its direct registration regressions are covered by the declared full test gate. Non-blocking follow-ups: none. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
emails servecommand through the server storage-backend resolvermcp,remove, root-help, and unknown-command registration when a client API is configured without a server database URLEMAILS_DATABASE_URLconfiguredWhy
PR #160 removed the server deployment-mode axis, but its public
emails serveentrypoint still selected the SQLite dashboard through the retired client mode. PR #160 was merged before the adversarial review comment identifying that P1 was posted, so this follow-up carries the tested remedy.Verification
bun run test— exit 0; 4282 pass, 156 skip, 0 fail across 288 files (4438 total)emails serve --helpselects the self-hosted/v1service and port 8080emails serve --helpretains the local dashboard and port 3900mcp --claude --dry-run --jsonremains availableThe repository declares no separate typecheck script.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.