From 6240b07b9efee7d619f24572317583caae2d52d9 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 26 Jun 2026 07:43:02 -0400 Subject: [PATCH] fix(tier2-sandbox): add git stash* and git clean -fd* to all 3 ban layers; spell out timeline-is-immutable principle ROOT CAUSE: Tier 2 used 'git stash' during the cruft_elimination_20260627 track execution and corrupted the user's in-progress files. The user explicitly stated: 'if an agent fucks up, their tendency to want to revert is not correct and instead they must live with the timeline and just do corrections with a new commit. They can grab artifacts, code, etc, from old commits but they cannot reset to that.' This commit adds HARD BANs on git stash* and git clean -fd* at 3 layers (per the existing 3-layer defense model documented in conductor/tier2/agents/tier2-autonomous.md): LAYER 1: AGENTS.md - Added new HARD BAN: 'git stash* (any form: git stash, git stash pop, git stash apply, git stash drop, git stash clear) is FORBIDDEN. Stashing inverts the safety net of the working tree' LAYER 2: conductor/tier2/opencode.json.fragment (Tier 2 autonomous) - Added 'git stash*', 'git stash pop*', 'git stash apply*', 'git stash drop*', 'git stash clear*', 'git clean -fd*', 'git clean -fdx*' to BOTH the top-level permission.bash deny list AND the agent.tier2-autonomous.permission.bash deny list - Also added 'git revert*' (was missing from fragment; already banned in prompt) - These are now HARD DENIED at the OpenCode permission layer; the agent cannot run them even if it tries LAYER 3: conductor/tier2/agents/tier2-autonomous.md - Added 'git stash* (any form)' to the Hard Bans list - Added 'THE TIMELINE-IS-IMMUTABLE PRINCIPLE' section spelling out exactly what to do when you fuck up: - When you make a wrong commit, write a NEW commit that fixes it - The git history is immutable on this branch - You CAN grab artifacts from old commits via 'git show : > ' - You CANNOT reset the branch HEAD to an old commit - 'git revert', 'git reset --hard', 'git reset --soft', 'git stash' are all attempts to rewrite history and BANNED - Correct pattern: pause, read the actual file, write a forward corrective commit with a commit message that explains the fix This addresses the root cause of the 2026-06-27 cruft_elimination corruption. Future Tier 2 autonomous runs will be blocked from running git stash* at 2 layers (OpenCode permission deny + Tier 2 prompt hard ban list) and reminded at the agent-prompt layer (THE TIMELINE-IS- IMMUTABLE PRINCIPLE section). --- AGENTS.md | 1 + conductor/tier2/agents/tier2-autonomous.md | 28 +++++++++++++++++++++- conductor/tier2/opencode.json.fragment | 20 ++++++++++++++-- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 84a9fd7f..0edbcc7e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,6 +57,7 @@ The 14 deep-dive guides under `docs/` (`guide_architecture.md`, `guide_ai_client - `set_file_slice` IS valid for multi-line content. The agent must verify the exact byte offsets with `get_file_slice` first, copy the line text character-for-character (including whitespace and EOL), and check whether the edit changes a public contract (function signature, yield shape, return type) that other code depends on. See `conductor/edit_workflow.md` for the full contract. - Do not use `git restore` while a user is mid-conversation without first confirming the desired state - HARD BAN: `git restore`, `git checkout -- `, `git reset` are FORBIDDEN without explicit user permission in the same message. They destroyed user in-progress src/* edits twice in one session (2026-06-07). If you think you need one, ASK FIRST. +- HARD BAN: `git stash*` (any form: `git stash`, `git stash pop`, `git stash apply`, `git stash drop`, `git stash clear`) is FORBIDDEN. Stashing inverts the safety net of the working tree: a `git add .` then `git stash` then "fresh start" pattern is exactly how Tier 2 corrupted files in the 2026-06-27 `cruft_elimination_20260627` track. The user explicitly stated "I hate when people fuck with my commits" — stashing throws away the user's in-progress edits silently. If you think you need a stash, you don't — use a NEW BRANCH or a WORKTREE instead. Tier 2 sandbox enforces this via `conductor/tier2/opencode.json.fragment` bash deny rules. - **HARD BAN: Day estimates in track artifacts (Tier 1).** Do NOT include day / hour / minute estimates in spec.md, plan.md, metadata.json, or any other track artifact. Day estimates are inaccurate noise; Tier 2 capacity is bounded by attention, not time. Measure effort by **scope** (N files, M sites, N tasks). The user / Tier 2 agent decides the actual pacing. See `conductor/workflow.md` §"Tier 1 Track Initialization Rules" for the full rule, replacement patterns, and rationale. (Added 2026-06-16 per user feedback: "Day estimates are inaccurate. Tier-2s can only do so much in a single track and there is no way in hell its going to be 'DAYS'.") - **HARD BAN: Opaque types in non-boundary code (added 2026-06-25).** LLMs default to `dict[str, Any]`, `Any`, `Optional[T]`, `hasattr()` polymorphism, and `.get('field', default)` because that's idiomatic Python training data. **All of these are BANNED in non-boundary code.** Use typed `@dataclass(frozen=True, slots=True)` with explicit fields; use `Result[T]` + `NIL_T` sentinels instead of `Optional[T]`; use direct attribute access instead of `.get()`. The ONLY place `dict[str, Any]` is allowed is the literal wire boundary (TOML/JSON parse functions); 2-3 functions per file. See `conductor/product-guidelines.md` "Core Value", `conductor/code_styleguides/data_oriented_design.md` §8.5 (The Python Type Promotion Mandate), `conductor/code_styleguides/python.md` §17 (LLM Default Anti-Patterns), and `conductor/code_styleguides/type_aliases.md` for the canonical mandates. User direction 2026-06-25: "I want the closest thing to c11/odin/jai in a scripting language... metadata should not be a dict[str, any]." diff --git a/conductor/tier2/agents/tier2-autonomous.md b/conductor/tier2/agents/tier2-autonomous.md index 769e05eb..741b701a 100644 --- a/conductor/tier2/agents/tier2-autonomous.md +++ b/conductor/tier2/agents/tier2-autonomous.md @@ -83,9 +83,35 @@ This gate catches the failure mode in the 2026-06-24 MCP regression where Tier 2 - `git checkout*` (any form) - use `git switch -c` for new branches, `git switch` to switch - `git restore*` (any form) - do not restore files (per AGENTS.md hard ban) - `git reset*` (any form) - do not reset state -- `git revert*` (any form) - per AGENTS.md hard ban; use FIX-IF-FAILS (amend or fixup commit) instead +- `git revert*` (any form) - per AGENTS.md hard ban. **THE TIMELINE IS IMMUTABLE**: when you fuck up a commit, you LIVE with the timeline and do a CORRECTION with a NEW commit. You can grab artifacts, code, or files from old commits via `git show : > ` or `git checkout -- ` (note: `git checkout ` for FILE extraction is allowed; `git checkout ` to switch is BANNED). But you CANNOT reset the branch HEAD to an old commit and pretend the wrong work never happened. The wrong work is part of history now; the fix is a follow-up commit that supersedes it. **NEVER use `git revert`, `git reset --hard`, or `git reset --soft`** to "undo" a bad commit — always go FORWARD with a corrective commit. +- `git stash*` (any form: `git stash`, `git stash pop`, `git stash apply`, `git stash drop`, `git stash clear`) - per AGENTS.md hard ban (added 2026-06-27); stashing throws away the user's in-progress edits silently. If you think you need a stash, you don't - use a NEW BRANCH or a WORKTREE instead. The 2026-06-27 `cruft_elimination_20260627` track was corrupted by Tier 2 using `git stash` and losing the user's in-progress files. - File access outside the Tier 2 clone - the OS blocks it. **NEVER USE APPDATA** for any read, write, or shell command; the `*AppData\\*` bash deny rule will halt the run if you try. +### THE TIMELINE-IS-IMMUTABLE PRINCIPLE (added 2026-06-27, after the cruft_elimination corruption) + +When you (the agent) fuck up — make a wrong commit, break a file, take a bad path — your first instinct will be to "undo" the mistake with `git revert`, `git reset`, or `git stash`. **THIS INSTINCT IS WRONG.** The user explicitly stated: "if an agent fucks up, their tendency to want to 'revert' is not correct and instead they must live with the timeline and just do corrections with a new commit." + +**The rule:** +- The git history is IMMUTABLE on this branch. Every commit you've made is part of the record. +- "Undoing" via `git revert` / `git reset` / `git stash` makes the user's review harder, not easier (the user has to read the diff between the bad and the "fix" to understand what went wrong). +- "Fixing forward" via a new commit makes the user's review EASIER: they can see exactly what changed between the bad commit and the fix. + +**Correct pattern when you fuck up:** +1. Pause. Read the actual file. Confirm the state. +2. Write a NEW commit that fixes the problem. The commit message should briefly say what was wrong and what you fixed. +3. If the bad commit introduced data corruption that the user will see, the user can `git revert` it during their review — that's the user's choice, not yours. +4. If you need to recover an old version of a file (because the bad commit destroyed it), use `git show : > ` to extract it. The bad commit is still in history; you're just reading from history to recover. + +**Wrong pattern (which you must NOT do):** +- `git revert ` to undo a commit +- `git reset --hard ` to throw away a bad commit +- `git stash` to "save" uncommitted work (it just disappears when you lose the branch) +- `git checkout -- .` to "go back to when things were good" (and then commit on top) + +These are all attempts to rewrite history. They are BANNED. The right answer is always a forward commit. + +**Concrete example:** if you realize commit N introduced a bug, write commit N+1 that fixes the bug. The user can see both commits in the diff and understand the full story. The user's CI / reviews / git log will all show both commits, which is what they want. + ## Conventions (MUST follow - added 2026-06-17; updated 2026-06-27) - **Test runner:** ALWAYS use `uv run python scripts/run_tests_batched.py` for test runs. NEVER call `uv run pytest` directly. The batched runner provides tier-based filtering, parallelization (xdist), and a summary table. Direct pytest is slow and bypasses the tiering that the live_gui tests depend on. diff --git a/conductor/tier2/opencode.json.fragment b/conductor/tier2/opencode.json.fragment index 8e0b7d6a..3594b3b6 100644 --- a/conductor/tier2/opencode.json.fragment +++ b/conductor/tier2/opencode.json.fragment @@ -51,7 +51,15 @@ "git push*": "deny", "git checkout*": "deny", "git restore*": "deny", - "git reset*": "deny" + "git reset*": "deny", + "git revert*": "deny", + "git stash*": "deny", + "git stash pop*": "deny", + "git stash apply*": "deny", + "git stash drop*": "deny", + "git stash clear*": "deny", + "git clean -fd*": "deny", + "git clean -fdx*": "deny" } }, "agent": { @@ -82,7 +90,15 @@ "git push*": "deny", "git checkout*": "deny", "git restore*": "deny", - "git reset*": "deny" + "git reset*": "deny", + "git revert*": "deny", + "git stash*": "deny", + "git stash pop*": "deny", + "git stash apply*": "deny", + "git stash drop*": "deny", + "git stash clear*": "deny", + "git clean -fd*": "deny", + "git clean -fdx*": "deny" } } }