feat(server): per-suite config resolution with overrides - #224
Merged
07prajwal2000 merged 2 commits intoAug 8, 2026
Merged
Conversation
…nner Test suites will run in a cold child process (Fluxify-rest#217) whose caches are empty, so the parent has to resolve app config and integrations up front and ship them over IPC. - `modules/testRunner/resolve.ts` returns a `ProjectConfigPayload` — the shape the compiled worker already hydrates from, so the child needs no new loader. - App config overrides are applied FIRST, then integrations are resolved against the overridden map. Integration configs hold `cfg:` references, so the other order expands pre-swap values and the override silently does nothing. The test fails if the order is flipped. - Integrations are re-resolved from their rows instead of copied out of the live cache, which is already expanded against the un-overridden config. - `integrationsLoader`: per-row resolution extracted into the exported `resolveIntegrationConfig(row, appConfig)`; the app config map is a parameter now instead of a module cache read. `loadFromDB` calls it. - Fixes a latent mutation: the three near-identical `mapIntegrationTo*` helpers wrote the expanded url back onto the row, destroying the `cfg:` reference for any second resolve. Collapsed into one function that expands into a new object, and a missing cfg key now logs instead of throwing. Closes Fluxify-rest#216 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018pjR2Xuyxu5UTUDFo9smSd
The spec only proved the resolution rules in-process. This adds an integration test that spawns a cold child with Bun.spawn + IPC, ships the resolved payload across, and has the child hydrate through the same loaders the compiled worker uses, then read the values back through getAppConfig/dbIntegrationsCache. Proves what an in-process assertion cannot: the payload is structured-cloneable (no class instances, no functions), the loaders work with a cold module cache, and a child with no database or NATS can build its whole view from the message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018pjR2Xuyxu5UTUDFo9smSd
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.
Closes #216. Part 2 of 6 of the sandboxed test suite runner.
Why
Test suites will run in an ephemeral child process (#217). That child starts cold —
setupContextVarsruns no queries, it reads module caches that are empty in a fresh process. So the parent has to resolve app config and integrations up front and ship them over IPC.What
apps/server/src/modules/testRunner/resolve.ts—resolveSuiteConfig(projectId, overrides).Returns a
ProjectConfigPayloadrather than theResolvedIntegration[]the issue sketched. That is the shape the compiled worker already hydrates from (hydrateAppConfig+hydrateIntegrations), so the child needs no new loader and no second hydration path.Order is load-bearing
Integration configs hold
cfg:references. Resolving them first expands pre-swap values and the override silently does nothing — a test quietly pointed at the real database instead of the sandbox one. The ordering test fails if the two steps are swapped.For the same reason integrations are re-resolved from their rows instead of copied out of
dbIntegrationsCache: the cached ones were already expanded against the un-overridden config, so copying would make the rule unenforceable.Loader refactor
Per-row resolution extracted into the exported
resolveIntegrationConfig(row, appConfig)inintegrationsLoader.ts. The app config map is a parameter now instead of agetAppConfigcache read;loadFromDBpasses the live one, the test runner passes the overridden one. No new decryption —loadFromDBbehaviour is unchanged.Bug fixed on the way
The three near-identical
mapIntegrationTo{Pg,Mysql,Mongo}ConnectionDatahelpers wrote the expanded url back onto the row (config.url = ...), destroying thecfg:reference. Harmless when a row is resolved once; fatal here, where the same row is resolved twice (live config, then overridden). Collapsed into onemapIntegrationToConnectionData(appConfig, config, parseUrl)that expands into a new object. A missing cfg key now logs "Failed to load integration" instead of throwing a TypeError onundefined.match.Tests
tests/resolve.spec.ts— ordering (fails if flipped), no-override identity, id swap, unknown override target throws.tests/resolve.integration.spec.ts+tests/fixtures/hydrateChild.ts— spawns a real child process, ships the payload over IPC, child hydrates through the production loaders and reads values back throughgetAppConfig/dbIntegrationsCache. Proves three things the in-process spec cannot:child.send()cannot fail on itPre-commit green on both commits: lint 9/9, 662 pass / 0 fail across 121 files.
Notes for #217
The spawn mechanism is now verified end to end, but the child here is a test stub — it only hydrates. The real
testExecutionProcess.ts(instantiate, run, watchdog, rlimits) is still #217. One gotcha worth carrying over:new URL(...).pathnameyields/D:/...on Windows and the child dies withModule not found— usefileURLToPath.Out of scope
No ownership check here —
assertOverridesOwnedis called by the orchestrator in #219, in the parent, before dispatch. Only this project's rows (and unowned ones) are loaded, so an override naming a foreign integration throws.🤖 Generated with Claude Code
https://claude.ai/code/session_018pjR2Xuyxu5UTUDFo9smSd