@exaudeus/workrail 3.28.0 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/console/assets/{index-C146q2kN.js → index-Bl5-Ghuu.js} +1 -1
  2. package/dist/console/index.html +1 -1
  3. package/dist/manifest.json +3 -3
  4. package/docs/README.md +57 -0
  5. package/docs/adrs/001-hybrid-storage-backend.md +38 -0
  6. package/docs/adrs/002-four-layer-context-classification.md +38 -0
  7. package/docs/adrs/003-checkpoint-trigger-strategy.md +35 -0
  8. package/docs/adrs/004-opt-in-encryption-strategy.md +36 -0
  9. package/docs/adrs/005-agent-first-workflow-execution-tokens.md +105 -0
  10. package/docs/adrs/006-append-only-session-run-event-log.md +76 -0
  11. package/docs/adrs/007-resume-and-checkpoint-only-sessions.md +51 -0
  12. package/docs/adrs/008-blocked-nodes-architectural-upgrade.md +178 -0
  13. package/docs/adrs/009-bridge-mode-single-instance-mcp.md +195 -0
  14. package/docs/adrs/010-release-pipeline.md +89 -0
  15. package/docs/architecture/README.md +7 -0
  16. package/docs/architecture/refactor-audit.md +364 -0
  17. package/docs/authoring-v2.md +527 -0
  18. package/docs/authoring.md +873 -0
  19. package/docs/changelog-recent.md +201 -0
  20. package/docs/configuration.md +505 -0
  21. package/docs/ctc-mcp-proposal.md +518 -0
  22. package/docs/design/README.md +22 -0
  23. package/docs/design/agent-cascade-protocol.md +96 -0
  24. package/docs/design/autonomous-console-design-candidates.md +253 -0
  25. package/docs/design/autonomous-console-design-review.md +111 -0
  26. package/docs/design/autonomous-platform-mvp-discovery.md +525 -0
  27. package/docs/design/claude-code-source-deep-dive.md +713 -0
  28. package/docs/design/console-cyberpunk-ui-discovery.md +504 -0
  29. package/docs/design/console-execution-trace-candidates-final.md +160 -0
  30. package/docs/design/console-execution-trace-candidates.md +211 -0
  31. package/docs/design/console-execution-trace-design-candidates-v2.md +113 -0
  32. package/docs/design/console-execution-trace-design-review.md +74 -0
  33. package/docs/design/console-execution-trace-discovery.md +394 -0
  34. package/docs/design/console-execution-trace-final-review.md +77 -0
  35. package/docs/design/console-execution-trace-review.md +92 -0
  36. package/docs/design/console-performance-discovery.md +415 -0
  37. package/docs/design/console-ui-backlog.md +280 -0
  38. package/docs/design/daemon-architecture-discovery.md +853 -0
  39. package/docs/design/daemon-design-candidates.md +318 -0
  40. package/docs/design/daemon-design-review-findings.md +119 -0
  41. package/docs/design/daemon-engine-design-candidates.md +210 -0
  42. package/docs/design/daemon-engine-design-review.md +131 -0
  43. package/docs/design/daemon-execution-engine-discovery.md +280 -0
  44. package/docs/design/daemon-gap-analysis.md +554 -0
  45. package/docs/design/daemon-owns-console-plan.md +168 -0
  46. package/docs/design/daemon-owns-console-review.md +91 -0
  47. package/docs/design/daemon-owns-console.md +195 -0
  48. package/docs/design/data-model-erd.md +11 -0
  49. package/docs/design/design-candidates-consolidate-dev-staleness.md +98 -0
  50. package/docs/design/design-candidates-walk-cache-depth-limit.md +80 -0
  51. package/docs/design/design-review-consolidate-dev-staleness.md +54 -0
  52. package/docs/design/design-review-walk-cache-depth-limit.md +48 -0
  53. package/docs/design/implementation-plan-consolidate-dev-staleness.md +142 -0
  54. package/docs/design/implementation-plan-walk-cache-depth-limit.md +141 -0
  55. package/docs/design/layer3b-ghost-nodes-design-candidates.md +229 -0
  56. package/docs/design/layer3b-ghost-nodes-design-review.md +93 -0
  57. package/docs/design/layer3b-ghost-nodes-implementation-plan.md +219 -0
  58. package/docs/design/list-workflows-latency-fix-plan.md +128 -0
  59. package/docs/design/list-workflows-latency-fix-review.md +55 -0
  60. package/docs/design/list-workflows-latency-fix.md +109 -0
  61. package/docs/design/native-context-management-api.md +11 -0
  62. package/docs/design/performance-sweep-2026-04.md +96 -0
  63. package/docs/design/routines-guide.md +219 -0
  64. package/docs/design/sequence-diagrams.md +11 -0
  65. package/docs/design/subagent-design-principles.md +220 -0
  66. package/docs/design/temporal-patterns-design-candidates.md +312 -0
  67. package/docs/design/temporal-patterns-design-review-findings.md +163 -0
  68. package/docs/design/test-isolation-from-config-file.md +335 -0
  69. package/docs/design/v2-core-design-locks.md +2746 -0
  70. package/docs/design/v2-lock-registry.json +734 -0
  71. package/docs/design/workflow-authoring-v2.md +1044 -0
  72. package/docs/design/workflow-docs-spec.md +218 -0
  73. package/docs/design/workflow-extension-points.md +687 -0
  74. package/docs/design/workrail-auto-trigger-system.md +359 -0
  75. package/docs/design/workrail-config-file-discovery.md +513 -0
  76. package/docs/docker.md +110 -0
  77. package/docs/generated/v2-lock-closure-plan.md +26 -0
  78. package/docs/generated/v2-lock-coverage.json +797 -0
  79. package/docs/generated/v2-lock-coverage.md +177 -0
  80. package/docs/ideas/backlog.md +3927 -0
  81. package/docs/ideas/design-candidates-mcp-resilience.md +208 -0
  82. package/docs/ideas/design-review-findings-mcp-resilience.md +119 -0
  83. package/docs/ideas/implementation_plan.md +249 -0
  84. package/docs/ideas/third-party-workflow-setup-design-thinking.md +1948 -0
  85. package/docs/implementation/02-architecture.md +316 -0
  86. package/docs/implementation/04-testing-strategy.md +124 -0
  87. package/docs/implementation/09-simple-workflow-guide.md +835 -0
  88. package/docs/implementation/13-advanced-validation-guide.md +874 -0
  89. package/docs/implementation/README.md +21 -0
  90. package/docs/integrations/claude-code.md +300 -0
  91. package/docs/integrations/firebender.md +315 -0
  92. package/docs/migration/v0.1.0.md +147 -0
  93. package/docs/naming-conventions.md +45 -0
  94. package/docs/planning/README.md +104 -0
  95. package/docs/planning/github-ticketing-playbook.md +195 -0
  96. package/docs/plans/README.md +24 -0
  97. package/docs/plans/agent-managed-ticketing-design.md +605 -0
  98. package/docs/plans/agentic-orchestration-roadmap.md +112 -0
  99. package/docs/plans/assessment-gates-engine-handoff.md +536 -0
  100. package/docs/plans/content-coherence-and-references.md +151 -0
  101. package/docs/plans/library-extraction-plan.md +340 -0
  102. package/docs/plans/mr-review-workflow-redesign.md +1451 -0
  103. package/docs/plans/native-context-management-epic.md +11 -0
  104. package/docs/plans/perf-fixes-design-candidates.md +225 -0
  105. package/docs/plans/perf-fixes-design-review-findings.md +61 -0
  106. package/docs/plans/perf-fixes-new-issues-candidates.md +264 -0
  107. package/docs/plans/perf-fixes-new-issues-review.md +110 -0
  108. package/docs/plans/prompt-fragments.md +53 -0
  109. package/docs/plans/ui-ux-workflow-design-candidates.md +120 -0
  110. package/docs/plans/ui-ux-workflow-discovery.md +100 -0
  111. package/docs/plans/ui-ux-workflow-review.md +48 -0
  112. package/docs/plans/v2-followup-enhancements.md +587 -0
  113. package/docs/plans/workflow-categories-candidates.md +105 -0
  114. package/docs/plans/workflow-categories-discovery.md +110 -0
  115. package/docs/plans/workflow-categories-review.md +51 -0
  116. package/docs/plans/workflow-discovery-model-candidates.md +94 -0
  117. package/docs/plans/workflow-discovery-model-discovery.md +74 -0
  118. package/docs/plans/workflow-discovery-model-review.md +48 -0
  119. package/docs/plans/workflow-source-setup-phase-1.md +245 -0
  120. package/docs/plans/workflow-source-setup-phase-2.md +361 -0
  121. package/docs/plans/workflow-staleness-detection-candidates.md +104 -0
  122. package/docs/plans/workflow-staleness-detection-review.md +58 -0
  123. package/docs/plans/workflow-staleness-detection.md +80 -0
  124. package/docs/plans/workflow-v2-design.md +69 -0
  125. package/docs/plans/workflow-v2-roadmap.md +74 -0
  126. package/docs/plans/workflow-validation-design.md +98 -0
  127. package/docs/plans/workflow-validation-roadmap.md +108 -0
  128. package/docs/plans/workrail-platform-vision.md +420 -0
  129. package/docs/reference/agent-context-cleaner-snippet.md +94 -0
  130. package/docs/reference/agent-context-guidance.md +140 -0
  131. package/docs/reference/context-optimization.md +284 -0
  132. package/docs/reference/example-workflow-repository-template/.github/workflows/validate.yml +125 -0
  133. package/docs/reference/example-workflow-repository-template/README.md +268 -0
  134. package/docs/reference/example-workflow-repository-template/workflows/example-workflow.json +80 -0
  135. package/docs/reference/external-workflow-repositories.md +916 -0
  136. package/docs/reference/feature-flags-architecture.md +472 -0
  137. package/docs/reference/feature-flags.md +349 -0
  138. package/docs/reference/god-tier-workflow-validation.md +272 -0
  139. package/docs/reference/loop-optimization.md +209 -0
  140. package/docs/reference/loop-validation.md +176 -0
  141. package/docs/reference/loops.md +465 -0
  142. package/docs/reference/mcp-platform-constraints.md +59 -0
  143. package/docs/reference/recovery.md +88 -0
  144. package/docs/reference/releases.md +177 -0
  145. package/docs/reference/troubleshooting.md +105 -0
  146. package/docs/reference/workflow-execution-contract.md +998 -0
  147. package/docs/roadmap/README.md +22 -0
  148. package/docs/roadmap/legacy-planning-status.md +103 -0
  149. package/docs/roadmap/now-next-later.md +70 -0
  150. package/docs/roadmap/open-work-inventory.md +389 -0
  151. package/docs/tickets/README.md +39 -0
  152. package/docs/tickets/next-up.md +76 -0
  153. package/docs/workflow-management.md +317 -0
  154. package/docs/workflow-templates.md +423 -0
  155. package/docs/workflow-validation.md +184 -0
  156. package/docs/workflows.md +254 -0
  157. package/package.json +4 -1
  158. package/spec/authoring-spec.json +61 -16
  159. package/workflows/workflow-for-workflows.json +3 -3
  160. package/workflows/workflow-for-workflows.v2.json +3 -3
@@ -0,0 +1,335 @@
1
+ # Design: Isolating Integration Tests from ~/.workrail/config.json
2
+
3
+ **Status:** Draft
4
+ **Date:** 2026-04-06
5
+ **Scope:** `src/config/config-file.ts`, `src/di/container.ts`, `src/mcp/v2-response-formatter.ts`, `src/v2/durable-core/domain/prompt-renderer.ts`, and all test infrastructure under `tests/`
6
+
7
+ ---
8
+
9
+ ## Problem
10
+
11
+ WorkRail reads `~/.workrail/config.json` at container initialization and merges it into `process.env` before constructing any services. This is correct behavior for production; the file is the mechanism for user-level defaults.
12
+
13
+ The problem is that integration tests that spin up an in-process server (`startHttpServer`, `initializeContainer`) are now sensitive to whatever the developer has set in their personal config file. The concrete breakage: `tests/integration/mcp-http-transport.test.ts` started failing when the developer has `WORKRAIL_CLEAN_RESPONSE_FORMAT=true` in their config, because the test asserts against the default (non-clean) response format.
14
+
15
+ A workaround was added (`process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT = 'false'`) and then reverted, which is the correct instinct: the workaround is the wrong fix. The test is correct; the isolation contract is wrong.
16
+
17
+ ---
18
+
19
+ ## Landscape: what exists today
20
+
21
+ ### The config loading flow
22
+
23
+ 1. `mcp-server.ts` / `stdio-entry.ts` / `http-entry.ts` call `composeServer()` -> `bootstrap()` -> `initializeContainer()`.
24
+ 2. `initializeContainer()` calls `registerConfig()`.
25
+ 3. `registerConfig()` calls `loadWorkrailConfigFile()`, which reads `~/.workrail/config.json` unconditionally.
26
+ 4. The result is merged as `mergedEnv = { ...configFileValues, ...process.env }` (env wins, file provides defaults).
27
+ 5. `mergedEnv` is used to construct `AppConfig` and `CustomEnvFeatureFlagProvider`.
28
+ 6. `mergedEnv` is captured by closure and passed to `LocalDataDirV2`.
29
+
30
+ ### The VITEST guard that exists -- and the one that doesn't
31
+
32
+ `config-file.ts` has no VITEST guard on `loadWorkrailConfigFile`. The comment in the problem statement says "note the VITEST guard on `ensureWorkrailConfigFile` but NOT on `loadWorkrailConfigFile`". The current code has no `ensureWorkrailConfigFile` function at all -- it was either removed or not yet added. What _does_ exist is:
33
+
34
+ - `detectRuntimeMode()` in `container.ts` correctly detects `process.env.VITEST` to set `RuntimeMode = 'test'`.
35
+ - `test-container.ts` (`setupTest`) pre-registers `DI.Config.App` before calling `initializeContainer`, which causes `registerConfig()` to skip the `loadConfig` call via the `if (!container.isRegistered(DI.Config.App))` guard.
36
+ - The same guard also causes `registerConfig()` to skip registering `FeatureFlags` if already registered.
37
+
38
+ So `setupTest()` already correctly bypasses config file injection for tests that use `test-container.ts`. The problem is tests that do NOT use `setupTest()` and instead call `initializeContainer()` directly.
39
+
40
+ ### The direct `process.env` reads (the deeper problem)
41
+
42
+ Two source files read `process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT` directly, bypassing the DI system entirely:
43
+
44
+ - `src/mcp/v2-response-formatter.ts` line 409: `return process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT === 'true';`
45
+ - `src/v2/durable-core/domain/prompt-renderer.ts` line 468: `const cleanResponseFormat = process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT === 'true';`
46
+
47
+ This means that even if the DI container is correctly isolated, these two functions will still pick up the developer's personal config setting -- because `loadWorkrailConfigFile()` writes into `mergedEnv`, not `process.env`. Wait: this means the two `process.env` direct reads would NOT be affected by the config file, because `loadWorkrailConfigFile()` only populates `mergedEnv` (a module-level variable in `container.ts`), not `process.env` itself. So the direct `process.env` reads are a separate (but related) violation of the DI contract: they bypass both `mergedEnv` and the feature flag system.
48
+
49
+ ### How the developer's config.json actually reaches the test
50
+
51
+ The developer has `WORKRAIL_CLEAN_RESPONSE_FORMAT=true` in `~/.workrail/config.json`. When `mcp-http-transport.test.ts` calls `startHttpServer(HTTP_PORT)`, the call chain is:
52
+
53
+ `startHttpServer` -> `composeServer` -> `bootstrap` -> `initializeContainer` -> `registerConfig` -> `loadWorkrailConfigFile` -> reads `~/.workrail/config.json` -> merges into `mergedEnv` -> constructs `CustomEnvFeatureFlagProvider(mergedEnv)` -> `featureFlags.isEnabled('cleanResponseFormat')` returns `true`.
54
+
55
+ But the direct `process.env` reads in `v2-response-formatter.ts` and `prompt-renderer.ts` read from `process.env`, not `mergedEnv`. Since the developer's config sets `WORKRAIL_CLEAN_RESPONSE_FORMAT=true` in the config file (not in `process.env` directly), these two direct reads would actually return `false` even when the DI feature flag says `true`. This is an existing inconsistency: the feature flag system and the direct `process.env` reads diverge.
56
+
57
+ The immediate test failure is through the DI path (the feature flags system sees `cleanResponseFormat=true` via `mergedEnv`), not through the direct reads. But both paths represent the same underlying problem.
58
+
59
+ ### Tests that call `initializeContainer()` directly (the affected set)
60
+
61
+ The following tests call `initializeContainer()` without pre-registering config, and are therefore exposed to the developer's `~/.workrail/config.json`:
62
+
63
+ - `tests/integration/mcp-http-transport.test.ts` -- confirmed broken
64
+ - `tests/integration/process-cleanup.test.ts` -- calls `initializeContainer()` in `beforeAll`, no env setup
65
+ - `tests/integration/tsyringe-di.test.ts` -- calls `initializeContainer()` in each `beforeEach`
66
+ - `tests/integration/bug-fixes-integration.test.ts` -- calls `initializeContainer()` directly
67
+ - `tests/integration/unified-dashboard.test.ts` -- calls `initializeContainer()` in `beforeAll`
68
+ - `tests/smoke/di-container.smoke.test.ts` -- calls `initializeContainer()` multiple times
69
+ - `tests/contract/transport-equivalence.test.ts` -- calls `bootstrap()` directly
70
+ - `tests/unit/git-worktree.test.ts` -- calls `initializeContainer()`
71
+ - `tests/performance/cache-eviction.test.ts` -- calls `initializeContainer()`
72
+
73
+ Tests that use `setupTest()` (from `test-container.ts`) are protected because they pre-register `DI.Config.App`, which blocks the config-file loading path in `registerConfig()`.
74
+
75
+ ---
76
+
77
+ ## Candidate solutions
78
+
79
+ ### Option A: VITEST guard on `loadWorkrailConfigFile`
80
+
81
+ Add an early return in `loadWorkrailConfigFile()` when running under Vitest:
82
+
83
+ ```typescript
84
+ export function loadWorkrailConfigFile(): Result<Record<string, string>, ConfigFileError> {
85
+ if (process.env.VITEST) {
86
+ return ok({});
87
+ }
88
+ // ... existing logic
89
+ }
90
+ ```
91
+
92
+ **Pros:**
93
+ - Single-line fix; zero ripple to test infrastructure.
94
+ - Consistent with the `detectRuntimeMode` pattern already in `container.ts`.
95
+ - Eliminates the entire class of failures for all tests at once.
96
+ - No test changes required.
97
+
98
+ **Cons:**
99
+ - Makes `loadWorkrailConfigFile()` impure (it inspects a runtime sentinel that's external to its inputs). This violates "determinism over cleverness" and "validate at boundaries, trust inside".
100
+ - Tests cannot exercise the config-file loading path.
101
+ - If someone later adds a smoke test for config file loading, it silently does nothing.
102
+
103
+ ---
104
+
105
+ ### Option B: VITEST guard in `registerConfig()` in `container.ts`
106
+
107
+ Instead of guarding the load function itself, guard the merge step in the composition root:
108
+
109
+ ```typescript
110
+ async function registerConfig(): Promise<void> {
111
+ const isTestEnv = Boolean(process.env.VITEST);
112
+ const configFileValues: Record<string, string> = isTestEnv
113
+ ? {}
114
+ : (() => {
115
+ const r = loadWorkrailConfigFile();
116
+ return r.kind === 'ok' ? r.value : {};
117
+ })();
118
+ mergedEnv = { ...configFileValues, ...process.env };
119
+ // ... rest unchanged
120
+ }
121
+ ```
122
+
123
+ **Pros:**
124
+ - `loadWorkrailConfigFile()` stays pure; it can be tested independently.
125
+ - The guard is in the composition root, which is the correct place for runtime-mode decisions (consistent with `detectRuntimeMode`).
126
+ - Same single-point fix; all affected tests healed at once.
127
+
128
+ **Cons:**
129
+ - Same testability concern: tests cannot exercise the "config file loaded and merged" initialization path.
130
+ - Relies on `process.env.VITEST` being set, which is true for Vitest tests but may not be true for other test runners if the project ever changes.
131
+
132
+ ---
133
+
134
+ ### Option C: Accept an explicit env override in `initializeContainer`
135
+
136
+ Extend `ContainerInitOptions` to accept an env map, and use it instead of `process.env` + config file when provided:
137
+
138
+ ```typescript
139
+ export interface ContainerInitOptions {
140
+ readonly runtimeMode?: RuntimeMode;
141
+ readonly env?: Record<string, string | undefined>; // explicit env; bypasses config file
142
+ }
143
+ ```
144
+
145
+ In `registerConfig()`:
146
+
147
+ ```typescript
148
+ async function registerConfig(env?: Record<string, string | undefined>): Promise<void> {
149
+ if (env !== undefined) {
150
+ // Caller provided explicit env; skip config file loading entirely.
151
+ mergedEnv = env;
152
+ } else {
153
+ const configFileValues = loadWorkrailConfigFile().kind === 'ok' ? ... : {};
154
+ mergedEnv = { ...configFileValues, ...process.env };
155
+ }
156
+ // ... rest unchanged
157
+ }
158
+ ```
159
+
160
+ Tests that want isolation:
161
+
162
+ ```typescript
163
+ await initializeContainer({ runtimeMode: { kind: 'test' }, env: { ...process.env } });
164
+ ```
165
+
166
+ Or a clean env:
167
+
168
+ ```typescript
169
+ await initializeContainer({
170
+ runtimeMode: { kind: 'test' },
171
+ env: { WORKRAIL_ENABLE_SESSION_TOOLS: 'false', WORKRAIL_ENABLE_V2_TOOLS: 'true' },
172
+ });
173
+ ```
174
+
175
+ **Pros:**
176
+ - Makes test env state explicit and declarative: the reader sees exactly what env the test is running with.
177
+ - Honors "validate at boundaries, trust inside": the container is now a pure function of its inputs.
178
+ - No sentinel (VITEST) check required; the mechanism is compositional.
179
+ - Tests can exercise config-file loading separately by calling `loadWorkrailConfigFile()` directly.
180
+ - Compatible with `test-container.ts` / `setupTest()` patterns without changes.
181
+ - The `env` opt-in means existing production code paths are unchanged.
182
+
183
+ **Cons:**
184
+ - Requires updating `initializeContainer` signature (non-breaking; new optional field).
185
+ - Test files that call `initializeContainer()` directly need to be updated to pass `env` -- or a convenience default (e.g., skip config file when `runtimeMode.kind === 'test'`) handles it automatically.
186
+ - `mcp-http-transport.test.ts` bypasses `initializeContainer` entirely (it calls `startHttpServer`, which calls `bootstrap`); this option alone doesn't fix that without also threading `env` through `bootstrap` and the entry points.
187
+
188
+ ---
189
+
190
+ ### Option D: Whitelist approach -- tests declare the flags they require
191
+
192
+ Rather than suppressing the config file, tests explicitly declare the feature-flag state they need by always pre-registering flags via `setupTest()` or equivalent:
193
+
194
+ Mandate that every integration test that uses the container calls `setupTest()` (or `setupIntegrationTest()`), and add a lint rule or CI check that flags direct `initializeContainer()` calls in test files without prior config registration.
195
+
196
+ **Pros:**
197
+ - No change to production code.
198
+ - Forces test authors to be explicit about required state.
199
+ - Consistent with existing pattern in `test-container.ts`.
200
+
201
+ **Cons:**
202
+ - Requires changes to ~10 test files that currently call `initializeContainer()` directly.
203
+ - Does not address `mcp-http-transport.test.ts`, which bypasses the container entirely via `startHttpServer`.
204
+ - Fragile: requires process discipline rather than a structural constraint. New test files can regress.
205
+ - Does not address the `prompt-renderer.ts` and `v2-response-formatter.ts` `process.env` direct reads.
206
+
207
+ ---
208
+
209
+ ### Option E: Propagate `mergedEnv` as an injected dependency
210
+
211
+ Make the merged env an explicit DI token, and pass it through to all consumers (including `LocalDataDirV2`, `EnvironmentFeatureFlagProvider`, `prompt-renderer`, `v2-response-formatter`).
212
+
213
+ **Pros:**
214
+ - Architecturally correct: environment becomes an explicit, injectable dependency.
215
+ - Enables all consumers to be tested with a controlled env.
216
+ - Eliminates the direct `process.env` reads in `prompt-renderer.ts` and `v2-response-formatter.ts`.
217
+
218
+ **Cons:**
219
+ - Large-footprint change: all consumers of env vars need to receive the injected env rather than reading `process.env` directly.
220
+ - `prompt-renderer.ts` is domain logic; adding an env token as a DI dependency pollutes it.
221
+ - High effort relative to the benefit; over-engineered for the immediate problem.
222
+
223
+ ---
224
+
225
+ ## Analysis and selection
226
+
227
+ ### The real root cause
228
+
229
+ The root cause has two layers:
230
+
231
+ 1. **Primary:** `loadWorkrailConfigFile()` is called unconditionally in `registerConfig()`, including when VITEST is set and a developer is running tests. This is the structural invariant that must change.
232
+
233
+ 2. **Secondary:** `prompt-renderer.ts` and `v2-response-formatter.ts` read `process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT` directly, bypassing both the feature flag system and `mergedEnv`. This is a separate violation of the "validate at boundaries, trust inside" principle, but it is NOT the cause of the reported failure (the config file only populates `mergedEnv`, not `process.env`). It would cause failures if someone sets `WORKRAIL_CLEAN_RESPONSE_FORMAT=true` in their shell environment directly. This should be fixed separately.
234
+
235
+ ### Why Option B wins over Option A
236
+
237
+ Both A and B add a VITEST sentinel check. Option A puts the check inside `loadWorkrailConfigFile()`, which is a leaf function that should be pure. Option B puts the check in `registerConfig()`, which is already a composition-root function that is explicitly allowed to inspect `process.env` for runtime-mode decisions (it already detects `detectRuntimeMode()` for the same reason). The sentinel belongs in the composition root.
238
+
239
+ ### Why Option C is better than Option B in principle, but can be combined
240
+
241
+ Option C (explicit env override) is architecturally superior to Option B because it eliminates the sentinel entirely. However, it requires touching ~10 test files or extending the entry points, making it higher effort. The pragmatic path is:
242
+
243
+ **Recommended: Option B as the immediate fix + Option C as a subsequent improvement.**
244
+
245
+ Option B heals all affected tests immediately with a two-line change to the composition root. Option C can be added incrementally -- `ContainerInitOptions.env` can be introduced as an optional field, and test files can be migrated over time to the explicit form.
246
+
247
+ ### The direct `process.env` reads: fix separately
248
+
249
+ The direct reads in `prompt-renderer.ts` and `v2-response-formatter.ts` should be corrected separately. The correct fix is to route the `cleanResponseFormat` flag through the DI feature flag system instead of reading `process.env` directly. This is already partially done: `prompt-renderer.ts`'s `renderPromptForNodeV2` accepts `args.cleanResponseFormat` per a comment (though the current implementation still reads `process.env`). The feature flag should be resolved once at the handler level and passed down into the domain function as a plain `boolean`. This is consistent with "dependency injection for boundaries" and "determinism over cleverness".
250
+
251
+ ---
252
+
253
+ ## Recommended solution
254
+
255
+ ### Immediate fix (Option B)
256
+
257
+ In `src/di/container.ts`, add a VITEST guard in `registerConfig()`:
258
+
259
+ ```typescript
260
+ async function registerConfig(): Promise<void> {
261
+ // In test environments, skip loading the user's personal config file.
262
+ // process.env takes full effect; tests set what they need explicitly.
263
+ // (VITEST is set by the test runner in all vitest-managed processes.)
264
+ const isTestEnv = Boolean(process.env.VITEST);
265
+ const configFileValues: Record<string, string> = isTestEnv
266
+ ? {}
267
+ : (() => {
268
+ const r = loadWorkrailConfigFile();
269
+ return r.kind === 'ok' ? r.value : {};
270
+ })();
271
+ mergedEnv = { ...configFileValues, ...process.env };
272
+
273
+ // ... rest of registerConfig unchanged
274
+ }
275
+ ```
276
+
277
+ This is a two-line addition that is fully consistent with the existing `detectRuntimeMode()` pattern, requires no test changes, and eliminates the entire class of failures.
278
+
279
+ ### Follow-up fix: remove direct `process.env` reads
280
+
281
+ In `src/mcp/v2-response-formatter.ts` and `src/v2/durable-core/domain/prompt-renderer.ts`, remove the direct `process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT` reads.
282
+
283
+ For `v2-response-formatter.ts`: the function `isCleanResponseFormat()` should accept the flag as a parameter or be replaced by passing the flag from the caller who already has access to the feature flags via DI.
284
+
285
+ For `prompt-renderer.ts`: the `renderPromptForNodeV2` function already has the infrastructure to accept `args.cleanResponseFormat` (per the comment at the call site). The implementation should use `args.cleanResponseFormat ?? false` rather than re-reading `process.env`. The caller (the handler or usecase layer) is responsible for resolving `cleanResponseFormat` from `featureFlags.isEnabled('cleanResponseFormat')` and passing it down.
286
+
287
+ This is consistent with "dependency injection for boundaries" and removes all direct `process.env` reads from domain logic.
288
+
289
+ ### Optional improvement: explicit env override in `ContainerInitOptions`
290
+
291
+ Add `env?: Record<string, string | undefined>` to `ContainerInitOptions` so tests can opt into fully declarative env. This makes the whitelist pattern (Option D) possible without process discipline: tests that need specific flag states pass them explicitly in the env override, and the absence of the override triggers the VITEST guard. Migration of existing tests to the explicit form can happen incrementally.
292
+
293
+ ---
294
+
295
+ ## Implementation plan
296
+
297
+ ### Phase 1 (immediate, 1 file, ~5 lines)
298
+
299
+ 1. In `src/di/container.ts`, add the VITEST guard in `registerConfig()` as described above.
300
+ 2. Verify `tests/integration/mcp-http-transport.test.ts` passes without any `process.env` overrides.
301
+ 3. Run the full test suite to confirm no regressions.
302
+
303
+ ### Phase 2 (follow-up, 2 files, ~20 lines)
304
+
305
+ 1. In `src/mcp/v2-response-formatter.ts`, change `isCleanResponseFormat()` to accept `cleanFormat: boolean` as a parameter. Thread it from wherever the function is called (likely from a handler that already has feature flag access).
306
+ 2. In `src/v2/durable-core/domain/prompt-renderer.ts`, change line 468 from reading `process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT` to using `args.cleanResponseFormat ?? false`. Ensure all callers pass the flag down from the feature flags system.
307
+ 3. Add a test that exercises `prompt-renderer.ts` with `cleanResponseFormat: true` explicitly, so the behavior is covered at the unit level.
308
+
309
+ ### Phase 3 (optional, ~1 day)
310
+
311
+ 1. Add `env?: Record<string, string | undefined>` to `ContainerInitOptions`.
312
+ 2. In `registerConfig()`, if `options.env` is provided, use it as `mergedEnv` directly (no config file loading).
313
+ 3. Migrate `mcp-http-transport.test.ts` to use `env` explicitly in its setup.
314
+ 4. Document the pattern in `tests/di/test-container.ts` comments.
315
+
316
+ ---
317
+
318
+ ## Confidence and residual risks
319
+
320
+ **Confidence:** High that Option B (VITEST guard) eliminates the reported failure class and all similar failures. The guard is conservative: it only skips the config file; tests still receive `process.env`, so any env vars set explicitly (e.g., `WORKRAIL_ENABLE_V2_TOOLS=true` in `mcp-http-transport.test.ts`'s `beforeAll`) continue to work.
321
+
322
+ **Risk 1:** Tests that intentionally test config file loading behavior (e.g., a future smoke test for `loadWorkrailConfigFile`) will be unaffected by the guard because the guard is in `registerConfig()`, not in `loadWorkrailConfigFile()` itself. Those tests should call `loadWorkrailConfigFile()` directly rather than going through `initializeContainer()`.
323
+
324
+ **Risk 2:** If a test needs to verify that the config file is applied during container init (integration between `loadWorkrailConfigFile` and `registerConfig`), it would need to temporarily unset `VITEST`, which is not practical. The recommended approach for that case is to call `loadWorkrailConfigFile()` and `loadConfig()` in the test directly, bypassing the container.
325
+
326
+ **Risk 3:** The direct `process.env` reads in `prompt-renderer.ts` and `v2-response-formatter.ts` remain a latent source of test sensitivity until Phase 2 is complete. A developer who sets `WORKRAIL_CLEAN_RESPONSE_FORMAT=true` in their shell (not just in `config.json`) can still cause test failures in tests that don't explicitly set that env var. Phase 2 is therefore important, not just cosmetic.
327
+
328
+ ---
329
+
330
+ ## What this is NOT
331
+
332
+ This design does not address:
333
+ - Parallelism issues between tests that call `initializeContainer()` concurrently without going through the mutex in `integration-container.ts`.
334
+ - The broader question of whether all integration tests should use `setupTest()`. That is a code quality concern, not a blocking correctness issue.
335
+ - Project-level `.workrail/config.json` files (these do not currently exist in the codebase).