| phase |
quick-260707-s44 |
| plan |
1 |
| type |
execute |
| wave |
1 |
| depends_on |
|
| files_modified |
src/phaze/routers/admin_agents.py |
tests/agents/routers/test_admin_agents.py |
|
| autonomous |
true |
| requirements |
|
| must_haves |
| truths |
artifacts |
key_links |
Revoked agents (revoked_at IS NOT NULL) never appear in the /admin/agents panel or its /_table HTMX poll partial |
The permanently-revoked legacy-application-server seed row is excluded from the operator agents panel |
The legacy-application-server Agent row still exists in the DB (FK default owner) β only its display is suppressed |
Non-revoked agents (alive/stale/dead/never) still render and sort correctly |
|
| path |
provides |
contains |
src/phaze/routers/admin_agents.py |
_load_agents with revoked_at IS NULL filter |
Agent.revoked_at.is_(None) |
|
| path |
provides |
tests/agents/routers/test_admin_agents.py |
Regression test asserting a revoked agent is absent + reconciled 5-state/sort-order tests |
|
|
| from |
to |
via |
pattern |
src/phaze/routers/admin_agents.py:_load_agents |
Agent.revoked_at |
select(Agent).where(Agent.revoked_at.is_(None)) |
revoked_at\.is_\(None\) |
|
|
|
Fix `legacy-application-server` (and any revoked agent) leaking into the `/admin/agents`
operator panel. `_load_agents()` currently runs `select(Agent)` with NO `revoked_at`
filter, so the permanently-revoked legacy seed row shows up in the "Agents Β· heartbeating"
panel. Every other agent query in the codebase (main.py, shell.py, pipeline.py) already
filters revoked agents via `.where(Agent.revoked_at.is_(None))`. Add the same filter here.
Purpose: Operator panel should only show live/heartbeating agents, not the permanently-revoked
FK-placeholder row.
Output: One-line filter change in _load_agents + reconciled/added tests.
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
@.planning/STATE.md
@src/phaze/routers/admin_agents.py
@src/phaze/models/agent.py
@src/phaze/services/agent_liveness.py
@tests/agents/routers/test_admin_agents.py
From src/phaze/models/agent.py:
LEGACY_AGENT_ID = "legacy-application-server" (module constant)
Agent.revoked_at: Mapped[datetime | None] β nullable timezone-aware column; NULL means "not revoked"
From src/phaze/routers/admin_agents.py (the ONLY line to change):
_load_agents(session) line ~68: result = await session.execute(select(Agent))
- BOTH
page() (full-page + HX-Request partial) and table_partial() (/_table poll) call _load_agents,
so a single filter change covers all render paths.
From src/phaze/services/agent_liveness.py:
classify(agent, now) still returns "revoked" when revoked_at IS NOT NULL, and sort_key
still has the revoked tier. These stay as-is (reachable if an agent is revoked mid-load elsewhere).
Out of scope to remove β just note they become effectively unreachable via this panel.
From tests/conftest.py (IMPORTANT nuance):
- The
async_engine fixture seeds the legacy row via Agent(id=LEGACY_AGENT_ID, name=LEGACY_AGENT_ID, scan_roots=[])
WITHOUT setting revoked_at. So in the TEST DB the legacy row is a NEVER agent (not revoked),
unlike production where alembic migration 012 stamps revoked_at == created_at.
=> The absence regression test MUST assert on an EXPLICITLY-revoked agent (the smoke fixture's
RevokedBox / id revoked-agent), NOT on the legacy row.
Task 1: Filter revoked agents out of _load_agents
src/phaze/routers/admin_agents.py
In `_load_agents` (~line 68), change `select(Agent)` to `select(Agent).where(Agent.revoked_at.is_(None))`,
matching the established codebase pattern in src/phaze/main.py, src/phaze/routers/shell.py, and
src/phaze/routers/pipeline.py. This is the ONLY source change. Do NOT modify the classify/sort_key
revoked handling in agent_liveness.py, the REVOKED status pill template, or the legacy Agent row itself β
the legacy-application-server row must keep existing in the DB (it is the FK default owner for
file.agent_id and scan_batch.agent_id with ondelete=RESTRICT). Update the `_load_agents` docstring's
"Load every Agent" phrasing to reflect that revoked agents are now excluded (mention the shared
`revoked_at IS NULL` convention). If a leading comment or module docstring elsewhere claims revoked
agents "land last" in this panel, adjust wording so it is not self-contradictory (they no longer appear).
cd /Users/Robert/orca/workspaces/phaze/post-deploy-debug-2 && grep -q "revoked_at.is_(None)" src/phaze/routers/admin_agents.py && uv run mypy src/phaze/routers/admin_agents.py
`_load_agents` executes `select(Agent).where(Agent.revoked_at.is_(None))`; mypy clean; legacy row untouched in DB.
Task 2: Add revoked-agent absence regression + reconcile 5-state and sort-order tests
tests/agents/routers/test_admin_agents.py
Reconcile the two tests that previously expected the revoked agent to render, and add a new
absence regression test. The `smoke` fixture seeds an explicitly-revoked `RevokedBox` (id `revoked-agent`,
revoked_at=now) plus alive/stale/dead/never agents β reuse it.
1. Add `test_revoked_agent_absent`: GET /admin/agents/_table (and optionally GET /admin/agents),
assert `"RevokedBox" not in body` and `'aria-label="Status: revoked"' not in body`, while asserting
a non-revoked control (e.g. `"AliveBox" in body`) IS present β proving the filter excludes only
revoked rows, not the whole table. This is the core regression guard for the leak.
2. Reconcile `test_status_pills_render_all_5_states` (~line 154): the revoked pill can no longer render
in this panel. Remove the `"REVOKED" in body` assertion (line ~172) and rename/redoc the test to
cover the 4 states that DO render (alive/stale/dead/never). Keep the gray-100/dark:gray-800 assertion
(NEVER uses the same neutral surface). Update the module docstring bullet (line ~7) accordingly.
3. Reconcile `test_sort_order` (~line 254): remove `"revoked"` from the `pos` dict (line ~265) and drop
it from the final chained assertion so it reads
`pos["alive"] < pos["stale"] < pos["dead"] < pos["never"]`. Update the test docstring (line ~256) and
the module docstring bullet (line ~9) from "alive β stale β dead β never β revoked" to
"alive β stale β dead β never (revoked agents are filtered out of the panel)".
Do NOT weaken the empty-state or BLOCKER-2 tests. Keep line length <= 150.
cd /Users/Robert/orca/workspaces/phaze/post-deploy-debug-2 && uv run pytest tests/agents/routers/test_admin_agents.py -x -q && uv run ruff check tests/agents/routers/test_admin_agents.py
New `test_revoked_agent_absent` passes; reconciled 5-state and sort-order tests pass; full test_admin_agents.py suite green; ruff clean.
- `grep -n "revoked_at" src/phaze/routers/admin_agents.py` shows the new `.where(Agent.revoked_at.is_(None))` filter.
- `uv run pytest tests/agents/routers/test_admin_agents.py -q` passes (including the new absence test and reconciled tests).
- `uv run ruff check src/phaze/routers/admin_agents.py tests/agents/routers/test_admin_agents.py` clean.
- `uv run mypy src/phaze/routers/admin_agents.py` clean.
- Legacy row untouched: no DELETE/UPDATE against the Agent row anywhere in the diff.
<success_criteria>
- Revoked agents (including the legacy placeholder in production, where revoked_at is stamped) never appear in
/admin/agents or /admin/agents/_table.
- Non-revoked agents still render and sort alive β stale β dead β never.
- The legacy-application-server Agent row still exists in the DB (display-only fix).
- Regression test locks the fix; previously-passing revoked-render/sort tests reconciled to expect absence.
</success_criteria>
Create `.planning/quick/260707-s44-hide-revoked-agents-legacy-application-s/260707-s44-SUMMARY.md` when done.