# Track Specification: Tier 2 Sandbox - Move State/Failures Off AppData **Track ID:** `tier2_no_appdata_20260618` **Date:** 2026-06-18 **Priority:** A (the in-flight Tier 2 run for `live_gui_test_fixes_20260618` is blocked by the AppData path assumption; a future Tier 2 clone will inherit the broken config unless this ships) **Type:** fix (convention + infrastructure; no behavior change in product code) ## Overview The Tier 2 autonomous sandbox currently persists its failcount state to `C:\Users\Ed\AppData\Local\manual_slop\tier2\\state.json` and writes failure reports to `C:\Users\Ed\AppData\Local\manual_slop\tier2_failures\`. The OpenCode permission JSON allowlists both. The user has explicitly directed: **"NEVER USE APPDATA"** — meaning the whole `C:\Users\Ed\AppData\...` tree should be off-limits to the Tier 2 sandbox. This track moves both the state and the failure-report directories **inside the Tier 2 clone** (`C:\projects\manual_slop_tier2\`) and removes every AppData reference from the conventions, the agent prompt, the slash command, the OpenCode JSON fragment, the bootstrap scripts, the user guide, and the tests. After this track, `C:\Users\Ed\AppData\...` is never referenced by the Tier 2 sandbox in any form. ## Current State Audit (as of 2026-06-18, commit 02aed999) ### Already Implemented (DO NOT re-implement) - **Tier 2 sandbox enforcement (3-layer):** OpenCode `permission.bash` deny rules + Windows restricted token + git hooks. Shipped in `tier2_autonomous_sandbox_20260616` (commit `00c6922c`). - **`*AppData\Local\Temp\*` deny rule:** already blocks the global Temp dir (the 2026-06-17 regression fix). The bash deny keys are present in both the top-level and the `tier2-autonomous` agent's `permission.bash`. - **`scripts/audit_no_temp_writes.py`:** scans `./scripts/**` for any `%TEMP%` / `tempfile.` / `$env:TEMP` usage. Default-on regression test `tests/test_no_temp_writes.py` invokes it with `--strict`. - **TIER2_STATE_DIR / TIER2_FAILURES_DIR env-var overrides:** `scripts/tier2/failcount.py` and `scripts/tier2/write_report.py` already accept env-var overrides; the AppData paths are just the *defaults*. ### Gaps to Fill (This Track's Scope) The AppData paths are still the **defaults** for failcount state and failure reports, and the conventions/permissions/tests all reinforce them: 1. **`scripts/tier2/failcount.py:117-123`** — `_state_dir(track_name)` defaults to `r"C:\Users\Ed\AppData\Local\manual_slop\tier2"` when `TIER2_STATE_DIR` is unset. 2. **`scripts/tier2/write_report.py:20-23`** — `_failures_dir()` defaults to `r"C:\Users\Ed\AppData\Local\manual_slop\tier2_failures"` when `TIER2_FAILURES_DIR` is unset. 3. **`conductor/tier2/opencode.json.fragment`** — `permission.read` and `permission.write` allowlist `C:\Users\Ed\AppData\Local\manual_slop\tier2\**` and `C:\Users\Ed\AppData\Local\manual_slop\tier2_failures\**` at both the top level and the `tier2-autonomous` agent level. These allow rules *keep the door open* — even if the agent is told not to use AppData, the permission system *would* allow it. 4. **`conductor/tier2/agents/tier2-autonomous.md`** — explicitly tells the agent "Use `C:\Users\Ed\AppData\Local\manual_slop\tier2\` for all scratch / audit-output / temp files." (Line 47) 5. **`conductor/tier2/commands/tier-2-auto-execute.md`** — same instruction at line 46. 6. **`scripts/tier2/setup_tier2_clone.ps1:122-133`** — creates `C:\Users\Ed\AppData\Local\manual_slop\tier2\` and `C:\Users\Ed\AppData\Local\manual_slop\tier2_failures\` with restricted ACLs on bootstrap. 7. **`scripts/tier2/run_tier2_sandboxed.ps1:20-21,77`** — references the AppData dirs and sets ACLs on them. 8. **`docs/guide_tier2_autonomous.md`** — 4 explicit AppData references (lines 24, 72, 119, 128). 9. **`conductor/workflow.md:386`** — hard bans table says "File access outside Tier 2 clone + app-data dir." 10. **`scripts/tier2/write_track_completion_report.py:262,264`** — writes the AppData paths into the generated completion report. 11. **`tests/test_tier2_slash_command_spec.py:91`** — asserts `'AppData\\Local\\manual_slop\\tier2' in content` (the test *requires* the agent prompt to reference AppData; this is the regression we are now reversing). 12. **`tests/test_no_temp_writes.py:33`** — the failure-message string still suggests `C:\Users\Ed\AppData\Local\manual_slop\tier2\` as the fix target. ### Root Cause The `tier2_autonomous_sandbox_20260616` track (shipped 2026-06-16) chose AppData because (a) it's outside the project tree so it doesn't pollute git, and (b) Windows restricted tokens can have explicit ACLs applied to AppData subdirs while keeping the rest of the user profile accessible. The trade-off was never questioned because Tier 2 was working. On 2026-06-17, the agent attempted to write an audit JSON to `C:\Users\Ed\AppData\Local\Temp\` (the wrong AppData path — the system Temp, not the manual_slop one). The OpenCode permission system denied it because `*AppData\Local\Temp\*` was in the bash deny list, but the agent was confused because the *prompt* said "use AppData" and the *allowlist* said "AppData/Local/manual_slop/tier2/ is OK." The 2026-06-17 fix added the Temp deny rule and the AppData instruction to the prompt — but the underlying assumption (AppData is fine) was still baked in. On 2026-06-18, the user issued the directive: **"NEVER USE APPDATA."** This is a stronger rule than the 2026-06-17 fix. The Tier 2 sandbox must stop treating AppData as a scratch space, period. ## Goals 1. **Zero AppData references in Tier 2 conventions.** The agent prompt, slash command, user guide, and OpenCode JSON must never say "use C:\Users\Ed\AppData\..." for any purpose. 2. **Default state location = inside the clone.** `scripts/tier2/state//state.json` (relative to the clone root, computed via `Path.cwd()` when the agent runs). 3. **Default failure-report location = inside the clone.** `scripts/tier2/failures/_.md` and `scripts/tier2/failures/.STOPPED`. 4. **Permission system refuses AppData.** OpenCode JSON `read`/`write` must not allowlist any `C:\Users\Ed\AppData\...` path. The deny rule for `*AppData\Local\Temp\*` stays; we add `*AppData\*` deny rules as a belt-and-suspenders. 5. **Bootstrap does not create AppData dirs.** `setup_tier2_clone.ps1` and `run_tier2_sandboxed.ps1` no longer reference AppData. 6. **Tests assert the new behavior.** `tests/test_tier2_slash_command_spec.py` and `tests/test_no_temp_writes.py` are updated to assert no AppData references in the agent prompt / fix messages. 7. **Backward-compatible env-var escape hatch.** The existing `TIER2_STATE_DIR` / `TIER2_FAILURES_DIR` env-var overrides are preserved (still honored if set), but the *default* moves inside the clone. ## Functional Requirements **FR1. State location moves inside the clone.** - `scripts/tier2/failcount.py:_state_dir` returns `Path.cwd() / "scripts" / "tier2" / "state" / track_name` by default. - `TIER2_STATE_DIR` env-var override is preserved. - `run_track.py:run_init` does `os.chdir(repo_path)` before calling `save_state` so `Path.cwd()` resolves to the clone root. **FR2. Failure-report location moves inside the clone.** - `scripts/tier2/write_report.py:_failures_dir` returns `Path.cwd() / "scripts" / "tier2" / "failures"` by default. - `TIER2_FAILURES_DIR` env-var override is preserved. - `run_track.py:run_report` does `os.chdir(repo_path)` before calling `write_failure_report`. **FR3. OpenCode permission JSON removes AppData allow rules.** - `conductor/tier2/opencode.json.fragment`: top-level and `tier2-autonomous` agent — `read`/`write` allow rules for `C:\Users\Ed\AppData\Local\manual_slop\tier2\**` and `C:\Users\Ed\AppData\Local\manual_slop\tier2_failures\**` are removed. - The existing `*AppData\Local\Temp\*` bash deny rule stays. - A new `*AppData\*` bash deny rule is added (belt-and-suspenders — the OpenCode `*` deny already blocks AppData reads, but a shell command like `> C:\Users\Ed\AppData\Local\foo.txt` was previously allowed because the bash `*` was set to `allow` at the agent level; tightening to `*` deny is too restrictive, so the targeted deny on `*AppData\*` is the surgical fix). **FR4. Agent prompt and slash command say "NEVER USE APPDATA".** - `conductor/tier2/agents/tier2-autonomous.md` "Temp files" convention replaced with: "All scratch, state, and audit-output files MUST live inside the Tier 2 clone (`scripts/tier2/state/`, `scripts/tier2/failures/`, `scripts/tier2/artifacts//`). The `C:\Users\Ed\AppData\...` tree is OFF-LIMITS for any read, write, or shell command. This is enforced by the OpenCode `*AppData\*` deny rule; a violation will halt the run." - `conductor/tier2/commands/tier-2-auto-execute.md` "Conventions" section: same update. **FR5. Bootstrap scripts stop creating AppData dirs.** - `scripts/tier2/setup_tier2_clone.ps1`: remove `$AppDataDir` / `$AppDataFailuresDir` variables and the `New-Item` / `Set-Acl` calls. - `scripts/tier2/run_tier2_sandboxed.ps1`: same. **FR6. Tests updated.** - `tests/test_tier2_slash_command_spec.py:test_agent_denies_temp_writes` — flipped assertion: the agent prompt must NOT contain `AppData\Local\manual_slop\tier2` and MUST contain `scripts/tier2/state` or `scripts/tier2/failures`. - `tests/test_tier2_slash_command_spec.py:test_command_denies_temp_writes` — same flip (the slash command prompt has the same convention). - `tests/test_no_temp_writes.py` docstring + fix message: replace the AppData suggestion with `scripts/tier2/state/` / `scripts/tier2/failures/`. **FR7. User guide updated.** - `docs/guide_tier2_autonomous.md`: 4 AppData references replaced with the new inside-clone locations. The "Verify the sandbox" checklist's `` reference is removed. **FR8. Hard bans table updated.** - `conductor/workflow.md:386`: "File access outside Tier 2 clone + app-data dir" → "File access outside Tier 2 clone (AppData, Temp, Documents, etc. all denied)." **FR9. Completion report writer updated.** - `scripts/tier2/write_track_completion_report.py`: replace the 2 AppData path strings with the new `scripts/tier2/state/...` / `scripts/tier2/failures/...` paths. **FR10. .gitignore updated.** - `scripts/tier2/state/` and `scripts/tier2/failures/` added (track-isolated scratch, must not be committed). ## Non-Functional Requirements - **No regressions:** all existing failcount and report-writer tests pass after the path changes. The existing `TIER2_STATE_DIR` / `TIER2_FAILURES_DIR` env-var tests (`tests/test_failcount.py:176,190,198` and `tests/test_tier2_report_writer.py:25,33,40,71`) continue to pass — they monkeypatch the env var, which overrides the default. - **CLI ergonomics:** `scripts/tier2/run_track.py` continues to take `--repo-path` (default `.`). The `os.chdir(repo_path)` call is silent and idempotent. - **The in-flight Tier 2 run is NOT broken by this change** — the Tier 2 clone at `C:\projects\manual_slop_tier2\` still has the old config until re-bootstrapped. The user's existing run for `live_gui_test_fixes_20260618` continues to use AppData as it was bootstrapped. ## Architecture Reference - **`docs/guide_tier2_autonomous.md`** — the user-facing Tier 2 sandbox guide. Sections 1 (bootstrap), 5 (the 4 hard bans), 7 (the failure report), and Troubleshooting are all touched. - **`conductor/workflow.md` §"Tier 2 Autonomous Sandbox" (lines 365-396)** — the convention-level rules and the 3-layer enforcement table. The "Hard bans" row is updated. - **`conductor/code_styleguides/workspace_paths.md`** — the principle "test workspaces live in the project tree under `tests/artifacts/`" extends naturally to "Tier 2 scratch lives in the project tree under `scripts/tier2/state/` and `scripts/tier2/failures/`." We cite this principle in the spec; we don't modify the styleguide (it's about *test* workspaces, not Tier 2 scratch). ## Out of Scope - Re-bootstrap of the live Tier 2 clone (`C:\projects\manual_slop_tier2\`). The user re-runs `pwsh -File scripts/tier2/setup_tier2_clone.ps1` after this track merges. - Migration of existing state from `C:\Users\Ed\AppData\Local\manual_slop\tier2\...` into `scripts/tier2/state/...`. Any in-flight run's state is discarded on the next re-bootstrap. - Repo-wide LF normalization (a separate future track). - Tier 2 audit script (`scripts/audit_no_temp_writes.py`) changes — it already correctly scans for `%TEMP%` patterns; the AppData path strings in its docstring are updated as part of FR6 (the test fix-message change).