Skip to content

[fix] - Ponytail-style tests-refactor pass over tests/ and .github/workflows - #1652

Merged
cgfixit merged 10 commits into
mainfrom
claude/ponytail-tests-refactor
Oct 13, 2026
Merged

cgfixit merged 10 commits into
mainfrom
claude/ponytail-tests-refactor

Conversation

@cgfixit

@cgfixit cgfixit commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Proposed changes

A /tests-refactor loop run with the retired /ponytail discipline (YAGNI, no dead code, abstract only at ≥4 identical copies; see docs/audits/PONYTAIL_PLUGIN_6.29.md) over tests/ and .github/workflows/. Today's docs/audits/2026-10-13-ponytail-audit.md explicitly left tests/ untriaged, and this pass covers it.

Cuts (ponytail):

  • tests/import_graph.py: replaces six byte-identical AST import walkers in the test_*_isolation.py I6 guards. Each guard aliases it to its old local name, so no test body or file name changes. Those files are cited by name across the docs. Registered in tests/README.md.
  • tests/test_gate_query_auth.py: drops test_device_token_caller_with_no_browser_headers_still_works, a byte-identical copy of test_device_token_works (same fixtures, request and asserts). Its rationale moves onto the surviving test as the docstring.
  • ci.yml and pip-audit.yml: the torch-pin "duplicated in" comments had drifted. They sent a future bump to devskim.yml (no torch) and copilot-setup-steps.yml (hashed lock, no literal pin) and left out nemo-guardrails.yml. They now keep only the grep instruction, which cannot drift.

Coverage, aimed at real logic rather than at 100%:

  • sync/scheduled.py 54→79%: policy-derived keystore service and its missing-row refusal; macOS Keychain exit codes (44 means absent; any other nonzero is an error; exactly one newline is stripped); Linux returns None; usage and missing-rclone exits; a real run hands the sync CLI only the prepared env (GH_TOKEN gone).
  • opentweet/client.py 60→83%: uses a real httpx.Client over MockTransport. Transport errors never echo the key or host. The /query failure is audited by fingerprint without the query text. Covers /me, plus a non-JSON 201.
  • utils/bounded_executor.py 83→100%: the real /query and auth capacity bound, which gate tests replace with an AsyncMock. Covers refusal at capacity, release after completion, a cancelled caller keeping its slot, slot release when a submit fails (mutation-checked: deleting that release() fails the test), and context propagation.
  • gate_auth.py 81→93%: the RBAC lifecycle routes (role, disable, enable, delete). Covers success paths, AUTH_LAST_ADMIN on all three mutations, operators acting only on non-admins, disable blocking login until enable, 404s, CSRF, and the audit events.
  • gate_memory.py 66→88%: the apply-time injection block over HTTP (400 PROMPT_INJECTION_BLOCKED, an audit event, no fact written), reject being final, and the listings.

Follow-up commits from CI and review:

  • c44e994: addresses two CodeQL findings on the new tests.
    • The cancelled task is now awaited through asyncio.gather, with an assert on the result. CodeQL had read the bare await as a no-op and turned the check red.
    • Each state-changing request in the new RBAC tests moves out of its assert, so python -O cannot strip it.
  • b7cf992: fixes windows-latest.
    • The scheduled-sync run-path test used clear=True, which removed USERPROFILE, so Path.home() raised inside load_sync_config. It now injects only GH_TOKEN.
    • Removing os.environ.clear() from sync/scheduled.py still fails the test.
  • Codex review on c44e994: no findings.

Deliberately kept (ponytail "keep"): the 27× per-file reset_config_cache autouse fixture (making it global would re-parse config.yaml in ~6,000 tests); the torch cache/download steps ×4 across 3 workflows (dep-guard reads per-file pins); policy.fallback.enabled in conftest (it mirrors a dead config.yaml key; removing it is the audit's Tier 2 owner call). All pytest.skip calls are conditional, no module-level test helper is unreferenced, and every workflow env: var has a consumer.

Invariant / Governance Impact: none. Only tests/ and comments in two workflows changed; no source file is touched. Graph topology, the RAG-first entry, audit convergence, the I3 gate, soul writes and I6 imports are unchanged. check_invariants.py: 46 passed, 0 failed.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Security (auth, privacy, injection, egress, redaction, or secrets)
  • Invariant / Governance refinement (use this for changes that strengthen or evolve the 6 invariants, I6 isolation, or harness phases)
  • Dependencies (added, removed, or bumped)
  • CI / infrastructure (workflows, hooks, templates, or checkers)
  • Refactor or tests (behavior-preserving, no new capability)
  • Documentation update
  • Other (none of the boxes above fit)

Scope: tests + Infrastructure / CI comments only.

Benefits / why

  • Real behavior now has tests on security-relevant paths that previously had none: the RBAC lifecycle, memory apply's injection block, the /query concurrency bound, and keystore exit codes. Coverage went from 90.14% to 90.83%, with 37 net new tests.
  • There is less duplicated test scaffolding, so a fix to what counts as an "import" now reaches all six I6 guards at once.
  • A future torch bump is no longer pointed at the wrong files.

Risks to monitor

  • tests/import_graph.py is a new non-test_* module in tests/. It is imported as tests.import_graph, the same way tests.executor_sandbox_double already is.
  • test_bounded_executor.py uses real threads coordinated with threading.Event, with no sleeps. It is green on all four legs.
    • If the capacity check in BoundedExecutor.run is ever deleted, the cancel test hangs (the job's 30-minute timeout) instead of failing fast.
    • Making it fail fast would need a timeout dependency, which this PR does not add.
  • The macOS Keychain tests patch sys.platform (and are skipped on Windows because they import pwd).

Checklist

  • I have read docs/THREAT_MODEL.md, INVARIANTS.md and SECURITY.md (and any relevant Phase docs)
  • This change preserves all 6 security invariants and I6 module isolation (explicit evidence or invariant matrix included for core changes)
  • Full sandbox validation has been run (GROK_API_KEY=dummy pytest tests/ -q --tb=short, and bash .claude/skills/CyClaw-Sandbox/verify.sh for core RAG/agentic paths) and passes with no regressions
  • No new external network dependencies or mandatory online LLM assumptions were introduced without explicit justification + offline fallback path
  • For any agentic/fsconnect change: two-phase audit, quota enforcement, governed delete/trash, and write guards have been verified
  • Relevant architecture docs or threat model notes have been updated if core behavior or topology changed
  • Commit messages follow the title prefix convention above
  • For large or complex changes: before/after invariant matrix + sandbox evidence is included in "Further comments" or linked

Further comments

Run directly in this session (Python 3.12 venv, Linux):

  • Full suite with --cov, twice. The explicit /tests-refactor invocation allows this.
    • Baseline: 6174 passed, 2 failed, 141 skipped, 90.14%.
    • Final: 6213 passed, 0 failed, 141 skipped, 90.83%.
  • The 2 baseline failures were both HardSandboxUnavailable: bubblewrap is required. The container had no bwrap; after installing it, both pass. This is environmental, not a code fault.
  • python -m guardrails.verify_install: all PASS.
  • invariant-guard 46/46; doc-sync 0 drift; dotenv-guard and dep-guard clean; ruff check --select F,B,S . clean; advisory ruff clean on every touched test file. The pre-existing ruff format drift in the isolation files is unchanged, not reformatted.
  • After the follow-up commits, the touched test files were re-run directly, and the scheduled-sync and executor tests were mutation-checked.

CI on b7cf992: all green, including the four test legs (ubuntu-latest, ubuntu-22.04, windows-latest, macos-latest), conda ci (3.12), postgres-backend, verify-install on all three OSes, all 13 verify-skills jobs, workflow-lint (actionlint + zizmor), and CodeQL.

Branch note: the session runtime asked for cc/zen-archimedes-sehyrr, but the tracked hooks refuse cc/*. With the owner's OK this uses claude/ponytail-tests-refactor, which the template prescribes, and no hook was bypassed.

Rollback: revert the PR. It is tests and comments only.

Suggested merge order of open PRs

Trial merge verified: branch contains main @ 522b3f7 (2026-10-13); no other open PRs.

This PR · Safe ✅
Impact: independent, only open PR; tests + workflow comments.
Action: Mark ready and merge; CI green on all legs, mergeable clean.

ELI5

The test suite had some copy-pasted parts and some important doors nobody had ever tried to open.

Copy-paste: six safety tests each carried their own identical "which modules does this file import?" scanner. Now there is one, in tests/import_graph.py. One test in tests/test_gate_query_auth.py was an exact twin of another and was removed. Two workflow comments (ci.yml, pip-audit.yml) told whoever bumps torch to look in the wrong files; they now just say "grep for it".

Untested doors, now tested:

  • Admin actions like disabling, deleting or re-roling users, including "you can't remove the last admin" (gate_auth.py).
  • Memory refusing to save text that looks like a prompt injection (gate_memory.py).
  • The limit on how many /query requests run at once (utils/bounded_executor.py).
  • How scheduled sync reads its password from the Mac Keychain (sync/scheduled.py).
  • How the OpenTweet client fails without leaking its key (opentweet/client.py).

Two later fixes, both to my own new tests:

  • CodeQL flagged two spots, and both are now rewritten. One was a request hidden inside an assert, which python -O would silently skip.
  • One new test wiped every environment variable, which broke Windows: it can't find the home folder without USERPROFILE. That test now only adds the one variable it checks.

No app code changed. Only tests and comments did. CI is green on Linux, Windows and macOS. One thing to know: if someone deletes the concurrency limit in BoundedExecutor, the cancel test notices by hanging until the 30-minute job timeout, not by failing straight away.

🤖 Generated with Claude Code

https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g


Generated by Claude Code

Last updated: 2026-10-13 14:46 ET

…audit.yml with the grep instruction

Both comments sent a future torch bump to devskim.yml (no torch) and
copilot-setup-steps.yml (hashed lock, no literal pin) while omitting
nemo-guardrails.yml. The grep they already recommend cannot drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
…files

Six byte-identical copies of the walker collapse into tests/import_graph.py
(not test_-named, so not collected). Each guard aliases it to its old local
name, so no test body or file name changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
test_device_token_caller_with_no_browser_headers_still_works sent the same
request with the same fixtures and assertions as test_device_token_works.
Its rationale moves onto the surviving test as the docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
… 54% to 79%

Pins the policy-derived service name and its missing-row refusal, the macOS
Keychain exit codes (44 absent, other nonzero an error, one newline
stripped), Linux's no-keystore None, the usage and missing-rclone exits,
and that a real run hands the sync CLI only the prepared environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
… to 83%

Real httpx.Client over MockTransport pins: a dead /query gateway is
retryable, names only the exception type, and audits a fingerprint without
the query text; GET /me sends the bearer and classifies 401/503/non-object
replies; transport errors toward OpenTweet never echo the key or host; a
non-JSON 201 from /posts is a hard failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
…ed_executor.py 83% to 100%

Gate tests mock it with an AsyncMock, so the semaphore behind /query and
the auth workers was never run. Event-coordinated tests pin refusal at
capacity, release after completion, a cancelled caller holding its slot
while its worker runs, slot release on a failed submit (mutation-checked),
and context propagation into the worker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
…er HTTP

Role set, disable, enable and hard delete had no HTTP test of their success
paths. Pins last-admin protection on all three mutations (AUTH_LAST_ADMIN
with the action named), operator-on-non-admin toggling, disable blocking
login until enable, 404 for unknown users, CSRF on each write, and the audit
event each success emits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
…te_memory.py 66% to 88%

The apply-time injection scan had no HTTP-level test. Pins a 400
PROMPT_INJECTION_BLOCKED with its audit event and no fact written; reject
being final (apply and re-reject both 400) with the rejected listing and
the unknown-status fallback; an unknown proposal id as 400 not 500; and
the episodes listing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

PR is a complete fill of .github/PULL_REQUEST_TEMPLATE.md (every section, ticked boxes, merge order, ELI5 last, last-updated stamp, title, branch).

Comment thread tests/test_gate_auth_admin.py Fixed
Comment thread tests/test_bounded_executor.py Fixed
Await the cancelled task through asyncio.gather so the cancellation is an
asserted value (CodeQL read the bare await as a no-op), and move each
state-changing request out of its assert so python -O cannot strip it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
@cgfixit

cgfixit commented Oct 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T20:06:19.190866Z b7cf992 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: c44e994df9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

… run-path test

clear=True stripped USERPROFILE, so Path.home() raised inside
load_sync_config on windows-latest. main() replaces os.environ itself, so the
test now injects only GH_TOKEN; removing os.environ.clear() still fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145zL6RjeL5rrT7RWULBN1g
@cgfixit
cgfixit marked this pull request as ready for review October 13, 2026 20:05
@cgfixit
cgfixit merged commit 9a0cdc2 into main Oct 13, 2026
76 checks passed
@cgfixit
cgfixit deleted the claude/ponytail-tests-refactor branch October 13, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants