# Track Plan: Tier 2 Sandbox - Move State/Failures Off AppData **Goal:** move failcount state and failure-report locations inside the Tier 2 clone; remove all AppData references from Tier 2 conventions, permissions, scripts, docs, and tests. **Scope:** 11 source files + 3 test files + 1 doc + 1 workflow.md section + 1 .gitignore. **Convention:** 1-space Python indentation. CRLF where the file is already CRLF (do not normalize). ## Phase 1: Move the default state and failure-report paths Focus: change the Python defaults so load/save use `scripts/tier2/state/...` and `scripts/tier2/failures/...` when no env-var override is set. ### Task 1.1: Update `scripts/tier2/failcount.py:_state_dir` default - **WHERE:** `scripts/tier2/failcount.py:117-123` (the `_state_dir(track_name)` function). - **WHAT:** change the default `base` from `r"C:\Users\Ed\AppData\Local\manual_slop\tier2"` to `Path.cwd() / "scripts" / "tier2" / "state"` (computed when the function is called; `Path` import already present at line 11). - **HOW:** rewrite the function as: ```python def _state_dir(track_name: str) -> Path: base_str = os.environ.get("TIER2_STATE_DIR") if base_str: return Path(base_str) / track_name return Path.cwd() / "scripts" / "tier2" / "state" / track_name ``` - **SAFETY:** preserve the env-var escape hatch (`TIER2_STATE_DIR`); preserve the `Path` return type. The function has no other callers. - **COMMIT:** `fix(tier2): move failcount state default inside Tier 2 clone (scripts/tier2/state/)` ### Task 1.2: Update `scripts/tier2/write_report.py:_failures_dir` default - **WHERE:** `scripts/tier2/write_report.py:20-23` (the `_failures_dir()` function). - **WHAT:** change the default from `r"C:\Users\Ed\AppData\Local\manual_slop\tier2_failures"` to `Path.cwd() / "scripts" / "tier2" / "failures"`. - **HOW:** rewrite the function as: ```python def _failures_dir() -> Path: base_str = os.environ.get("TIER2_FAILURES_DIR") if base_str: return Path(base_str) return Path.cwd() / "scripts" / "tier2" / "failures" ``` - **SAFETY:** preserve `TIER2_FAILURES_DIR` env-var override; preserve the `Path` return type. Callers are `compute_report_path`, `compute_stopped_flag_path`, and `write_failure_report` (all in the same file). - **COMMIT:** `fix(tier2): move failure-report default inside Tier 2 clone (scripts/tier2/failures/)` ### Task 1.3: `scripts/tier2/run_track.py` chdir before state calls - **WHERE:** `scripts/tier2/run_track.py:run_init` (around line 78, before `save_state`) and `run_track.py:run_report` (around line 100, before `write_failure_report`). - **WHAT:** add `os.chdir(repo_path)` so `Path.cwd()` in `_state_dir` / `_failures_dir` resolves to the repo root. - **HOW:** add `import os` at the top (the file already imports `argparse`, `subprocess`, `sys`, `datetime`, `pathlib`); add `os.chdir(repo_path)` as the first line of `run_init` and `run_report`. - **SAFETY:** `os.chdir` is process-global; this is acceptable because `run_track.py` is the CLI entry point, not a library. The chdir is idempotent within a single invocation. - **COMMIT:** `fix(tier2): chdir to repo_path in run_track before state/report calls` ### Task 1.4: Add `scripts/tier2/state/` and `scripts/tier2/failures/` to .gitignore - **WHERE:** `.gitignore` (top-level). Currently excludes `scripts/generated` on line 11. - **WHAT:** add `scripts/tier2/state/` and `scripts/tier2/failures/` after the `scripts/generated` line. - **HOW:** edit the file in place. - **SAFETY:** these are track-isolated scratch dirs; committing them would pollute the tree. - **COMMIT:** `chore(tier2): gitignore scripts/tier2/state/ and scripts/tier2/failures/` ## Phase 2: Update OpenCode permissions and agent/command prompts Focus: remove AppData allow rules from the OpenCode JSON fragment; update the agent prompt and slash command to say "NEVER USE APPDATA". ### Task 2.1: `conductor/tier2/opencode.json.fragment` — remove AppData allow rules - **WHERE:** lines 10-11, 16-17, 62-63, 68-69 (the `permission.read` and `permission.write` blocks at top level and at the `tier2-autonomous` agent level). - **WHAT:** delete the two `C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2\\**` and `C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2_failures\\**` allow rules. The remaining allow rule (the Tier 2 clone path) is unchanged. - **HOW:** four targeted `edit_file` calls (one per `read`/`write` block × top-level/agent). - **SAFETY:** keep the existing `*AppData\\Local\\Temp\\*` bash deny rule. **Do NOT** modify the bash rules in this task — that's Task 2.2. - **COMMIT:** `fix(tier2): remove AppData allow rules from OpenCode permission JSON` ### Task 2.2: `conductor/tier2/opencode.json.fragment` — add `*AppData\\*` bash deny - **WHERE:** the `permission.bash` block at top level (line 46) and at the `tier2-autonomous` agent level (line 73). - **WHAT:** add `"*AppData\\*": "deny"` after the existing `"*AppData\\Local\\Temp\\*": "deny"` rule. The broader pattern catches `Local`, `LocalLow`, `Roaming`, and any other subdir. - **HOW:** two targeted edits. - **SAFETY:** the rule denies any bash command containing `AppData\`. Legitimate Tier 2 work does not write there. Combined with Task 2.1 (no allow rules), this is belt-and-suspenders. - **COMMIT:** `fix(tier2): add *AppData\\* bash deny rule (broader than just Temp)` ### Task 2.3: `conductor/tier2/agents/tier2-autonomous.md` — replace AppData convention - **WHERE:** line 47 (the "Temp files" bullet under "Conventions (MUST follow - added 2026-06-17)"). - **WHAT:** replace the entire bullet. The new bullet says: "All scratch, state, audit-output, and intermediate files MUST live inside the Tier 2 clone (the OpenCode `*` deny rule blocks everything else). Default locations: `scripts/tier2/state//state.json` for failcount state, `scripts/tier2/failures/` for failure reports, `scripts/tier2/artifacts//` for throwaway scripts. **The `C:\Users\Ed\AppData\...` tree is OFF-LIMITS** for any read, write, or shell command. The OpenCode `*AppData\\*` bash deny rule enforces this." - **HOW:** edit_file on the bullet's full text. - **SAFETY:** preserve the env-var escape-hatch language (TIER2_STATE_DIR / TIER2_FAILURES_DIR are honored if set). - **COMMIT:** `docs(tier2): agent prompt - replace AppData convention with inside-clone convention` ### Task 2.4: `conductor/tier2/commands/tier-2-auto-execute.md` — replace AppData convention - **WHERE:** line 46 (the "Temp files" bullet under "Conventions (MUST follow - added 2026-06-17)"). - **WHAT:** identical change to Task 2.3, applied to the slash command prompt. Also update line 19 ("Check for a previous run" — the path is `/tier2//state.json`) and line 25 (step 3 in Protocol — "Initialize failcount state at `/tier2//state.json`") to reference `scripts/tier2/state//state.json`. - **HOW:** three edit_file calls. - **SAFETY:** the slash command prompt is what the Tier 2 agent reads; if it still says ``, the agent will continue trying to use AppData. - **COMMIT:** `docs(tier2): slash command - replace AppData paths with inside-clone paths` ## Phase 3: Update bootstrap scripts Focus: `setup_tier2_clone.ps1` and `run_tier2_sandboxed.ps1` stop creating/referencing AppData dirs. ### Task 3.1: `scripts/tier2/setup_tier2_clone.ps1` — remove AppData dir creation - **WHERE:** lines 23 (`$AppDataDir`), 30 (`$AppDataFailuresDir`), 122-133 (the `New-Item` / `Get-Acl` / `Set-Acl` block). - **WHAT:** delete the `$AppDataDir` and `$AppDataFailuresDir` parameter / variable declarations and the entire "Create app-data dir with restricted ACLs" step block. Update the docstring (lines 6-9) to remove the "creates the app-data temp dir with restricted ACLs" sentence. - **HOW:** three edit_file calls. - **SAFETY:** the script must still create the Tier 2 clone, copy templates, install git hooks, and create the desktop shortcut. The deleted step is purely about AppData dirs. - **COMMIT:** `fix(tier2): setup_tier2_clone.ps1 - stop creating AppData dirs` ### Task 3.2: `scripts/tier2/run_tier2_sandboxed.ps1` — remove AppData dir references - **WHERE:** lines 20-21 (`$AppDataDir`, `$AppDataFailuresDir`), line 7 (docstring), line 77 (the "Set explicit ACLs on the Tier 2 clone + app-data dir" comment). - **WHAT:** delete the `$AppDataDir` / `$AppDataFailuresDir` variable declarations and any ACL-set logic that references them. Update the docstring (line 7) to remove "app-data dir" from the list. - **HOW:** four edit_file calls. - **SAFETY:** the restricted-token + Job-Object + launch logic must stay intact. - **COMMIT:** `fix(tier2): run_tier2_sandboxed.ps1 - remove AppData dir references` ## Phase 4: Update tests Focus: flip the slash-command-spec tests so they assert "no AppData refs" instead of "AppData refs required"; update `test_no_temp_writes.py` docstring and fix-message. ### Task 4.1: `tests/test_tier2_slash_command_spec.py:test_agent_denies_temp_writes` - **WHERE:** lines 82-91 (the entire `test_agent_denies_temp_writes` function). - **WHAT:** flip the assertions. Replace: ```python assert 'AppData\\Local\\Temp' in content, "agent prompt must include Temp deny rule in frontmatter bash" assert 'AppData\\Local\\manual_slop\\tier2' in content or 'app-data' in content.lower(), "agent prompt must point agent at the app-data dir for temp files" ``` with: ```python assert 'AppData\\Local\\Temp' in content, "agent prompt must include Temp deny rule in frontmatter bash" assert "*AppData\\\\*" in content or "AppData\\\\*" in content, "agent prompt must include the broader AppData deny rule" assert "scripts/tier2/state" in content, "agent prompt must point agent at scripts/tier2/state for failcount state" assert "scripts/tier2/failures" in content, "agent prompt must point agent at scripts/tier2/failures for failure reports" assert "AppData\\Local\\manual_slop\\tier2" not in content, "agent prompt must NOT reference the AppData tier2 dir (2026-06-18 hard ban)" ``` Update the docstring to mention the 2026-06-18 reversal. - **HOW:** edit_file on the function body and docstring. - **SAFETY:** the `*AppData\\*` substring check matches the literal JSON bash key `"*AppData\\*"`. Be careful with Python string-escape semantics — use a raw string or a literal substring that survives the JSON double-escape. - **COMMIT:** `test(tier2): slash_command_spec - assert no AppData refs, point at inside-clone` ### Task 4.2: `tests/test_tier2_slash_command_spec.py:test_command_denies_temp_writes` (or the equivalent for the command file) - **WHERE:** the parallel test for the slash command prompt (likely also in `tests/test_tier2_slash_command_spec.py`). - **WHAT:** apply the same flip as Task 4.1 to the command prompt content. - **HOW:** edit_file. - **SAFETY:** keep the Temp deny assertion; add the new inside-clone-pointing assertions; remove the AppData-required assertion. - **COMMIT:** `test(tier2): slash_command_spec - command prompt assert no AppData refs` ### Task 4.3: `tests/test_no_temp_writes.py` docstring + fix message - **WHERE:** lines 1-15 (the docstring) and line 33 (the fix-message string). - **WHAT:** replace the AppData paths in the docstring (lines 6-7) with `scripts/tier2/state/` and `scripts/tier2/failures/`. Replace the fix-message suggestion on line 33 (`C:\\Users\\Ed\\AppData\\Local\\manual_slop\\tier2\\ instead of %TEMP%.`) with `scripts/tier2/state/ or scripts/tier2/failures/ instead of %TEMP%.`. - **HOW:** edit_file. - **SAFETY:** the audit script's behavior is unchanged; only the human-facing strings change. - **COMMIT:** `test(tier2): no_temp_writes - replace AppData refs in docstring + fix message` ## Phase 5: Update user-facing docs and workflow Focus: `docs/guide_tier2_autonomous.md` and `conductor/workflow.md` stop referencing AppData. ### Task 5.1: `docs/guide_tier2_autonomous.md` — replace AppData refs - **WHERE:** line 24 (bootstrap step 5), line 59 (the "4 hard bans" table row), line 72 (failure report location), lines 119-129 (Troubleshooting section). - **WHAT:** replace each `C:\Users\Ed\AppData\Local\manual_slop\tier2...` reference with the new `scripts/tier2/state/...` / `scripts/tier2/failures/...` paths. - **HOW:** multiple edit_file calls (one per paragraph that contains an AppData path). - **SAFETY:** the guide's structure and other content stay intact; only path strings change. - **COMMIT:** `docs(tier2): guide_tier2_autonomous - replace AppData paths with inside-clone paths` ### Task 5.2: `conductor/workflow.md` — update hard bans table - **WHERE:** line 386 (the row "File access outside Tier 2 clone + app-data dir"). - **WHAT:** replace with "File access outside Tier 2 clone (AppData, Temp, Documents, etc. all denied at the OpenCode `*` level + targeted `*AppData\\*` deny)." - **HOW:** edit_file. - **SAFETY:** the surrounding 3-layer-enforcement table structure stays. - **COMMIT:** `docs(tier2): workflow.md hard bans - AppData denied (no exception)` ### Task 5.3: `scripts/tier2/write_track_completion_report.py` — update report output - **WHERE:** lines 262, 264 (the "Filesystem boundary" and "Failcount monitored" rows in the generated report). - **WHAT:** replace the AppData path strings with `scripts/tier2/state/...` / `scripts/tier2/failures/...`. - **HOW:** two edit_file calls. - **SAFETY:** the generated report's structure stays; only path strings change. The report's downstream consumers (the user reading it after a Tier 2 run) need to see the actual paths the next run will use. - **COMMIT:** `fix(tier2): write_track_completion_report - use inside-clone paths in output` ## Phase 6: Conductor verification Focus: ensure the test suite still passes after the changes; register the track in `conductor/tracks.md`. ### Task 6.1: Run targeted test batches - **COMMAND:** `uv run python scripts/run_tests_batched.py --tier tier-1-unit-core tests/test_failcount.py tests/test_tier2_report_writer.py tests/test_tier2_slash_command_spec.py tests/test_no_temp_writes.py` - **EXPECTED:** all 4 test files pass. The `test_failcount` and `test_tier2_report_writer` env-var tests pass because they monkeypatch the env var (FR7's backward-compat requirement). The `test_tier2_slash_command_spec` tests pass because the new assertions match the updated agent prompt and slash command. The `test_no_temp_writes` test passes because the audit script's behavior didn't change. - **COMMIT:** no commit (this is a verification step). ### Task 6.2: Run the static analyzer batch - **COMMAND:** `uv run python scripts/audit_no_temp_writes.py --strict` - **EXPECTED:** `CLEAN: no script under ./scripts/ emits to %TEMP%` and exit code 0. The audit's exclusion list (`scripts/tier2/artifacts`) covers the throwaway scripts that may still have AppData path strings. - **COMMIT:** no commit. ### Task 6.3: Register the track in `conductor/tracks.md` - **WHERE:** append a new entry block following the precedent set by `tier2_autonomous_sandbox_20260616`. - **WHAT:** add the link, spec, plan, metadata, status, and a one-line summary. - **COMMIT:** `conductor(tracks): register tier2_no_appdata_20260618 (shipped)` (after Phase 1-5 commit SHAs are recorded). --- ## End-of-Track Report (added 2026-06-17 convention) On Phase 6 completion, write `docs/reports/TRACK_COMPLETION_tier2_no_appdata_20260618.md` following the precedent set by `docs/reports/TRACK_COMPLETION_tier2_autonomous_sandbox_20260616.md`. Update `conductor/tracks/tier2_no_appdata_20260618/state.toml` to `status = "completed"`.