Repository navigation
docs(config): document and validate environment - #256
Conversation
|
@therealbibson Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Miracle656
left a comment
There was a problem hiding this comment.
Validating the environment at startup is the right thing and src/validateEnv.ts is a good shape for it. One blocker, which is your own test failing against your own change.
● docker-compose.yml › declares every env key .env.example documents
- Array []
+ Array [
+ "DATABASE_REPLICAS",
+ "NODE_ENV",
+ "SOROBAN_RPC_URL_TESTNET",
+ "SOROBAN_RPC_URL_MAINNET",
…38 in total
The PR touches .env.example, src/validateEnv.ts, src/index.ts and the test — not docker-compose.yml. So it documents 38 keys and simultaneously asserts that compose declares every documented key, which it does not. 731 tests, 1 failed, and it is this one.
Two honest ways out, and I do not mind which:
Add the keys to compose. If the intent is that a docker compose up gives you a working service, then every key an operator must set belongs there with a sensible default or an explicit empty. That is 38 entries and a judgement call per key about what the default should be — which is why I have not just done it for you.
Or scope the test to the keys that are actually required. A compose file does not need to declare every optional variable — DATABASE_REPLICAS and NODE_ENV have reasonable defaults and an operator who never sets them is fine. If validateEnv.ts already knows which keys are required in production, that set is the right one for this assertion, and it makes the test say something true and useful rather than something aspirational.
The second is probably the better test: "compose declares everything you must set" is a property worth enforcing forever; "compose mentions every variable that exists" will be false again the next time someone adds an optional flag.
One thing I would like either way: .env.example is the file operators read, and it is also the file the sibling repo's drift guard checks. Whatever the test ends up asserting, make sure a key that is required in production is impossible to miss — validating at startup is only half of it if the example file does not say the variable exists.
Everything else passes — 730 of 731 — and tsc --noEmit is clean. Pick either route and I will merge it.
…action
Startup and shutdown now emit structured events rather than prose
`console.log`, each carrying a `requestId` — a per-process `bootId` — so the
lines from one boot can be correlated in an aggregator.
The part that mattered most on review is `SECRET_PATTERNS` in `src/logger.ts`,
which redacts an 11-digit NIN, connection strings, bearer tokens, API keys and
JWTs before anything is written. Given a NIN is personal data under the NDPA,
and that a connection string has leaked through an error path in a sibling
service before, that list earns its place. Two of its patterns did not work:
- **The connection-string pattern did not compile.** It read
`/\b(?:abcdefghijklmnopqrstuvwxyz{})?:(?:\/\/))[^\s]+/` — an unbalanced
paren and what looks like a placeholder left where the schemes belong. `tsc`
rejected it (`TS1508`), so the branch did not build. Replaced with
`/\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?):\/\/\S+/i`,
checked against real connection strings and against prose.
- **The API-key pattern missed the common shape.** `[A-Za-z0-9]{16,}` cannot
cross a second separator, so `sk_live_…` — the form Stripe-style keys
actually take — went through unredacted. The PR's own test caught it once the
file compiled. Widened to `[A-Za-z0-9_-]{16,}`, which matches `sk_live_…`
and `api-key_…` while still not matching bare prose like "the sk_ value".
Two more changes while merging:
- **`if (require.main === module) { main() }` reverted to a bare `main()`.**
`src/__tests__/ohlcStartup.test.ts` boots the real entrypoint through
`jest.isolateModules(() => require("../index"))`, and under that require this
module is not `require.main`, so `main()` never ran and all 19 of its cases
failed. The guard is good practice in general; here it silently disables the
only test of the startup wiring, so there is now a comment saying why it is
absent.
- `src/index.ts` conflicts resolved as a union with #256, which landed first:
`validateEnv()` and the OHLC worker wiring both stay, and the structured
`shutdown.received` event replaces the prose line while keeping
`stopOhlcRefreshWorker?.()`.
Verified on the merge result: `tsc --noEmit` silent, 1,003 tests pass across 66
suites.
Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
|
Merged, with three changes — the first is the one worth reading.
Worth knowing for next time: this project compiles with
The feature itself is right: Verified on the merge result: |
The deploy crash-looped: build succeeded, `npm start` exited 1, repeatedly.
Error: [wraith] Missing required env vars: SOROBAN_RPC_URL_MAINNET
at validateEnv (dist/validateEnv.js:24:15)
at main (dist/index.js:30:35)
The deployed service runs with mainnet among its networks and no
`SOROBAN_RPC_URL_MAINNET`, and had done for months. `validateEnv` from #256
made that fatal, so merging it took the service down — the API, every network's
reads, and the naira rails with it.
This repo had already fixed this exact failure once. `planNetworks()` in
`src/indexer.ts` says why, and I should have read it before merging #256:
validateNetworkConfig(networks) threw for the whole set if any one entry
was misconfigured, which broke the isolation promised in startAllIndexers
in the worst way: an unset SOROBAN_RPC_URL_MAINNET killed the process
during main(), taking testnet indexing AND the HTTP API with it. The API
does not index. The naira rails are plain HTTP to Linq and never touch
Soroban RPC, so a missing mainnet endpoint was stopping /ngn from
answering for a reason that has nothing to do with it.
A second, stricter gate in front of that undid it. `validateEnv` now checks
only `DATABASE_URL` and `DIRECT_DATABASE_URL` — the two things without which no
route can do anything — and the reasoning plus the rule for what may be added
later is recorded in the function's doc comment.
Two tests updated. One asserted the mainnet-RPC requirement; the other asserted
that **the production configuration is invalid** — `NETWORKS=testnet,mainnet`
with only `SOROBAN_RPC_URL` set. Both now pin the opposite: a missing
per-network RPC must not block startup. That is the regression guard this
needed and did not have.
`tsc --noEmit` silent, 1,026 tests pass across 68 suites.
Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
|
Follow-up, and the fault here is mine rather than yours. Merging this took the deployed service down. Build succeeded, then The deployment runs with mainnet among its This repo had already fixed exactly this, and left a comment saying so, in
A stricter gate in front of that undid it. I reviewed your function on its own merits and never checked it against the live configuration — that is the review I owed you and did not do. Fixed in Two of your tests needed inverting, and one of them is worth pointing at: it("requires a network-specific RPC URL when indexing multiple networks", () => {
// STELLAR_NETWORK: testnet, NETWORKS: testnet,mainnet, SOROBAN_RPC_URL only
).toThrow(/SOROBAN_RPC_URL_MAINNET/);That is the production configuration, asserted as invalid. It passed, so it read as a test doing its job — but a test asserting a config is invalid is a claim about reality, and this one was wrong about it. Both tests now pin the opposite: a missing per-network RPC must not block startup. The env-validation idea stands and the rest of the PR is untouched. Sorry for the disruption — that one was on the review, not the contribution. |
Closes #248
Validation: targeted env tests and TypeScript build pass. The full npm test run encounters the pre-existing OpenAPI snapshot mismatch noted in #248 and did not complete before hanging.