{
  "track_id": "test_sandbox_hardening_20260619",
  "name": "Test Sandbox Hardening",
  "created": "2026-06-19",
  "status": "spec_written",
  "blocked_by": [],
  "blocks": [],
  "priority": "A",
  "rationale": "User has lost important sample data multiple times over the past month because tests have written to top-level TOML files (manual_slop.toml, manual_slop_history.toml, personas.toml, presets.toml, tool_presets.toml, credentials.toml) at the project root. This track adds a 4-layer enforcement stack to make such writes impossible at the Python layer (default) and at the OS layer (opt-in).",
  "scope": {
    "new_files": [
      "scripts/audit_test_sandbox_violations.py",
      "scripts/run_tests_sandboxed.ps1",
      "tests/test_test_sandbox.py",
      "conductor/code_styleguides/test_sandbox.md"
    ],
    "modified_files": [
      "src/paths.py",
      "src/models.py",
      "sloppy.py",
      "tests/conftest.py",
      "pyproject.toml",
      "conductor/tech-stack.md",
      "conductor/code_styleguides/workspace_paths.md",
      "docs/guide_testing.md",
      ".gitignore"
    ],
    "deleted_files": []
  },
  "estimated_effort": {
    "method": "scope (per workflow.md Tier 1 Track Initialization Rules). NO day estimates.",
    "phase_1": "1 task: baseline pass-rate capture + verification that isolate_workspace + check_test_toml_paths work as documented",
    "phase_2": "1 audit script + 4 regression tests + 1 commit",
    "phase_3": "1 conftest fixture (Layer 1 audit hook) + 4 guard-specific regression tests + 1 commit",
    "phase_4": "isolate_workspace migration + pyproject.toml addopts + tech-stack.md note + 1 commit",
    "phase_5": "1 PowerShell wrapper (Layer 3) + 1 smoke test + 1 commit",
    "phase_6": "2 doc files updated or 1 new styleguide + 1 commit",
    "phase_7": "11-tier verification run + 1 commit (report)",
    "phase_8": "1 end-of-track report + 1 commit",
    "summary": "8 phases, ~8 commits, ~10-12 source files touched across scripts/, tests/, pyproject.toml, docs/, conductor/"
  },
  "verification_criteria": [
    "tests/test_test_sandbox.py exists and all 13 tests pass",
    "scripts/audit_test_sandbox_violations.py runs in both default and --strict modes",
    "pyproject.toml contains addopts = '--basetemp=tests/artifacts/_pytest_tmp' under [tool.pytest.ini_options]",
    "tests/conftest.py isolate_workspace no longer calls tmp_path_factory.mktemp (per workspace_paths.md); all env-var redirects point to paths inside ./tests/artifacts/",
    "src/paths.py:get_config_path() does NOT call os.environ.get('SLOP_CONFIG', ...); uses set_config_override() instead",
    "src/paths.py:set_config_override(path) exists and is callable from sloppy.py and conftest.py",
    "sloppy.py accepts --config argparse argument and calls paths.set_config_override() before importing src/",
    "tests/conftest.py parses sys.argv for --config at module body (BEFORE any src/ import); auto-defaults to tests/artifacts/_isolation_workspace_<RUN_ID>/config_overrides.toml",
    "tests/artifacts/_isolation_workspace_<RUN_ID>/config_overrides.toml is auto-generated on every pytest run",
    "conductor/code_styleguides/test_sandbox.md exists and documents the --config CLI flag + config_overrides.toml convention",
    "scripts/run_tests_sandboxed.ps1 exists, parses cleanly, and on Windows can be invoked (-WhatIf mode for dry-run)",
    "conductor/tech-stack.md has a dated note explaining the --basetemp choice",
    "conductor/code_styleguides/workspace_paths.md or new test_sandbox.md documents the 3-layer model",
    "Full test suite (11 tiers) runs to completion with no regression vs. pre-track baseline (1288 passed + 4 xdist-skipped per result_migration_small_files_20260617)",
    "No new @pytest.mark.skip markers added (per conductor/workflow.md Skip-Marker Policy + user directive)",
    "End-of-track report at docs/reports/TRACK_COMPLETION_test_sandbox_hardening_20260619.md"
  ],
  "risk_register": [
    {
      "id": "R1",
      "title": "Layer 1 audit hook breaks a test that legitimately writes outside ./tests/",
      "likelihood": "medium",
      "scope_impact": "the implementation may be larger than the spec suggests if many tests need to be migrated to tmp_path",
      "mitigation": "allowlist includes pytest --basetemp; RuntimeError includes test name so offending test is obvious; add new paths to allowlist only via explicit allowlist update"
    },
    {
      "id": "R2",
      "title": "Layer 1 audit hook slows down the test suite",
      "likelihood": "low",
      "scope_impact": "minimal",
      "mitigation": "sys.addaudithook is a thin C-level callback; overhead measured in <2% per Python docs"
    },
    {
      "id": "R3",
      "title": "Layer 4 audit flags a currently-passing test as a false positive",
      "likelihood": "medium",
      "scope_impact": "the implementation may be larger than the spec suggests if many tests need cleanup",
      "mitigation": "audit is INFORMATIONAL by default; --strict is opt-in for CI; fix offending test rather than suppress audit"
    },
    {
      "id": "R4",
      "title": "Layer 3 PowerShell wrapper breaks on a Windows version without the required privileges",
      "likelihood": "low",
      "scope_impact": "minimal",
      "mitigation": "wrapper is opt-in; default invocation stays uv run pytest; wrapper docs explain privilege requirements"
    },
    {
      "id": "R5",
      "title": "Existing tests that don't go through isolate_workspace still read real config files",
      "likelihood": "high",
      "scope_impact": "known gap, out of scope",
      "mitigation": "Reads are out of scope per the Out of Scope section; Layer 1 still blocks writes which is the user's primary concern"
    },
    {
      "id": "R7",
      "title": "Removing SLOP_CONFIG env var fallback breaks code paths that relied on it",
      "likelihood": "medium",
      "scope_impact": "the implementation may be larger than the spec suggests if many call sites need updating",
      "mitigation": "conftest.py auto-defaults to config_overrides.toml inside the test workspace; sloppy.py auto-defaults to root_dir/config.toml; the change should be transparent for any code that goes through get_config_path()"
    },
    {
      "id": "R8",
      "title": "conftest.py sys.argv parse at module body races with pytest's own argparse",
      "likelihood": "low",
      "scope_impact": "minimal",
      "mitigation": "pytest_addoption registers --config so pytest doesn't warn about unknown flag; sys.argv parse at module body is a known-safe pattern (per conductor/tracks/test_infrastructure_hardening_20260609 conftest patterns)"
    },
    {
      "id": "R6",
      "title": "pytest_configure setting _tmp_path_factory._basetemp uses a private API that changes between versions",
      "likelihood": "medium",
      "scope_impact": "minimal",
      "mitigation": "the --basetemp addopts is the primary mechanism; the _basetemp assignment is defensive only; if it breaks, addopts still works"
    }
  ],
  "architecture_reference": {
    "primary_styleguide": "conductor/code_styleguides/workspace_paths.md",
    "secondary_styleguides": [
      "conductor/code_styleguides/feature_flags.md",
      "conductor/code_styleguides/data_oriented_design.md"
    ],
    "related_tracks": [
      "conductor/archive/workspace_path_finalize_20260609/",
      "conductor/tracks/tier2_autonomous_sandbox_20260616/",
      "Test Consolidation & TOML Sandboxing (per conductor/tracks.md:395)"
    ],
    "pattern_references": [
      "scripts/audit_no_temp_writes.py (audit script pattern)",
      "scripts/tier2/run_tier2_sandboxed.ps1 (PowerShell wrapper pattern)",
      "scripts/check_test_toml_paths.py (existing static audit)"
    ]
  },
  "deferred_to_followup_tracks": [
    {
      "title": "Eliminate the remaining SLOP_* env vars (presets, credentials, etc.)",
      "description": "This track only eliminates SLOP_CONFIG. The other 7 SLOP_* env vars (SLOP_GLOBAL_PRESETS, SLOP_GLOBAL_TOOL_PRESETS, SLOP_GLOBAL_PERSONAS, SLOP_GLOBAL_WORKSPACE_PROFILES, SLOP_CREDENTIALS, SLOP_MCP_ENV, SLOP_LOGS_DIR, SLOP_SCRIPTS_DIR) remain env-var-driven. Per user directive, this is the 'mess' to address in follow-up tracks. Same pattern: paths.set_<thing>_override() module-level + CLI flag at entry point.",
      "track_status": "not yet specced"
    },
    {
      "title": "Read-side isolation (block reads of real config from tests)",
      "description": "Layer 1 only blocks writes; reads of real credentials.toml / config.toml still happen for tests that don't go through isolate_workspace. Future track could block reads via a stricter allowlist.",
      "track_status": "not yet specced"
    },
    {
      "title": "macOS/Linux OS-level sandbox wrapper",
      "description": "Layer 3 is Windows-only (restricted token + Job Object). A run_tests_sandboxed.sh using bwrap/unshare would extend to macOS/Linux.",
      "track_status": "not yet specced"
    },
    {
      "title": "Per-fixture sandbox strictness tuning",
      "description": "The blanket autouse fixture is the v1. A future track could add @pytest.fixture(sandbox_strict=True) for tests that need full OS isolation vs. the default Python guard.",
      "track_status": "not yet specced"
    }
  ],
  "regressions_and_pre_existing_failures": [],
  "pre_existing_failures_remaining": [],
  "user_directives": [
    "Hard sandbox for tests, similar to Tier 2 - completely banned from accessing files outside ./tests/",
    "No new @pytest.mark.skip markers",
    "User has lost important sample data multiple times - this is the primary motivation",
    "NO ENV VARS for config path. Use --config CLI flag at the entry point (sloppy.py for production, conftest.py for tests)",
    "Test workspace file naming: config_overrides.toml (per user direction)",
    "Out of scope: converting the other SLOP_* env vars (presets, credentials, etc.) to CLI flags. User considers them a separate mess to address in follow-up tracks.",
    "Hard fail on any sandbox violation (no warnings, no soft fails)",
    "Tests should never need AppData temp (tempfile.mkdtemp/mkstemp without dir= is a flag)"
  ]
}
