@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.
- package/dist/console/assets/{index-C146q2kN.js → index-Bl5-Ghuu.js} +1 -1
- package/dist/console/index.html +1 -1
- package/dist/manifest.json +3 -3
- package/docs/README.md +57 -0
- package/docs/adrs/001-hybrid-storage-backend.md +38 -0
- package/docs/adrs/002-four-layer-context-classification.md +38 -0
- package/docs/adrs/003-checkpoint-trigger-strategy.md +35 -0
- package/docs/adrs/004-opt-in-encryption-strategy.md +36 -0
- package/docs/adrs/005-agent-first-workflow-execution-tokens.md +105 -0
- package/docs/adrs/006-append-only-session-run-event-log.md +76 -0
- package/docs/adrs/007-resume-and-checkpoint-only-sessions.md +51 -0
- package/docs/adrs/008-blocked-nodes-architectural-upgrade.md +178 -0
- package/docs/adrs/009-bridge-mode-single-instance-mcp.md +195 -0
- package/docs/adrs/010-release-pipeline.md +89 -0
- package/docs/architecture/README.md +7 -0
- package/docs/architecture/refactor-audit.md +364 -0
- package/docs/authoring-v2.md +527 -0
- package/docs/authoring.md +873 -0
- package/docs/changelog-recent.md +201 -0
- package/docs/configuration.md +505 -0
- package/docs/ctc-mcp-proposal.md +518 -0
- package/docs/design/README.md +22 -0
- package/docs/design/agent-cascade-protocol.md +96 -0
- package/docs/design/autonomous-console-design-candidates.md +253 -0
- package/docs/design/autonomous-console-design-review.md +111 -0
- package/docs/design/autonomous-platform-mvp-discovery.md +525 -0
- package/docs/design/claude-code-source-deep-dive.md +713 -0
- package/docs/design/console-cyberpunk-ui-discovery.md +504 -0
- package/docs/design/console-execution-trace-candidates-final.md +160 -0
- package/docs/design/console-execution-trace-candidates.md +211 -0
- package/docs/design/console-execution-trace-design-candidates-v2.md +113 -0
- package/docs/design/console-execution-trace-design-review.md +74 -0
- package/docs/design/console-execution-trace-discovery.md +394 -0
- package/docs/design/console-execution-trace-final-review.md +77 -0
- package/docs/design/console-execution-trace-review.md +92 -0
- package/docs/design/console-performance-discovery.md +415 -0
- package/docs/design/console-ui-backlog.md +280 -0
- package/docs/design/daemon-architecture-discovery.md +853 -0
- package/docs/design/daemon-design-candidates.md +318 -0
- package/docs/design/daemon-design-review-findings.md +119 -0
- package/docs/design/daemon-engine-design-candidates.md +210 -0
- package/docs/design/daemon-engine-design-review.md +131 -0
- package/docs/design/daemon-execution-engine-discovery.md +280 -0
- package/docs/design/daemon-gap-analysis.md +554 -0
- package/docs/design/daemon-owns-console-plan.md +168 -0
- package/docs/design/daemon-owns-console-review.md +91 -0
- package/docs/design/daemon-owns-console.md +195 -0
- package/docs/design/data-model-erd.md +11 -0
- package/docs/design/design-candidates-consolidate-dev-staleness.md +98 -0
- package/docs/design/design-candidates-walk-cache-depth-limit.md +80 -0
- package/docs/design/design-review-consolidate-dev-staleness.md +54 -0
- package/docs/design/design-review-walk-cache-depth-limit.md +48 -0
- package/docs/design/implementation-plan-consolidate-dev-staleness.md +142 -0
- package/docs/design/implementation-plan-walk-cache-depth-limit.md +141 -0
- package/docs/design/layer3b-ghost-nodes-design-candidates.md +229 -0
- package/docs/design/layer3b-ghost-nodes-design-review.md +93 -0
- package/docs/design/layer3b-ghost-nodes-implementation-plan.md +219 -0
- package/docs/design/list-workflows-latency-fix-plan.md +128 -0
- package/docs/design/list-workflows-latency-fix-review.md +55 -0
- package/docs/design/list-workflows-latency-fix.md +109 -0
- package/docs/design/native-context-management-api.md +11 -0
- package/docs/design/performance-sweep-2026-04.md +96 -0
- package/docs/design/routines-guide.md +219 -0
- package/docs/design/sequence-diagrams.md +11 -0
- package/docs/design/subagent-design-principles.md +220 -0
- package/docs/design/temporal-patterns-design-candidates.md +312 -0
- package/docs/design/temporal-patterns-design-review-findings.md +163 -0
- package/docs/design/test-isolation-from-config-file.md +335 -0
- package/docs/design/v2-core-design-locks.md +2746 -0
- package/docs/design/v2-lock-registry.json +734 -0
- package/docs/design/workflow-authoring-v2.md +1044 -0
- package/docs/design/workflow-docs-spec.md +218 -0
- package/docs/design/workflow-extension-points.md +687 -0
- package/docs/design/workrail-auto-trigger-system.md +359 -0
- package/docs/design/workrail-config-file-discovery.md +513 -0
- package/docs/docker.md +110 -0
- package/docs/generated/v2-lock-closure-plan.md +26 -0
- package/docs/generated/v2-lock-coverage.json +797 -0
- package/docs/generated/v2-lock-coverage.md +177 -0
- package/docs/ideas/backlog.md +3927 -0
- package/docs/ideas/design-candidates-mcp-resilience.md +208 -0
- package/docs/ideas/design-review-findings-mcp-resilience.md +119 -0
- package/docs/ideas/implementation_plan.md +249 -0
- package/docs/ideas/third-party-workflow-setup-design-thinking.md +1948 -0
- package/docs/implementation/02-architecture.md +316 -0
- package/docs/implementation/04-testing-strategy.md +124 -0
- package/docs/implementation/09-simple-workflow-guide.md +835 -0
- package/docs/implementation/13-advanced-validation-guide.md +874 -0
- package/docs/implementation/README.md +21 -0
- package/docs/integrations/claude-code.md +300 -0
- package/docs/integrations/firebender.md +315 -0
- package/docs/migration/v0.1.0.md +147 -0
- package/docs/naming-conventions.md +45 -0
- package/docs/planning/README.md +104 -0
- package/docs/planning/github-ticketing-playbook.md +195 -0
- package/docs/plans/README.md +24 -0
- package/docs/plans/agent-managed-ticketing-design.md +605 -0
- package/docs/plans/agentic-orchestration-roadmap.md +112 -0
- package/docs/plans/assessment-gates-engine-handoff.md +536 -0
- package/docs/plans/content-coherence-and-references.md +151 -0
- package/docs/plans/library-extraction-plan.md +340 -0
- package/docs/plans/mr-review-workflow-redesign.md +1451 -0
- package/docs/plans/native-context-management-epic.md +11 -0
- package/docs/plans/perf-fixes-design-candidates.md +225 -0
- package/docs/plans/perf-fixes-design-review-findings.md +61 -0
- package/docs/plans/perf-fixes-new-issues-candidates.md +264 -0
- package/docs/plans/perf-fixes-new-issues-review.md +110 -0
- package/docs/plans/prompt-fragments.md +53 -0
- package/docs/plans/ui-ux-workflow-design-candidates.md +120 -0
- package/docs/plans/ui-ux-workflow-discovery.md +100 -0
- package/docs/plans/ui-ux-workflow-review.md +48 -0
- package/docs/plans/v2-followup-enhancements.md +587 -0
- package/docs/plans/workflow-categories-candidates.md +105 -0
- package/docs/plans/workflow-categories-discovery.md +110 -0
- package/docs/plans/workflow-categories-review.md +51 -0
- package/docs/plans/workflow-discovery-model-candidates.md +94 -0
- package/docs/plans/workflow-discovery-model-discovery.md +74 -0
- package/docs/plans/workflow-discovery-model-review.md +48 -0
- package/docs/plans/workflow-source-setup-phase-1.md +245 -0
- package/docs/plans/workflow-source-setup-phase-2.md +361 -0
- package/docs/plans/workflow-staleness-detection-candidates.md +104 -0
- package/docs/plans/workflow-staleness-detection-review.md +58 -0
- package/docs/plans/workflow-staleness-detection.md +80 -0
- package/docs/plans/workflow-v2-design.md +69 -0
- package/docs/plans/workflow-v2-roadmap.md +74 -0
- package/docs/plans/workflow-validation-design.md +98 -0
- package/docs/plans/workflow-validation-roadmap.md +108 -0
- package/docs/plans/workrail-platform-vision.md +420 -0
- package/docs/reference/agent-context-cleaner-snippet.md +94 -0
- package/docs/reference/agent-context-guidance.md +140 -0
- package/docs/reference/context-optimization.md +284 -0
- package/docs/reference/example-workflow-repository-template/.github/workflows/validate.yml +125 -0
- package/docs/reference/example-workflow-repository-template/README.md +268 -0
- package/docs/reference/example-workflow-repository-template/workflows/example-workflow.json +80 -0
- package/docs/reference/external-workflow-repositories.md +916 -0
- package/docs/reference/feature-flags-architecture.md +472 -0
- package/docs/reference/feature-flags.md +349 -0
- package/docs/reference/god-tier-workflow-validation.md +272 -0
- package/docs/reference/loop-optimization.md +209 -0
- package/docs/reference/loop-validation.md +176 -0
- package/docs/reference/loops.md +465 -0
- package/docs/reference/mcp-platform-constraints.md +59 -0
- package/docs/reference/recovery.md +88 -0
- package/docs/reference/releases.md +177 -0
- package/docs/reference/troubleshooting.md +105 -0
- package/docs/reference/workflow-execution-contract.md +998 -0
- package/docs/roadmap/README.md +22 -0
- package/docs/roadmap/legacy-planning-status.md +103 -0
- package/docs/roadmap/now-next-later.md +70 -0
- package/docs/roadmap/open-work-inventory.md +389 -0
- package/docs/tickets/README.md +39 -0
- package/docs/tickets/next-up.md +76 -0
- package/docs/workflow-management.md +317 -0
- package/docs/workflow-templates.md +423 -0
- package/docs/workflow-validation.md +184 -0
- package/docs/workflows.md +254 -0
- package/package.json +4 -1
- package/spec/authoring-spec.json +61 -16
- package/workflows/workflow-for-workflows.json +3 -3
- 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).
|