Skip to content

fix(remediation): isolate soak/shadow tests from the production data dir (#3932)#3947

Merged
williamzujkowski merged 1 commit into
mainfrom
fix/3932-soak-test-isolation
Jun 18, 2026
Merged

fix(remediation): isolate soak/shadow tests from the production data dir (#3932)#3947
williamzujkowski merged 1 commit into
mainfrom
fix/3932-soak-test-isolation

Conversation

@williamzujkowski

Copy link
Copy Markdown
Collaborator

Closes #3932.

Problem

The auto-remediation enforce-readiness gate reads the durable soak sink ~/.nexus-agents/learning/remediation-soak.jsonl (via summarizeRemediationShadowsoak.totalshadowSelections). The cycle's default soak sink (getRemediationSoakSink()) resolves under NEXUS_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 called runAutoRemediationCycle({ mode: 'audit' }, ...) without injecting a soakSink, so buildSoakCollector fell 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 → wrote routing:cli-floor:codex:docs
  • forwards every collected signal to the run → wrote signalKeys a and b (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-file createRemediationSoakSink(join(tmpdir(), ...)), or set NEXUS_DATA_DIR to a mkdtempSync dir + 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.ts now pins NEXUS_DATA_DIR to a mkdtempSync temp dir for the whole suite (beforeEach/afterEach), with _resetRemediationSoakSinkForTests() on both ends and rmSync cleanup. This protects every case in the file — including future ones — regardless of whether they inject a sink. The writer already honors NEXUS_DATA_DIR via nexusDataPath('learning', ...) (learning is a cross-repo subdir; an explicit NEXUS_DATA_DIR overrides it), so no production-path threading was needed.

Regression guard

Added in the cycle test:

  • getRemediationSoakFile() resolves under the temp NEXUS_DATA_DIR and not under ~/.nexus-agents — so a future test can't silently write to production.
  • An audit cycle with no injected soakSink writes only under the temp dir and leaves the home-dir file's existence unchanged.

Sanity filter (added — defense-in-depth, the issue's preferred option)

readRemediationSoakSummary() now drops structurally-implausible records before summarizing, via the new isPlausibleSoakSignalKey / filterPlausibleSoakRecords. Real keys always carry the category:detail shape (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 readiness volume criterion 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 --noEmit clean; eslint clean on the 3 changed files; all 7 soak/shadow/readiness suites green (61 tests).
  • Changeset .changeset/soak-test-isolation-3932.md ('nexus-agents': patch, fix). Zero any; no new MCP tool / CLI command; new exports consumed in-src (readRemediationSoakSummaryfilterPlausibleSoakRecordsisPlausibleSoakSignalKey).

🤖 Generated with Claude Code

…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>
@williamzujkowski williamzujkowski merged commit 943e21f into main Jun 18, 2026
43 checks passed
@williamzujkowski williamzujkowski deleted the fix/3932-soak-test-isolation branch June 18, 2026 04:39
@github-project-automation github-project-automation Bot moved this from Backlog to Done in nexus-agents project Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Test runs pollute the production remediation-soak file → inflates enforce-readiness 'volume' with synthetic records

1 participant