conductor(track): tier2_no_appdata_20260618 spec/plan/metadata

The track directory was created at the start of the fix but the
spec.md, plan.md, and metadata.json were never committed. They are
committed now (the implementation has been done; this is the planning
artifact pair).

The plan is marked as executed via the per-file atomic commits that
landed during the fix; the state.toml is already set to status=completed.

Refs: conductor/tracks/tier2_no_appdata_20260618
This commit is contained in:
ed
2026-06-18 14:48:37 -04:00
parent 09df69daff
commit a0b0f6290b
3 changed files with 385 additions and 0 deletions
@@ -0,0 +1,79 @@
{
"id": "tier2_no_appdata_20260618",
"name": "Tier 2 Sandbox - Move State/Failures Off AppData",
"date": "2026-06-18",
"type": "fix",
"priority": "A",
"spec": "conductor/tracks/tier2_no_appdata_20260618/spec.md",
"plan": "conductor/tracks/tier2_no_appdata_20260618/plan.md",
"status": "active",
"blocked_by": {},
"blocks": {},
"scope": {
"new_files": [],
"modified_files": [
"scripts/tier2/failcount.py",
"scripts/tier2/write_report.py",
"scripts/tier2/run_track.py",
"scripts/tier2/setup_tier2_clone.ps1",
"scripts/tier2/run_tier2_sandboxed.ps1",
"scripts/tier2/write_track_completion_report.py",
"conductor/tier2/opencode.json.fragment",
"conductor/tier2/agents/tier2-autonomous.md",
"conductor/tier2/commands/tier-2-auto-execute.md",
"docs/guide_tier2_autonomous.md",
"conductor/workflow.md",
".gitignore",
"tests/test_tier2_slash_command_spec.py",
"tests/test_no_temp_writes.py"
],
"deleted_files": []
},
"verification_criteria": [
"scripts/tier2/failcount.py default state dir is scripts/tier2/state/<track>/ (Path.cwd()-relative)",
"scripts/tier2/write_report.py default failures dir is scripts/tier2/failures/ (Path.cwd()-relative)",
"scripts/tier2/run_track.py chdirs to repo_path before state/report calls",
"conductor/tier2/opencode.json.fragment has NO AppData allow rules in read/write",
"conductor/tier2/opencode.json.fragment has *AppData\\* bash deny rule (in addition to *AppData\\Local\\Temp\\*)",
"conductor/tier2/agents/tier2-autonomous.md contains 'NEVER USE APPDATA' or equivalent phrasing; no AppData path strings",
"conductor/tier2/commands/tier-2-auto-execute.md contains no AppData path strings",
"scripts/tier2/setup_tier2_clone.ps1 has no AppData variable declarations or New-Item/Set-Acl calls",
"scripts/tier2/run_tier2_sandboxed.ps1 has no AppData variable declarations",
"docs/guide_tier2_autonomous.md has no AppData path strings",
"conductor/workflow.md hard-bans table row says 'File access outside Tier 2 clone (AppData denied)'",
".gitignore has scripts/tier2/state/ and scripts/tier2/failures/",
"tests/test_tier2_slash_command_spec.py asserts NO AppData refs in agent prompt and command",
"uv run python scripts/run_tests_batched.py passes for test_failcount.py + test_tier2_report_writer.py + test_tier2_slash_command_spec.py + test_no_temp_writes.py",
"uv run python scripts/audit_no_temp_writes.py --strict exits 0"
],
"regressions_and_pre_existing_failures": [],
"pre_existing_failures_remaining": [],
"deferred_to_followup_tracks": [
{
"title": "Re-bootstrap the live Tier 2 clone",
"description": "The user re-runs pwsh -File scripts/tier2/setup_tier2_clone.ps1 after this track merges so the clone picks up the new inside-clone conventions and the AppData-denied permissions.",
"track_status": "manual user action"
}
],
"estimated_effort": {
"method": "scope (per workflow.md §Tier 1 Track Initialization Rules). NO day estimates.",
"scope": "11 source files + 3 test files + 1 doc + 1 workflow.md section + 1 .gitignore; ~15 atomic commits across 6 phases."
},
"risk_register": [
{
"risk": "An existing Tier 2 run is using the old AppData config and its state cannot be migrated automatically",
"likelihood": "high",
"mitigation": "Document in the spec that the user's existing live_gui_test_fixes_20260618 run is unaffected by this change until re-bootstrap. State on AppData is discarded on next bootstrap."
},
{
"risk": "The AppData path strings are hard-coded in a downstream script we missed",
"likelihood": "medium",
"mitigation": "Run scripts/audit_no_temp_writes.py --strict after the changes. Run a grep for 'AppData' across scripts/ and conductor/ and docs/ as the final verification."
},
{
"risk": "The TIER2_STATE_DIR / TIER2_FAILURES_DIR env-var escape hatch is removed by mistake",
"likelihood": "low",
"mitigation": "The existing tests (tests/test_failcount.py:176,190,198 and tests/test_tier2_report_writer.py:25,33,40,71) monkeypatch the env var. They must still pass after the change."
}
]
}
@@ -0,0 +1,189 @@
# 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/<track>/state.json` for failcount state, `scripts/tier2/failures/` for failure reports, `scripts/tier2/artifacts/<track>/` 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 `<app-data>/tier2/<track-name>/state.json`) and line 25 (step 3 in Protocol — "Initialize failcount state at `<app-data>/tier2/<track-name>/state.json`") to reference `scripts/tier2/state/<track-name>/state.json`.
- **HOW:** three edit_file calls.
- **SAFETY:** the slash command prompt is what the Tier 2 agent reads; if it still says `<app-data>`, 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"`.
@@ -0,0 +1,117 @@
# 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\<track>\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/<track>/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/<track>_<utc-ts>.md` and `scripts/tier2/failures/<track>.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/<track>/`). 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 `<app-data>` 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).