fix(remediation): isolate soak/shadow tests from the production data dir (#3932)#3947
Merged
Merged
Conversation
…dir (#3932) The auto-remediation cycle's default durable soak sink resolves under NEXUS_DATA_DIR (falling back to ~/.nexus-agents/learning/remediation-soak.jsonl, which the enforce-readiness gate reads via summarizeRemediationShadow → soak.total → shadowSelections). Audit-mode cases in auto-remediation-cycle.test.ts ran runAutoRemediationCycle without injecting a soakSink, so synthetic fixture records (signalKeys `a`/`b`) were written into the operator's real home-dir soak file, inflating the readiness volume criterion with non-real data. - Pin NEXUS_DATA_DIR to a throwaway mkdtempSync temp dir for the whole auto-remediation-cycle test suite (beforeEach/afterEach), with soak-sink singleton reset and cleanup, so the default sink can never reach the home dir. - Add a regression guard asserting getRemediationSoakFile() resolves under the temp NEXUS_DATA_DIR and never under ~/.nexus-agents, plus an audit-cycle test that leaves the home-dir file untouched. - Defense-in-depth: readRemediationSoakSummary() now drops structurally- implausible soak records (signalKeys lacking the real category:detail shape) via isPlausibleSoakSignalKey/filterPlausibleSoakRecords, so leaked junk can't inflate the readiness volume count. Conservative — requires only the colon shape, not a known category enum, so no legitimate record is dropped. The owner's existing ~/.nexus-agents/learning/remediation-soak.jsonl is runtime home-dir state out of repo scope; cleaning it is the owner's one-time local action. This change is the repo-level isolation + guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 #3932.
Problem
The auto-remediation enforce-readiness gate reads the durable soak sink
~/.nexus-agents/learning/remediation-soak.jsonl(viasummarizeRemediationShadow→soak.total→shadowSelections). The cycle's default soak sink (getRemediationSoakSink()) resolves underNEXUS_DATA_DIR, falling back to that home-dir file.Which tests were writing to the real soak file
Only
auto-remediation-cycle.test.ts. Two audit-mode cases calledrunAutoRemediationCycle({ mode: 'audit' }, ...)without injecting asoakSink, sobuildSoakCollectorfell through to the default durable singleton and persisted synthetic records into the operator's home-dir file:audit mode collects signals and runs the path→ wroterouting:cli-floor:codex:docsforwards every collected signal to the run→ wrote signalKeysaandb(the synthetic records the issue calls out)Confirmed against the live file: 105 records, 70 of them synthetic
a/b.The other six soak/shadow/readiness test files were already isolated — they either use in-memory sinks (
createRemediationShadowSink), inject an explicit temp-filecreateRemediationSoakSink(join(tmpdir(), ...)), or setNEXUS_DATA_DIRto amkdtempSyncdir + reset the singleton (auto-remediation-deps.test.ts,remediation-review-command.test.ts). The e2e test always injects a recording sink and uses throwaway git repos.How they were isolated
auto-remediation-cycle.test.tsnow pinsNEXUS_DATA_DIRto amkdtempSynctemp dir for the whole suite (beforeEach/afterEach), with_resetRemediationSoakSinkForTests()on both ends andrmSynccleanup. This protects every case in the file — including future ones — regardless of whether they inject a sink. The writer already honorsNEXUS_DATA_DIRvianexusDataPath('learning', ...)(learningis a cross-repo subdir; an explicitNEXUS_DATA_DIRoverrides it), so no production-path threading was needed.Regression guard
Added in the cycle test:
getRemediationSoakFile()resolves under the tempNEXUS_DATA_DIRand not under~/.nexus-agents— so a future test can't silently write to production.Sanity filter (added — defense-in-depth, the issue's preferred option)
readRemediationSoakSummary()now drops structurally-implausible records before summarizing, via the newisPlausibleSoakSignalKey/filterPlausibleSoakRecords. Real keys always carry thecategory:detailshape (routing:cli-floor:codex:docs,tech-debt:fitness-below-floor, …); the synthetic leaks were bare tokens (a,b,x). The check is conservative — it requires only the colon-delimited shape, not a known category enum — so no legitimate record (including future categories) is dropped. This means the readinessvolumecriterion can no longer be inflated by junk even if some leaked into the file historically.Home-dir file
Per the issue, I did not clean the owner's real
~/.nexus-agents/learning/remediation-soak.jsonl— that runtime home-dir state is out of repo scope and the one-time cleanup is the owner's local action. Verified the file (105 lines, mtime 2026-06-17) was not touched by the test run after this change.Gates
tsc --noEmitclean; eslint clean on the 3 changed files; all 7 soak/shadow/readiness suites green (61 tests)..changeset/soak-test-isolation-3932.md('nexus-agents': patch,fix). Zeroany; no new MCP tool / CLI command; new exports consumed in-src(readRemediationSoakSummary→filterPlausibleSoakRecords→isPlausibleSoakSignalKey).🤖 Generated with Claude Code