Skip to content

[P1.33] Isolate tests from the developer's ambient .codeframe/ workspace (cwd walk-up) #975

Description

@frankbria

Surfaced while implementing #895 (PR #974). Not caused by that change — reproduces on clean main.

Problem

Four tests fail on any branch, including a pristine main checkout, purely because the developer's repo-root .codeframe/ directory exists:

  • tests/cli/test_v2_cli_integration.py::TestPRCommands::test_pr_merge
  • tests/ui/test_v2_scope_enforcement.py::TestMethodScope::test_read_key_allowed_on_get
  • tests/ui/test_v2_scope_enforcement.py::TestMethodScope::test_write_key_allowed_on_write
  • tests/ui/test_v2_scope_enforcement.py::TestAdminScope::test_admin_key_allowed_on_pr_merge

They fail with OperationalError('no such table: workspace') and exit_code == 1.

Mechanism. _check_proof9_merge_gate resolves the workspace from the process cwd:

try:
    workspace = get_workspace(Path.cwd())   # pr_commands.py:413
except FileNotFoundError:
    return None                             # only "no workspace" is tolerated

try:
    open_reqs = proof_ledger.list_requirements(workspace, ...)
except Exception as e:
    # Fail closed, like the API path: a broken ledger blocks the merge.
    raise typer.Exit(1)                     # pr_commands.py:422

CliRunner does not change cwd, so Path.cwd() is the repo root — which on a developer machine contains a real .codeframe/state.db left over from demos/dogfooding. When that DB predates the current schema, list_requirements raises, the gate correctly fails closed, and the test's exit_code == 0 assertion fails.

The production behaviour is right: a broken ledger should block a merge. The defect is that tests read the developer's ambient workspace instead of an isolated one.

Why this matters

  • CI cannot catch it. CI has no .codeframe/, so Test Suite (Unit + E2E) is green on main while the same commit is red locally. This is the inverse of the usual ambient-env trap and it is genuinely disorienting.
  • It taxes every remaining P0/P1. Each full-suite run surfaces 4 red tests that must be re-diagnosed and dismissed. During [P0.1] Gitignore .env.production and check history for committed secrets #895 this cost a full investigation cycle (stash → run on main → bisect to .codeframe/) before the change could be cleared.
  • It erodes the signal. A suite with permanently-red tests trains everyone to ignore red, which is how a real regression eventually ships.

Reproduction

# with a .codeframe/ present in the repo root
uv run pytest tests/cli/test_v2_cli_integration.py::TestPRCommands::test_pr_merge \
              tests/ui/test_v2_scope_enforcement.py -q
# => 4 failed, 6 passed

mv .codeframe /tmp/                 # remove the ambient workspace
uv run pytest <same args> -q
# => 10 passed

Evidence

  • codeframe/cli/pr_commands.py:413get_workspace(Path.cwd())
  • codeframe/cli/pr_commands.py:414 — catches only FileNotFoundError
  • codeframe/cli/pr_commands.py:419-422 — fail-closed on any ledger error
  • codeframe/cli/pr_commands.py:61,205 — two more Path.cwd() resolutions on the same surface

Acceptance criteria

  • Tests that invoke cwd-resolving CLI/API paths run against an isolated workspace (a tmp_path chdir fixture, an explicit repo_path, or a monkeypatched resolver) — not the developer's .codeframe/
  • The four named tests pass with a real .codeframe/ present in the repo root
  • A guard prevents regression: an autouse fixture (or conftest check) that fails loudly if a test resolves a workspace outside tmp_path
  • Production fail-closed behaviour on a broken ledger is unchanged — do not relax pr_commands.py:419-422 to make tests pass
  • uv run pytest tests/ --ignore=tests/e2e -m "not lifecycle" is green on a machine that has a .codeframe/ directory

Atomic by construction: one developer, one focused session.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions