fix(drivers): resolve vault sentinels for out-of-process drivers (swamp-club#263)#1321
fix(drivers): resolve vault sentinels for out-of-process drivers (swamp-club#263)#1321
Conversation
…ocess drivers (swamp-club#263) The method execution service built an ExecutionRequest with unresolved vault sentinel tokens in methodArgs and globalArgs. The raw driver resolves sentinels internally, but out-of-process drivers (docker, custom) received the sentinels as-is — delivering literal __SWAMP_VSEC_*__ strings to the model instead of decrypted values. Call secretBag.resolveDeep() on the execution request's args in the non-raw driver dispatch path before invoking the driver. The resolution operates on structuredClone data from the definition, so persisted state is never mutated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
No blocking issues found. This is a well-scoped bug fix with solid test coverage.
What the PR does well:
- Correctly identifies the gap: raw driver resolves vault sentinels internally, but out-of-process drivers (docker, custom) received unresolved sentinel strings
- Resolution placement is right — at the dispatch boundary in
DefaultMethodExecutionService, before handing off to the driver. Drivers stay vault-unaware, which is the correct DDD boundary - Definition integrity preserved:
globalArgumentsandgetMethodArguments()both returnstructuredClonecopies, so the persisted definition is never mutated - The
isEmptyguard avoids unnecessary work when no secrets are present - Test is thorough: registers a mock capture driver, dispatches through the full
executeWorkflowpath, and asserts resolved plaintext values in the capturedExecutionRequest - Design doc update clearly explains the two driver paths and the cloning guarantee
- All imports stay within the domain layer — no libswamp boundary violations
Suggestions
- Minor:
assertEquals(capturedRequest !== null, true)at line 2888 could useassertExists(capturedRequest)from@std/assertfor a more descriptive failure message. Not blocking — the existing pattern matches line 2711 in the same file, so this is consistent with current convention.
LGTM — safe to merge.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None.
Medium
None.
Low
-
src/domain/models/method_execution_service_test.ts:2836— Test-registered driver is never unregistered from the globaldriverTypeRegistrysingleton. The test uses a randomized name (test-capture-${crypto.randomUUID().slice(0, 8)}), which prevents collisions, so this won't cause flakiness. However, across a large test suite execution, orphaned entries accumulate in the singleton's map. This is purely theoretical — the process exits after tests and the map entries are tiny. -
src/domain/models/method_execution_service_test.ts:2888—assertEquals(capturedRequest !== null, true)is a weaker assertion thanassertNotEquals(capturedRequest, null). If the driver never fires, the failure message would sayfalse !== truerather than the more descriptivenull !== [something]. Cosmetic only — the subsequentcapturedRequest!assertions would also fail, giving enough diagnostic info.
Verdict
PASS. The fix is correct and minimal. Both globalArguments and getMethodArguments() return structuredCloned data, so the mutations on executionRequest.methodArgs/executionRequest.globalArgs at lines 717-722 cannot corrupt persisted definition state. The guard pattern (secretBag && !secretBag.isEmpty) and resolveDeep usage are identical to the existing raw-driver path at lines 249-257, maintaining consistency. The resolveDeep method handles nested objects/arrays recursively and always returns a new object for record inputs, making the as Record<string, unknown> cast safe. The test directly verifies that resolved values (not sentinels) reach the driver, which is the exact bug scenario described in the PR.
Summary
__SWAMP_VSEC_*__) inExecutionRequest.methodArgsandExecutionRequest.globalArgsbefore dispatching to out-of-process drivers (docker, custom extension drivers)DefaultMethodExecutionService.execute(), but the non-raw dispatch path sent sentinels through unresolved — models received literal sentinel strings instead of decrypted secret valuesstructuredClonedata from the definition, so persisted state is never mutatedTest Plan
DriverTypeRegistry, dispatches throughexecuteWorkflowwith aVaultSecretBagcontaining sentinel mappings, and asserts the capturedExecutionRequesthas resolved plaintext values — not sentinels${{ vault.get(...) }}input under docker driver now resolves correctly (sentinel__SWAMP_VSEC_8c14a9ea_0__→ redacted***)deno check,deno lint,deno fmtcleanCloses swamp-club#263
🤖 Generated with Claude Code