docker(deps): bump python from 3.13-slim to 3.14-slim in the docker-images group across 1 directory#1
Open
dependabot[bot] wants to merge 1 commit into
Open
Conversation
Contributor
Author
LabelsThe following labels could not be found: Please fix the above issues or remove invalid values from |
e6677eb to
1bcc779
Compare
1bcc779 to
53a6c6b
Compare
53a6c6b to
bb3b2e3
Compare
bb3b2e3 to
c284f72
Compare
c284f72 to
e89ad13
Compare
e89ad13 to
5f752b4
Compare
Bumps the docker-images group with 1 update in the / directory: python. Updates `python` from 3.13-slim to 3.14-slim --- updated-dependencies: - dependency-name: python dependency-version: 3.14-slim dependency-type: direct:production dependency-group: docker-images ... Signed-off-by: dependabot[bot] <support@github.com>
5f752b4 to
1c00293
Compare
SimplicityGuy
added a commit
that referenced
this pull request
May 13, 2026
* chore(26): sync phase 26 planning artifacts into worktree
Pulls the Phase 26 planning files (PLAN, CONTEXT, RESEARCH, PATTERNS,
VALIDATION, DISCUSSION-LOG) plus updated STATE/ROADMAP/REQUIREMENTS from
main into this worktree so plan execution has the canonical references.
* chore(26): add tenacity + respx deps and mypy strict overrides
Wave 0 foundation (Task 1 of Plan 26-01, D-11 + D-31 + D-33):
- Add tenacity>=8.5.0 to [project].dependencies (runtime retry decorator
for PhazeAgentClient HTTP methods in Plan 02).
- Add respx>=0.21.1 to [dependency-groups].dev (httpx mock library for
PhazeAgentClient contract tests in Plan 02).
- Add [[tool.mypy.overrides]] blocks for phaze.services.agent_client and
phaze.services.agent_task_router that opt them into strict checking
(disallow_untyped_defs, check_untyped_defs, warn_return_any,
strict_equality) despite the global services/ exclude. New code in
these two modules will be fully type-checked.
- Regenerate uv.lock with the new constraints.
The global mypy exclude = "^(tests/|prototype/|services/)" is unchanged
so the rest of services/ continues to iterate without strict gates.
* feat(26): split phaze.config into Base + Control + Agent settings (D-14)
Wave 0 foundation (Task 2 of Plan 26-01):
- Add `Role` StrEnum with values "control" and "agent".
- Split the legacy `Settings` class into:
* `BaseSettings(PydanticBaseSettings)` β shared fields (database_url,
redis_url, debug, scan_path, models_path, output_path, worker_*,
audfprint_url, panako_url, discogsography_url, api_host/port,
agent_token_prefix, agent_file_chunk_max).
* `ControlSettings(BaseSettings)` β application-server role: Discogs
concurrency + LLM proposal generation fields.
* `AgentSettings(BaseSettings)` β file-server role: `agent_api_url`,
`agent_token: SecretStr`, `scan_roots: list[str]`. A
`model_validator(mode="after")` raises ValueError at construction
time if any is missing/empty so the agent worker fails fast rather
than silently producing 401s or path-traversal rejections at
runtime (D-14 + threat model T-26-01-T2 mitigation).
- Add `@lru_cache(maxsize=1) def get_settings() -> BaseSettings`: reads
`PHAZE_ROLE` env (default "control") and dispatches to the right
subclass.
- Replace `settings = Settings()` with `settings: ControlSettings = ...`
module-level singleton (annotated as ControlSettings so existing
call sites that read `settings.llm_*` / `settings.discogs_match_concurrency`
still type-check). Module-level singleton stays Control-typed; the
agent worker (Plan 10) calls `get_settings()` / `AgentSettings()`
directly and stashes the instance at ctx["agent_settings"].
- Add `Settings = ControlSettings` back-compat alias for test files
that still import the legacy class name.
Deviations from the literal plan text (Rule 2 β missing critical
functionality):
- pydantic-settings v2 does NOT natively comma-split `PHAZE_AGENT_SCAN_ROOTS
=/a,/b` into `["/a", "/b"]`. It expects a JSON-encoded list and raises
JSONDecodeError on bare comma-strings. Fix: annotate the field as
`Annotated[list[str], NoDecode]` so pydantic-settings skips the JSON
decode step, then add a `@field_validator("scan_roots", mode="before")`
classmethod `_split_scan_roots` that comma-splits string input while
passing native list input through unchanged.
- pydantic-settings reads env vars by *field name* (case-insensitive)
absent an `env_prefix`. The documented env-var names `PHAZE_AGENT_*`
required explicit `validation_alias=AliasChoices(...)` per field so
both the documented env-var form and direct kwargs work.
Module-level `settings = get_settings()` keeps every existing
`from phaze.config import settings` call site (37+ across src/ and
tests/) working unchanged. Tests pass: 593 routers+services, 102
models+config-worker+phase01-gaps. Full repo mypy clean.
* feat(26): extend ProposalStatus + FileState with D-28 transition targets
Wave 0 foundation (Task 3 of Plan 26-01):
- ProposalStatus gains EXECUTED + FAILED. These are the terminal targets
of the PATCH /api/internal/agent/proposals/{id}/state router (Plan 08).
State machine: APPROVED -> EXECUTED or APPROVED -> FAILED. Re-PATCHing
the same terminal value is a 200 idempotent no-op; any other transition
returns 409.
- FileState gains MOVED + UNCHANGED. MOVED pairs with ProposalStatus.
EXECUTED (file successfully copy-verified-deleted at new path);
UNCHANGED pairs with ProposalStatus.FAILED (file stays at original
path). The pre-existing FileState.EXECUTED + FAILED values are
retained for Phase 25-era execution-log emit paths; Phase 28's batch
execution will adopt MOVED/UNCHANGED when wiring the PATCH endpoint
into the live dispatch loop.
No alembic migration needed:
- Proposal.status uses String(20): "executed" (8) and "failed" (6) fit.
- FileRecord.state uses String(30): "moved" (5) and "unchanged" (9) fit.
StrEnum values store as plain strings β adding values widens the
accepted value set without altering DDL. Existing 83 model tests pass.
* docs(26): finalize Phase 26 Plan 01 -- Wave 0 foundation summary
Captures the outcome of Plan 26-01 (tenacity+respx deps, settings
split into BaseSettings + ControlSettings + AgentSettings with
fail-fast scan_roots validator, enum extensions ProposalStatus.
EXECUTED|FAILED + FileState.MOVED|UNCHANGED) and advances STATE.md:
- Current Position now points to Phase 26 / Plan 01 complete, ready
for Plan 02 (PhazeAgentClient + AgentApiError).
- Resume file updated to 26-02-PLAN.md.
- Four [Phase 26-01]: decisions appended to Accumulated Context
documenting the pydantic-settings v2 env-var quirks discovered
during execution (no native comma-split for list[str]; no automatic
PHAZE_AGENT_* prefix mapping) and the back-compat alias choices.
- Progress recalculated to 14/26 plans (54%).
The SUMMARY.md records all four deviations (two Rule-2 fixes for
pydantic-settings quirks, two Rule-1 fixes for type-system and
test-import regressions) with their exact rationale.
* test(26-02): add respx contract tests for PhazeAgentClient (RED)
- 9 respx-mocked async tests covering D-09..D-13 invariants
- Asserts 4xx never retried (call_count == 1) for 401/403/404/422
- Asserts 5xx retries 3 times (call_count == 3) on persistent 500
- Asserts 5xx-then-200 recovery (call_count == 2)
- Asserts ConnectError retries 3 times (call_count == 3)
- Asserts Authorization: Bearer <token> header injected
- whoami parses AgentIdentity model from response JSON
Tests fail at import (ModuleNotFoundError) until Task 2 ships
phaze.services.agent_client and Plan 03 ships agent_analysis +
agent_identity schemas.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26-03): add failing tests for 4 HTTP-boundary schema modules
RED phase for Task 1 of Plan 26-03. Tests target modules that don't exist yet:
- tests/test_schemas/test_agent_identity.py (AgentIdentity β D-15)
- tests/test_schemas/test_agent_analysis.py (AnalysisWritePayload/Response β D-26)
- tests/test_schemas/test_agent_tracklists.py (TracklistCreatePayload + nested β D-27, T-26-07-DoS)
- tests/test_schemas/test_agent_proposals.py (ProposalStatePatch/Response β D-28)
Covers:
- extra='forbid' on request bodies and nested items
- Literal value validation against ProposalStatus/FileState enum string values
- _require_path_when_moved conditional validator
- max_length=2000 cap on tracks (T-26-07-DoS)
- Optional-everything semantics on AnalysisWritePayload
- bpm >= 0, danceability/energy in [0, 1] bounds
* feat(26-03): add 4 HTTP-boundary schemas for agent endpoints (D-15, D-26, D-27, D-28)
GREEN for Task 1 of Plan 26-03. Implements the wire-level Pydantic models
consumed by Wave 2 routers (Plans 05-08) and the PhazeAgentClient (Plan 02):
- agent_identity.py: AgentIdentity (response-only, loose schema for /whoami)
- agent_analysis.py: AnalysisWritePayload + AnalysisWriteResponse (PUT /analysis,
all fields optional for partial-PUT, extra='forbid' + ge/le bounds)
- agent_tracklists.py: TracklistTrackPayload + TracklistCreatePayload
(POST /tracklists, request_id idempotency, tracks min=1/max=2000 per
T-26-07-DoS) + TracklistCreateResponse
- agent_proposals.py: ProposalStatePatch with _require_path_when_moved
model_validator (current_path mandatory when file_state='moved') +
ProposalStateResponse. Literal values mirror ProposalStatus.EXECUTED/FAILED
+ FileState.MOVED/UNCHANGED enum string values from Plan 26-01.
Every request/nested schema sets ConfigDict(extra='forbid') per Phase 25 D-16
(unknown body keys -> 422). Response schemas stay loose for forward-compat.
All 34 unit tests pass; mypy strict + ruff clean.
* test(26-03): add failing tests for agent_tasks SAQ payload schemas
RED for Task 2 of Plan 26-03. Tests the 6 models that will land in
src/phaze/schemas/agent_tasks.py: ProcessFilePayload, ExtractMetadataPayload,
FingerprintFilePayload, ScanLiveSetPayload, ExecuteApprovedBatchPayload, and
the nested ExecuteBatchProposalItem.
Covers D-22..D-24 invariants:
- D-22: only ProcessFilePayload carries models_path
- D-23: ExecuteApprovedBatchPayload is fully self-contained
(no DB read-back mid-job; B2 Option A)
- D-24: NO payload has a current_path field (agents work off original_path)
- min/max_length=500 on proposals list (DoS hardening)
- JSON round-trip equality preserved
- extra='forbid' on all 6 classes
* feat(26-02): implement PhazeAgentClient with tenacity retry funnel + 4-class error hierarchy (GREEN)
Implements D-09..D-13 + D-31/32/33:
- PhazeAgentClient mirrors DiscogsographyClient pattern (services/discogs_matcher.py).
- Constructor injects bearer token as default Authorization header on the
underlying httpx.AsyncClient -- token is NEVER stored as an instance
attribute (T-26-02-I mitigation, D-13).
- _request funnel applies tenacity AsyncRetrying with stop_after_attempt(3),
wait_exponential_jitter(initial=0.5, max=4.0), retry_if_exception(_should_retry).
- _should_retry returns True for ConnectError, ReadTimeout, WriteTimeout, and
5xx HTTPStatusError. NEVER returns True for 4xx (D-11, D-32).
- 4-class exception hierarchy (D-12): AgentApiError base + AgentApiAuthError
(401/403, no retry) + AgentApiClientError (other 4xx, no retry) +
AgentApiServerError (5xx + persistent network, after retry exhaustion).
- DEBUG on success, WARNING on failure; log format strings exclude the token
(T-26-02-I, D-13).
- 10 endpoint methods (D-10): whoami, upsert_files, put_metadata,
put_fingerprint, put_analysis, create_tracklist, post_execution_log,
patch_execution_log, patch_proposal_state, heartbeat.
- PUT/PATCH methods use exclude_unset=True per Phase 25 CR-01 partial-update
semantics. POST + heartbeat use full bodies.
- _client kwarg supports respx test injection (leading underscore = private).
- Plan 03 schema imports gated behind TYPE_CHECKING + lazy method-body imports
so the module loads independent of merge order; type: ignore[import-not-found]
markers self-delete via warn_unused_ignores once Plan 03 lands.
Verified:
- uv run ruff check src/phaze/services/agent_client.py exits 0
- uv run mypy src/phaze/services/agent_client.py exits 0 (strict per
pyproject.toml [[tool.mypy.overrides]] for the agent_client module)
- Contract tests fail at import for Plan 03 schemas (agent_analysis,
agent_identity, agent_proposals, agent_tracklists) -- GREEN gate satisfied
at Wave 2 merge per plan body acceptance criteria.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-03): add 5 SAQ-job payload schemas in agent_tasks.py (D-22..D-24)
GREEN for Task 2 of Plan 26-03. Ships the typed payload models used by every
file-bound SAQ task in Plan 11. Task bodies will call <Payload>.model_validate
(ctx kwargs) at entry, applying the same extra='forbid' strictness as HTTP
request bodies (D-16).
Payloads:
- ProcessFilePayload (essentia analysis; only payload with models_path)
- ExtractMetadataPayload (mutagen tag extraction)
- FingerprintFilePayload (audfprint + panako submission)
- ScanLiveSetPayload (live-set fingerprint resolution)
- ExecuteApprovedBatchPayload (per-agent execution sub-batch β B2 Option A)
- ExecuteBatchProposalItem (nested: per-proposal copy+verify+delete details)
Invariants enforced:
- D-22: only ProcessFilePayload carries models_path
- D-23: ExecuteApprovedBatchPayload is fully self-contained (proposals carry
original_path + proposed_path + optional sha256_hash so the agent
never reads DB state mid-job)
- D-24: NO payload carries current_path anywhere
- Field(min_length=1, max_length=500) caps per-job batch size
- All 6 classes set ConfigDict(extra='forbid')
`from __future__ import annotations` omitted because Pydantic resolves the
uuid.UUID field annotation at runtime; deferred annotations would trigger
ruff TC003 on the runtime-needed uuid import (mirrors agent_metadata.py pattern).
22 unit tests pass; mypy strict + ruff clean.
* docs(26-02): complete PhazeAgentClient plan -- summary + state + roadmap
- 26-02-SUMMARY.md captures contract-test invariants, retry policy values,
mypy-strict-override confirmation (RESEARCH A8 / A12), and the 3 auto-fix
deviations (PLC0415 noqa, import-not-found ignore, uuid TC003 hoist).
- STATE.md advances Current Plan to 02 (complete), records the 3
Plan-26-02 decisions, and bumps progress to 15/26 plans (58%).
- ROADMAP.md updated for Phase 26 plan-progress row (2/13 plans complete).
- REQUIREMENTS.md marks TASK-02 + TASK-03 complete with traceability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-03): complete schema modules plan -- SUMMARY recording 4 HTTP + 5 SAQ schemas
Plan 26-03 ships 5 new Pydantic schema modules (4 HTTP-boundary + 1 SAQ-payload bundle)
with 56 unit tests, all green. mypy strict + ruff clean across all modules.
Pre-commit hooks pass on all files. 582 non-integration tests pass overall.
Two auto-fix deviations folded into GREEN commits (both Rule 1 β ruff lint
fixes to the plan's verbatim source): removed unused `# noqa: TC003` in
agent_identity.py; dropped `from __future__ import annotations` in
agent_tasks.py to avoid TC003 firing on runtime-needed `import uuid`.
Closes: Plan 26-03 of Phase 26 (TASK-02, TASK-03, DIST-03 β partial; full
completion deferred to phase-end verifier).
* chore(26-04): retire self-deleting type:ignore tripwires in agent_client
Plan 03 has merged, so the parallelization-debt `# type: ignore[import-not-found]`
comments on phase 26 schema imports are now unused. `warn_unused_ignores` flags
them as errors and blocks every commit on this branch. Removing per the original
comment intent ("removed once Plan 03 merges").
Rule 3 (blocking issue) deviation -- minimal, surgical cleanup required to
proceed with Plan 26-04.
* test(26-04): add failing tests for AgentTaskRouter (RED)
- 4 integration tests covering per-agent queue isolation, lazy cache
identity, close() drain, and enqueue_for_file delegation
- Uses real Redis via PHAZE_REDIS_URL env (default redis://localhost:6379/0)
- Marked @pytest.mark.integration per D-30; skips cleanly when Redis is down
- Module import currently fails (ModuleNotFoundError) -- RED confirmed
* test(26-06): add contract tests for PUT /agent/analysis (RED)
- 8 contract tests covering happy path, replay idempotence, partial-PUT
field-level LWW (CR-01 invariant), empty-body no-op, first-PUT-with-
empty-body row creation, 422 on extra fields (AUTH-01 spoof block),
401 missing auth, 403 unknown token.
- Mirrors test_agent_metadata.py smoke-app pattern with _seed_file FK
helper (AnalysisResult.file_id FKs files.id).
- Asserts mood/style dict -> summary string conversion at storage
boundary (per D-26 storage discretion area).
- Collection fails with ModuleNotFoundError until Task 2 ships the
router module (RED state confirmed).
Deviation (Rule 3 -- auto-fix blocking issue): removed four
`# type: ignore[import-not-found]` tripwires in services/agent_client.py.
These were intentional self-deleting markers placed by Plan 03's author;
now that Plan 03 has merged the schema modules they reference, mypy with
`warn_unused_ignores=true` correctly errors on the now-resolvable imports.
Removing them unblocks the pre-commit mypy gate for this and all
subsequent commits in Phase 26 Wave 3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-04): implement AgentTaskRouter with lazy per-agent Queue cache (GREEN)
Controller-side service that enqueues file-bound SAQ jobs onto the right
per-agent queue. Replaces the inline Queue.from_url + try/finally pattern
at agent_files.py:99-117 with a reusable service whose Queue instances are
cached per-agent (one Redis connection pool per agent, reused across enqueues).
API:
- AgentTaskRouter(redis_url) -- stores URL + initializes empty cache
- _queue_for(agent_id) -- lazy Queue.from_url with name=phaze-agent-<id> (D-18)
- enqueue_for_agent(*, agent_id, task_name, payload) -- enqueue via model_dump
- enqueue_for_file(*, file_record, task_name, payload) -- delegate via FileRecord.agent_id
- close() -- disconnect every cached Queue and clear cache (idempotent)
Decisions: D-19 (class shape), D-20 (lifespan-wired in Plan 12),
D-21 (replaces inline enqueue in agent_files.py in Plan 12),
D-30 (real-Redis integration tests), D-33 (mypy strict opt-in).
All 4 integration tests pass against a real Redis instance.
* fix(26-08): drop now-unused 'type: ignore[import-not-found]' tripwires in agent_client.py
Plan 26-02's PhazeAgentClient annotated 4 Plan 03 schema imports with
'# type: ignore[import-not-found]' parallelization debt, with a comment
explicitly declaring them a self-deleting tripwire that would fire once
Plan 03 schemas landed.
Plan 03 merged via 6ae8a49 (5 schema modules including agent_analysis,
agent_identity, agent_proposals, agent_tracklists). The four 'type: ignore'
comments are now unused -- mypy's 'warn_unused_ignores' flag turns this
into a hard failure (Rule 3 blocker preventing Plan 08 commits).
Removed all four ignore directives. Mypy now exits clean. No runtime change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(26-05): clean self-deleting type-ignore tripwires in agent_client (Rule 3)
Plan 26-02 (PhazeAgentClient) added `# type: ignore[import-not-found]`
to five schema imports as self-deleting tripwires, intended to fire
unused-ignore errors once Plan 26-03 (schemas) merged so the cleanup
would be obvious.
Plan 26-03 merged at 6ae8a49 but the cleanup did not happen, so every
subsequent commit fails the local mypy pre-commit hook with 4
`Unused "type: ignore" comment [unused-ignore]` errors -- blocking
Wave 3 plans 26-04..26-08.
Removing the ignores (and their stale explanatory comments) restores
mypy green and unblocks Plan 26-05 (this plan).
Rule 3 deviation: scope-adjacent to Plan 26-05's `files_modified`
but required to commit at all (pre-commit mypy is mandatory).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26-08): add failing tests for PATCH /api/internal/agent/proposals/{id}/state (RED)
11 contract tests for the joint Proposal+FileRecord state-transition endpoint
(D-28). Covers:
- APPROVED -> EXECUTED joint update (proposal_state + file_state=moved + current_path)
- APPROVED -> FAILED joint update (proposal_state + file_state=unchanged)
- Same-state PATCH idempotent no-op (EXECUTED -> EXECUTED returns 200)
- Illegal transitions (EXECUTED -> FAILED, PENDING -> EXECUTED -> 409)
- 404 on unknown proposal_id
- 422 on extra field (extra='forbid' from ProposalStatePatch)
- 422 on moved-without-current_path (_require_path_when_moved validator)
- 401 missing bearer, 403 unknown token
- Cross-agent 403 (W1 / T-26-08-S2): agent B cannot mutate agent A's proposal
Uses smoke-app pattern (mirrors test_agent_execution.py) so tests don't depend
on Plan 12 wiring the router into main.py. Confirms RED: ImportError because
phaze.routers.agent_proposals does not exist yet (implemented in Task 2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26-05): add failing contract tests for GET /whoami (RED)
- 4 contract tests: happy path (200 + AgentIdentity body),
missing header -> 401 (with WWW-Authenticate: Bearer per RFC 6750),
unknown token -> 403, revoked-mid-session -> 403 (AUTH-04).
- Uses Phase 25 per-router smoke-app pattern
(mirrors tests/test_routers/test_agent_metadata.py:30-38) so the
suite is independent of Plan 12's main.py wiring.
- Re-uses session + seed_test_agent fixtures from tests/conftest.py.
Currently RED: import of phaze.routers.agent_identity fails because
the router module has not been created yet (implemented in Task 2
of this plan).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(26-07): remove dead type-ignore on Plan 03 schema imports
The four ``# type: ignore[import-not-found]`` comments in agent_client.py
were placed by Plan 26-02 as a deliberate self-deleting tripwire (see
the inline comment) -- once Plan 26-03 lands the schema modules,
mypy's ``warn_unused_ignores`` flags the comments as unused and fails
the local hook. Plan 26-03 merged into the phase branch in Wave 2;
removing the ignores unblocks Wave 3 plans (including this one, 26-07)
from running their pre-commit suite.
Rule 3 scope-blocker fix: pre-existing mypy failure in a file not
authored by Plan 26-07. Surgical removal of dead comments only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26-07): add failing integration tests for POST /tracklists (RED)
- 7 integration tests covering happy path, idempotent replay, multi-version
promotion via new request_id, extra-field 422, too-many-tracks 422
(T-26-07-DoS), 401, 403
- Smoke-app fixture pattern + real-Redis fixture with scan_iter cleanup
- Tests fail RED until router lands (ImportError: phaze.routers.agent_tracklists)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-06): implement PUT /agent/analysis with idempotent upsert (GREEN)
Mirror of agent_metadata.py for AnalysisResult. Key behaviors:
- pg_insert + on_conflict_do_update on AnalysisResult.file_id (UQ from
models/analysis.py:18) for idempotent upsert.
- body.model_dump(exclude_unset=True) so only fields the caller set land
in the UPDATE SET clause (Phase 25 CR-01 field-level LWW invariant).
- Empty body -> on_conflict_do_nothing branch (avoids Postgres "empty
SET" syntax error; new rows still get an INSERT with all NULL fields).
- payload["id"] = uuid.uuid4() stamped explicitly because
AnalysisResult.id has a Python-only default that pg_insert bypasses.
- agent_id sourced from auth dep, never from body (AUTH-01).
- Depends(get_authenticated_agent) -> 401/403 surface honored.
Storage representation:
- mood/style: dict[str, float] -> top-3 "k=v,k=v" summary string via
_summarize_dict_to_string (bounded 50 chars to fit existing String(50)
columns; deterministic alphabetical tiebreak on equal scores).
- Overflow funnel (Rule 1 + Rule 3 -- plan-discretion area): wire-format
fields without a dedicated column (`danceability`, `energy`) are
merged into the existing `features` JSONB column rather than dropped,
preserving D-26's wire contract without an Alembic migration. Plan
task originally asserted `row.danceability == 0.8` directly; the
model has no such column, so RED-tests were updated to assert against
`row.features["danceability"]` to match the storage funnel.
Tests: 8/8 contract tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-08): implement PATCH /api/internal/agent/proposals/{id}/state (GREEN)
Joint Proposal + FileRecord state-transition endpoint per D-28. Mirrors the
agent_execution.py PATCH handler (Phase 25) for structural symmetry:
- Table-driven transitions via _PROPOSAL_TRANSITIONS (not monotonic ladder --
D-28 has only one allowed from-state, APPROVED -> {EXECUTED, FAILED})
- Idempotent same-state PATCH: returns 200 echoing current row state with NO
DB writes (canonical SAQ retry case for terminal states)
- Joint Proposal + FileRecord update in ONE session.commit() (Pitfall 6)
- 409 with detail 'illegal transition {cur} -> {new}' for any other transition
- 404 when proposal_id not found
- W1 / T-26-08-S2: cross-tenant guard via FileRecord.agent_id check; returns
403 BEFORE state-machine logic so a leaked proposal_id cannot be timing-probed
via 409 vs 403
- Auth gated by Depends(get_authenticated_agent) -- 401/403 inherited from
HTTPBearer + agent_auth.get_authenticated_agent
Also fixes a Rule 1 bug in test_agent_proposals.py: AsyncSession.expire_all()
is a sync method (not async); the original 'await session.expire_all()' raised
TypeError. Stripped 'await' from all three callsites.
All 11 contract tests pass:
- test_executed_joint_update
- test_failed_joint_update
- test_same_state_idempotent_no_op
- test_illegal_transition_409
- test_pending_to_executed_409
- test_proposal_not_found_404
- test_proposal_extra_field_422
- test_moved_without_current_path_422
- test_proposal_cross_agent_403 (W1)
- test_proposal_missing_auth_returns_401
- test_proposal_unknown_token_returns_403
Router NOT wired into main.py here -- that lands in Plan 26-12.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26-06): parametrized unit tests for _summarize_dict_to_string (W6)
Six test invocations exercising every documented edge case of the
helper that converts dict[str, float] -> "k=v,k=v,k=v" summary string
for mood/style storage in AnalysisResult's String(50) columns:
- Empty dict -> ""
- Single-key dict -> one entry, no comma
- 3-key dict -> top-3 sorted by score descending
- 10-key dict -> top-3 only (truncation contract)
- Identical scores -> alphabetical tiebreak (determinism invariant
pinned to the `(-score, key)` two-key sort)
- Long keys -> hard 50-char cap fires
The alphabetical-tiebreak case prevents regression to the older
`reverse=True` single-key sort that would tiebreak by insertion order.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-04): complete AgentTaskRouter plan -- SUMMARY + state + roadmap
- 26-04-SUMMARY.md recording the lazy-cache implementation choice
(plain dict over functools.cache / LRU), real-Redis integration test
fixture pattern, and the 1 deviation (Rule 3 unblocker for stale
type:ignore tripwires in agent_client.py)
- STATE.md advances Current Plan to 04 (complete); 4/26 plans of v4.0
done -> 19/26 plans overall (73%)
- ROADMAP.md flips 26-03 + 26-04 checkboxes (26-03 SUMMARY already on disk
from prior merge); progress 4/13 plans in phase 26
* feat(26-07): implement POST /api/internal/agent/tracklists with Redis idempotency (GREEN)
POST /api/internal/agent/tracklists -- idempotent atomic create of
Tracklist + new TracklistVersion + N TracklistTrack rows in a single
transaction. Three-path Stripe-style idempotency keyed on body.request_id:
1. Fast path: tracklist_resp:{request_id} cached JSON -> return without DB work
2. Concurrent-writer path: SET NX lost -> poll resp_key 10x50ms -> 409 on timeout
3. Owner path: SET NX won -> UPSERT Tracklist + version_number+1 + INSERT tracks
+ UPDATE latest_version_id pointer + commit + cache response
Key choices documented in 26-07-SUMMARY (to follow):
- No payload-hash check on cached replays (T-26-07-T accept, single-operator trust)
- max_length=2000 on tracks lives in schemas/agent_tracklists.py (Plan 26-03)
- Redis client pulled via `request.app.state.redis` (Plan 26-12 wires the lifespan)
Implements: D-27, TASK-03
Resolves RED: tests/test_routers/test_agent_tracklists.py (7 integration tests GREEN)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-05): implement GET /api/internal/agent/whoami router (GREEN)
Implements D-15..D-17 with the established Phase 25 router pattern:
- APIRouter(prefix="/api/internal/agent/whoami", tags=["agent-internal"])
- Single GET handler depending on get_authenticated_agent
(401 missing/malformed header, 403 unknown/revoked token)
- Returns AgentIdentity{agent_id, name, scan_roots, created_at}
projected directly from the auth dep's Agent row
Used at agent worker startup (Plan 10) to verify the bearer token
is valid AND the token-derived agent_id matches the operator-supplied
PHAZE_AGENT_QUEUE env var (anti-misconfiguration probe per
RESEARCH Pitfall 1), and by Phase 29's Agents admin reachability probe.
Plan 12 will wire this router into create_app(); this plan keeps the
router module + tests parallel-safe via the smoke-app fixture pattern.
Test-side adjustment (Rule 1 fix):
- Relaxed `assert parsed.tzinfo is not None` on the happy-path test:
TimestampMixin in src/phaze/models/base.py uses `Mapped[datetime]`
WITHOUT `DateTime(timezone=True)`, so the server-side timestamp is
naive UTC -- matching the project-wide convention asserted by
tests/test_routers/test_execution.py:70's `.replace(tzinfo=None)`.
The plan's verbatim test asserted a tzinfo guarantee that the
established ORM convention does not (and should not) provide.
Replaced with `assert isinstance(parsed, datetime)` to keep the
ISO-8601 round-trip check intact.
Verification:
uv run pytest tests/test_routers/test_agent_identity.py -x -q --no-cov # 4 pass
uv run mypy src/phaze/routers/agent_identity.py # clean
uv run ruff check src/phaze/routers/agent_identity.py # clean
coverage of agent_identity.py: 100.00% (9/9 lines)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-05): complete plan β SUMMARY + STATE/ROADMAP/REQUIREMENTS update
- SUMMARY records 2 task commits (RED + GREEN) + 1 Rule 3 deviation
commit (agent_client.py tripwire cleanup) + 1 Rule 1 deviation
(timezone-aware assertion relaxed to match naive-UTC convention).
- 100% coverage on src/phaze/routers/agent_identity.py (9/9 lines).
- 4 contract tests pass: 200 happy path, 401 missing header,
403 unknown token, 403 revoked-mid-session.
- deferred-items.md logs out-of-scope full-suite integration-test
flakiness (pre-existing on the inherited Phase 26 base).
- ROADMAP marks 26-05 + 26-03 complete (4/13 plans for Phase 26).
- REQUIREMENTS marks OPS-01 complete (TASK-02/TASK-03 already
completed by earlier plans).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-06): complete Plan 06 -- agent_analysis router + helper unit tests
- 26-06-SUMMARY.md documenting:
* Overflow funnel decision: D-26 wire fields without dedicated columns
(danceability, energy) land in `features` JSONB column (no migration).
* mood/style top-3 string summarization with deterministic
`(-score, key)` two-key tiebreak.
* Two auto-fix deviations: (1) removed self-deleting tripwires in
agent_client.py that fired as designed when Plan 03 merged in,
(2) updated test assertions to match the overflow funnel storage.
- STATE.md updated: Phase 26 Plan 06 complete, 18/26 plans done (69%),
+3 accumulated-context entries for the overflow-funnel pattern, the
two-key sort canonical pattern, and the tripwire-fire-on-merge note.
- ROADMAP.md: mark 26-03 (merged-in dependency) and 26-06 complete.
Verified: 14/14 tests pass (8 contract + 6 helper), mypy clean across
102 source files, ruff/ruff-format/all pre-commit hooks green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-07): complete POST /tracklists plan -- SUMMARY + STATE + ROADMAP
- 26-07-SUMMARY.md records: three-path idempotency design, discretion
decisions (no payload-hash check, accept T-26-07-T), test fixture
insights (PHAZE_REDIS_URL, scan_iter cleanup, decode_responses=True),
coverage (88.24%), deviations (chore commit + sqlalchemy.update fix)
- STATE.md: advance to Plan 07 complete (18/26 plans, 69%); record
Phase 26 P07 in metrics table (14m 31s); add 6 [Phase 26-07] decisions
- ROADMAP.md: tick the 26-07-PLAN.md checkbox
- deferred-items.md: log pre-existing test-isolation regression that
surfaces under full-suite test_routers runs (unrelated to Plan 07)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-08): complete agent_proposals router plan -- SUMMARY + STATE + ROADMAP
Plan 26-08 (Wave 3) complete in 14 min. Two TDD tasks (RED + GREEN) committed
atomically:
- e2e35e0 test(26-08): 11 failing contract tests
- 8c94069 feat(26-08): PATCH router implementation + test sync-method bug fix
Plus one Rule 3 blocker fix commit (03b3d28) that cleared now-unused
'type: ignore[import-not-found]' parallelization tripwires in agent_client.py
which were blocking the pre-commit mypy hook.
SUMMARY documents:
- _PROPOSAL_TRANSITIONS table for state-machine validation (D-28)
- W1 / T-26-08-S2 cross-tenant guard placement (BEFORE state-machine logic)
- Pitfall 6 invariant: single await session.commit() for joint mutation
- Idempotent same-state PATCH semantics (no DB writes on retry)
- Deferred-items.md: pre-existing test-fixture flakiness in conftest.py
(DEF-26-08-01) -- async_engine teardown silently fails on prior-test errors,
affects all agent tests (Phase 25 + Phase 26). Out of scope for Plan 08.
STATE.md updated: progress 18/26 (69%), last_activity=Phase 26 Plan 08 complete,
4 new decisions appended to Accumulated Context.
ROADMAP.md updated: 26-08-PLAN.md marked complete with 11 tests (incl. W1).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-09): create phaze.tasks.controller -- SAQ settings for control role
- Fileless-only subset of legacy worker.py (D-01..D-04)
- Functions list: generate_proposals, match_tracklist_to_discogs,
search_tracklist, scrape_and_store_tracklist
- Cron list: refresh_tracklists (1st of each month at 03:00)
- Module-level Queue.from_url(name="controller") for saq CLI consumption
- Startup hook stashes ctx["async_session"], ctx["task_engine"],
ctx["discogs_client"], ctx["proposal_service"], ctx["queue"] (W4)
- Startup banner logs role=control queue=controller (OPS-01)
- Imports ZERO file-bound modules (no functions/execution/fingerprint/
metadata_extraction/scan/pool, no services.fingerprint or agent_client)
- Legacy worker.py stays in place this plan (deleted by Plan 26-13)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26-09): controller startup banner test (W2 / OPS-01 coverage)
- Asserts controller.startup() emits "role=control queue=controller" log line
- Verifies W4 invariant: ctx["queue"] = queue is stashed by startup
- Monkeypatches create_async_engine/DiscogsographyClient/ProposalService
so the test runs without Postgres/HTTP connections (<1s)
- Underscore-prefixes unused lambda args to satisfy ruff ARG005
(Rule 3 deviation -- plan body had bare *a/**kw which trips ruff;
semantically equivalent fix)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-09): complete controller SAQ settings module plan
SUMMARY records:
- 2 commits: 555a718 (feat controller.py), 8cffb47 (test banner)
- 1 deviation: ARG005 lambda-arg fix in test (mechanical, no semantic change)
- Plan-level success criteria all green: mypy, ruff, format, pre-commit, smoke
- Banner test: 1 passed in 1.07s
- Acceptance grep matrix: all 12 checks pass
Closes Plan 26-09 (Wave 4). Plan 26-10 will mirror this module under
phaze.tasks.agent_worker for the file-bound role.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(26-11): rewrite process_file + extract_file_metadata over HTTP (Task 1)
- Drop all phaze.database / phaze.models / sqlalchemy imports from both tasks
- Validate kwargs via ProcessFilePayload / ExtractMetadataPayload (extra='forbid')
- Call PhazeAgentClient.put_analysis / put_metadata via ctx["api_client"]
- Add _features_to_mood_dict + _features_to_style_dict helpers to convert
essentia's str outputs to D-26 wire-format dict[str, float] for AnalysisWritePayload
- Rewrite test_functions.py + test_metadata_extraction.py to mock api_client
instead of async_session (no new pytest.skip markers)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(26-11): rewrite fingerprint_file + scan_live_set over HTTP (Task 2)
- Drop all phaze.database / phaze.models / sqlalchemy imports from both tasks
- Validate kwargs via FingerprintFilePayload / ScanLiveSetPayload (extra='forbid')
- fingerprint_file calls api.put_fingerprint(file_id, engine, body) per engine
- scan_live_set uses stable uuid5(NAMESPACE_URL, "phaze-scan-{file_id}") request_id
so SAQ retries collapse via server-side Redis idempotency cache
- scan_live_set drops the in-process FileMetadata join (W5 Option (b)):
artist/title now None on fingerprint tracks; documented as a known v3.0 UI
regression for future controller-side enrichment (Phase 27/28)
- Refactor src/phaze/services/fingerprint.py: move sqlalchemy / phaze.models
imports inside get_fingerprint_progress() so the agent worker can import the
orchestrator without pulling in the Postgres driver (Rule 3 -- structural
fix required to satisfy DIST-03 import boundary)
- Rewrite test_fingerprint.py + test_scan.py to mock api_client + orchestrator
(no new pytest.skip markers; preserves coverage)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-11): rewrite execute_approved_batch over HTTP (B2 Option A) (Task 3)
- Implement full per-proposal copy + verify + delete + HTTP state reporting
(no NotImplementedError stub -- B2 Option A per CONTEXT.md revision iter 2)
- Per-proposal lifecycle: POST execution-log (in_progress) -> file op ->
PATCH execution-log (completed|failed) -> PATCH proposal-state (executed|failed)
- Failure isolation: bad file (IO error, path traversal, sha256 mismatch) gets
state=failed; siblings succeed; batch status='completed_with_errors' iff any failed
- Path-traversal guard (T-26-11-S1): _resolve_and_check_containment rejects
proposed_path that escapes scan_roots via Path.resolve() + relative_to() check
- Streaming sha256 verify (avoid loading huge files into memory)
- Refuse to execute when scan_roots is empty (mis-deployment fail-fast)
Schema-extraction (Rule 3 fix for D-03 import boundary):
- Move ExecutionStatus enum to new phaze.enums.execution module (DB-free)
- src/phaze/models/execution.py re-exports for backward compatibility
- src/phaze/schemas/agent_execution.py now imports from phaze.enums (clean boundary)
- src/phaze/tasks/execution.py imports from phaze.enums
Tests:
- New tests/test_tasks/test_execute_approved_batch.py: 5 contract tests (happy, partial,
path-escape, sha256-mismatch, requires_scan_roots)
- Rewrite tests/test_tasks/test_execution.py to 2 smoke tests covering aggregate
status='completed' and status='completed_with_errors'
Verified: import phaze.tasks.execution does NOT load phaze.database / sqlalchemy /
phaze.models -- the D-03 import boundary holds for all 5 rewritten task modules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-11): complete task-body HTTP rewrite plan
- Create 26-11-SUMMARY.md documenting the 5 rewritten task bodies, the B2
Option A implementation of execute_approved_batch, the ExecutionStatus
enum extraction to phaze.enums (Rule 3 fix), the function-local DB import
refactor in services/fingerprint.py, and the v3.0 UI regression note
for scan_live_set artist/title resolution (W5 Option (b))
- Mark ROADMAP.md Phase 26 Plan 11 entry as COMPLETE
- Update STATE.md Current Position, Decisions, Quick Tasks Completed,
Session Continuity
Plan 11 is the mechanical core of Phase 26 -- the agent worker can now
boot without Postgres and run the full file-bound pipeline once Plans 10
(agent_worker SAQ settings) and 12 (main.py wiring) land.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-12): wire 4 new agent routers + AgentTaskRouter + Redis into FastAPI lifespan
- Add include_router calls for agent_identity, agent_analysis, agent_tracklists,
agent_proposals (Phase 26 D-15, D-26, D-27, D-28). All 10 /api/internal/agent/*
endpoints are now reachable from the production create_app() factory.
- Wire AgentTaskRouter at app.state.task_router (D-20); used by agent_files.py's
auto-enqueue path in Task 2.
- Wire shared async Redis client at app.state.redis with decode_responses=True
for the tracklists idempotency cache (D-27); agent_tracklists.py reads it via
request.app.state.redis.
- Lifespan shutdown closes both new resources before the existing default queue
(reverse construction order).
Verifies: 10 unique agent route paths enumerated from create_app(); all 31 Phase 25
router tests still green; mypy + ruff clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26-10): add subprocess import-boundary test for agent_worker (D-25)
- Launches a clean Python subprocess to import phaze.tasks.agent_worker.
- Asserts phaze.database, phaze.tasks.session, sqlalchemy.ext.asyncio
are absent from sys.modules.
- Runs on every CI build (no marks, no skips) β Phase 26 structural invariant.
- Currently RED: ModuleNotFoundError until Task 2 creates agent_worker.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(26-10): create phaze.tasks.agent_worker β SAQ settings for agent role (D-01..D-04, D-16)
Companion to phaze.tasks.controller (Plan 09). Boots the file-server role
under PHAZE_ROLE=agent. Six-step startup:
1. Models check (essentia .pb files).
2. Construct PhazeAgentClient(base_url, token, timeout=30.0).
3. /whoami probe with bounded exponential backoff (1sβ32s, β€63s total).
4. Queue-name mismatch guard β token-derived agent_id MUST match
operator-supplied PHAZE_AGENT_QUEUE env (anti-misconfig probe).
5. FingerprintOrchestrator(AudfprintAdapter + PanakoAdapter) stashed at
ctx["fingerprint_orchestrator"] (B1 β Plan 11 readers).
6. CPU-bound essentia process pool.
functions list contains exactly the 5 file-bound tasks:
process_file, extract_file_metadata, fingerprint_file, scan_live_set,
execute_approved_batch.
Module-level Queue.from_url(redis_url, name=PHAZE_AGENT_QUEUE) β env-driven
at import time per RESEARCH Pitfall 7. Module exits non-zero if
PHAZE_AGENT_QUEUE is unset.
D-13 invariant: bearer never logged. Banner emits a 12-char preview
(`phaze_agent_...`) under a non-secret format key (`auth_id_prefix=`).
The variable is named `token_preview` for grep-ability of the D-13
intent across the codebase.
D-25 import-boundary test (tests/test_task_split.py from Task 1) now
passes β no phaze.database / phaze.tasks.session / sqlalchemy.ext.asyncio
in sys.modules after `import phaze.tasks.agent_worker`. Structural
invariant of Phase 26 is enforced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(26-12): swap inline Queue.from_url for AgentTaskRouter in agent_files.upsert_files
Phase 26 D-20 / D-21 -- the auto-enqueue path in agent_files.upsert_files no
longer constructs a SAQ Queue per request. Instead it reads the lifespan-wired
AgentTaskRouter at request.app.state.task_router and calls
enqueue_for_agent(agent_id=..., task_name="extract_file_metadata", payload=...).
- Handler signature gains `request: Request` (positional, before `body`).
- `from saq import Queue` import removed; `from fastapi import Request` and
`from phaze.schemas.agent_tasks import ExtractMetadataPayload` added.
- UPSERT RETURNING extended with `FileRecord.original_path` so the payload
can be built without a re-query.
- The handler no longer owns the Queue lifecycle -- close() runs once in the
FastAPI lifespan shutdown via app.state.task_router.close().
Test fixture (tests/test_routers/test_agent_files.py) migrates from
`patch("phaze.routers.agent_files.Queue")` to installing an AsyncMock at
`app.state.task_router` on the smoke-app. Assertions now inspect
`mock_router.enqueue_for_agent.await_args_list` and verify the
ExtractMetadataPayload's typed fields (file_id, original_path, file_type,
agent_id). All 9 existing tests continue to pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26-10): add agent_worker startup-banner test (W2 / D-13 / OPS-01)
- Asserts the startup banner contains module name, role=agent,
agent_id=<value>, and the 12-char token preview `phaze_agent_...`.
- Asserts the full bearer-token secret bytes never appear in the log
output (D-13 invariant).
- Heavy constructors (PhazeAgentClient, process pool, fingerprint
adapters/orchestrator, models check) are monkeypatched so the test
runs in-memory with no Postgres/Redis/.pb files required.
- Mirrors tests/test_tasks/test_controller_startup_banner.py (Plan 09)
with agent-specific assertions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-12): complete FastAPI wiring plan -- SUMMARY + STATE + ROADMAP + REQUIREMENTS
Plan 26-12 wired the four Phase 26 agent routers (whoami, analysis, tracklists,
proposals) into create_app(), installed AgentTaskRouter + async Redis client at
app.state.task_router / app.state.redis in the FastAPI lifespan, and refactored
agent_files.upsert_files off the inline Queue.from_url pattern. Marks DIST-03
requirement complete.
- SUMMARY: 26-12-SUMMARY.md (created)
- STATE: current_plan advanced 11 -> 12; progress 92% (24/26); metric recorded
- ROADMAP: phase 26 progress 11/13 -> in progress (Plan 13 outstanding)
- REQUIREMENTS: DIST-03 marked complete
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(26-10): complete HTTP-backed agent worker plan
- Add 26-10-SUMMARY.md documenting agent_worker.py + import-boundary test (D-25)
+ banner test (D-13/OPS-01).
- Advance STATE.md plan counter (10 of 13 done; in-progress).
- Mark TASK-01, DIST-03 complete in REQUIREMENTS.md (OPS-01 already marked).
- Update ROADMAP.md plan progress (11/13 summaries).
- Record three key decisions: import-boundary structural invariant,
format-key rename to avoid secret-detector false-positive, and
/whoami retry budget + queue-name mismatch guard semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(26-13): delete legacy tasks.worker + tasks.session; wire compose to controller.settings
Closes Phase 26 D-04 + D-06 + D-08:
- DELETED src/phaze/tasks/worker.py (115 lines) -- replaced by phaze.tasks.controller
(fileless, control role) and phaze.tasks.agent_worker (file-bound, agent role).
- DELETED src/phaze/tasks/session.py (5 lines) -- the legacy v1.0 session-helper stub;
both new SAQ settings modules build their own session pool in their startup hooks.
- Updated docker-compose.yml worker service:
command: uv run saq phaze.tasks.controller.settings (was: phaze.tasks.worker.settings)
environment: + PHAZE_ROLE=control
depends_on: dropped audfprint + panako (controller is fileless per D-04)
- Updated test references that imported from phaze.tasks.worker / phaze.tasks.session:
- tests/test_tasks/test_worker.py DELETED (covered by test_controller_startup_banner.py)
- tests/test_tasks/test_session.py DELETED (covered by test_task_split.py D-25 invariant)
- tests/test_tasks/test_pool.py stripped worker.startup/shutdown tests (3); kept
pool-helper tests (2) which still exercise
phaze.tasks.pool (used by agent_worker).
- tests/test_tasks/test_proposal.py retargeted test_worker_* -> test_controller_*
- tests/test_tasks/test_tracklist.py retargeted test_worker_* -> test_controller_*
- tests/test_phase04_gaps.py models-dir checks retargeted to agent_worker
(the new owner per D-04); docker-compose-command
assertion now expects controller.settings.
- Logged D-3 in deferred-items.md: agent_task_router + agent_tracklists tests need a live
Redis; pre-existing flakiness independent of Plan 26-13.
Acceptance: `uv run pytest tests/test_task_split.py tests/test_tasks/ tests/test_phase04_gaps.py
-x --no-cov` => 62 passed; `uv run mypy src/` clean; `uv run ruff check .` clean;
`docker compose config -q` exits 0.
* docs(v4.0): replace hostname-leaked lux_worker with role-neutral controller
Per Phase 26 D-02 / D-33, replaces every forward-looking reference to
`phaze.tasks.lux_worker` (the original roadmap name that leaked the
application-server hostname "lux") with `phaze.tasks.controller`. The new
name pairs cleanly with `PHAZE_ROLE=control` and reads naturally without
betraying physical topology.
Sweep targets defined by D-33 (5 files):
- .planning/PROJECT.md β milestone v4.0 task-code-reorg bullet (1 hit)
- .planning/ROADMAP.md β Phase 26 plan-13 line + Phase 29 success-criterion #1
(2 hits)
- .planning/REQUIREMENTS.md β clean (already used `controller` / `agent_worker`)
- .planning/STATE.md β clean (no historical lux_worker references)
- .planning/phases/25-internal-agent-http-api-bearer-auth/25-CONTEXT.md
β clean
Historical records preserved (intentionally untouched):
- .planning/phases/26-β¦/26-13-PLAN.md
- .planning/phases/26-β¦/26-10-SUMMARY.md
- .planning/phases/26-β¦/26-CONTEXT.md
- .planning/phases/26-β¦/26-DISCUSSION-LOG.md
These document what was planned and decided at the time and are part of the
audit trail; per Plan 26-13 Task 2 explicit guidance, historical SUMMARY /
CONTEXT / DISCUSSION-LOG files are not rewritten.
Verification:
$ grep -rn 'lux_worker' .planning/ROADMAP.md .planning/REQUIREMENTS.md \
.planning/STATE.md .planning/PROJECT.md \
.planning/phases/25-internal-agent-http-api-bearer-auth/25-CONTEXT.md
# β 0 matches
* docs(26-13): complete closing-housekeeping plan -- SUMMARY + STATE + ROADMAP
Plan 26-13 final metadata commit:
- Adds .planning/phases/26-β¦/26-13-SUMMARY.md (closing housekeeping plan summary;
deletions + docker-compose + doc sweep details; deviations + threat-flag scan)
- STATE.md advanced to Plan 13/13 (100% v4.0-phase-26 progress); decision recorded
- ROADMAP.md updated by roadmap.update-plan-progress: Phase 26 marked complete
(13/13 plans), aligned with the SUMMARY artefacts on disk
Phase 26 (Task Code Reorg & HTTP-Backed Agent Worker) is ready for verifier
sweep + merge. The next milestone-v4.0 phase (Phase 27: Watcher Service &
User-Initiated Scan) can plan against a clean controller/agent_worker split.
* docs(26): finalize phase 26 β verification PASS, Nyquist flags 3 coverage gaps
Verification: All 5 success criteria PASS via goal-backward analysis (task
split, role-driven startup, HTTP-only file-bound tasks, per-agent queue
isolation, self-contained payloads). 26-VERIFICATION.md records evidence.
Nyquist audit: NEEDS GAPS FILLED β 26-NYQUIST.md identifies:
- GAP-1 (critical): tests/test_config_role_split.py missing β
AgentSettings validators + get_settings() PHAZE_ROLE dispatch untested
- GAP-2 (high): tests/test_services/test_agent_client_endpoints.py missing β
5 client methods (create_tracklist, patch_proposal_state, execution_log
POST+PATCH, heartbeat) have no respx tests
- GAP-3 (medium): bearer-token-never-logged invariant has no caplog
assertion in PhazeAgentClient WARNING path
Recommendation: address via /gsd:validate-phase before shipping. Pre-existing
infrastructure issues (D-1 DB fixture flakiness, D-2 enum-type race, D-3
Redis daemon requirement) are logged in deferred-items.md and out of scope.
* test(26-val): add config role-split unit tests (GAP-1)
Cover 7 behaviors of get_settings() dispatch and AgentSettings fail-fast
validators: role dispatch to AgentSettings/ControlSettings, missing
PHAZE_AGENT_API_URL/TOKEN/SCAN_ROOTS each raise, and comma-split produces
correct list. No DB or Redis required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(26-val): add PhazeAgentClient per-method respx tests (GAP-2)
Five respx happy-path tests covering Phase-26-new methods: create_tracklist,
patch_proposal_state, post_execution_log, patch_execution_log, and heartbeat.
Verifies URL construction, payload serialization (including exclude_unset=True
on PATCH methods), and response model type for each endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(26-val): assert bearer token absent from WARNING logs (GAP-3)
Adds one caplog test to test_agent_client.py that mocks a 500 response,
captures WARNING-level logs from PhazeAgentClient._request(), and asserts
the token string does not appear β enforcing the D-13 invariant on the
HTTP-client warning path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(26-val): cover original_path escape in execute_approved_batch (GAP-4)
Adds one test where original_path="/etc/shadow" (outside scan_root) while
proposed_path is inside the scan root. Verifies that _resolve_and_check_containment
rejects the operation (error_count==1) and leaves the proposed destination
uncreated β confirming the guard covers both paths, not just proposed_path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(26-val): verify queue/token mismatch raises at agent_worker startup (GAP-5)
Adds a second test to test_agent_startup_banner.py where PHAZE_AGENT_QUEUE
is set to 'phaze-agent-wrong-id' while whoami returns agent_id='correct-id'.
Asserts startup() raises RuntimeError matching 'queue/token mismatch',
exercising the Pitfall 1 anti-misconfiguration guard at agent_worker.py:133-142.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(26-val): record Nyquist validation audit β 5/5 gaps resolved
Audit trail appended to 26-VALIDATION.md documenting the 5 gap closures
(GAP-1 critical through GAP-5 low) added in commits ac6e3b0..9179907.
15 new test assertions across 2 new files (test_config_role_split.py,
test_agent_client_endpoints.py) + 3 existing-file edits.
Phase 26 is now nyquist_compliant. All DIST-03, TASK-01/02/03, OPS-01
requirements have automated verification.
* docs(26): ship phase 26 β PR #57
* ci: add Redis sidecar to tests workflow (D-30)
Phase 26 introduced 11 integration tests (test_agent_task_router.py,
test_agent_tracklists.py) that require live Redis at PHAZE_REDIS_URL.
SAQ Queue.from_url is not compatible with fakeredis at saq>=0.26.3, so
these tests have no fakeredis fallback and were failing in CI.
Adds redis:7-alpine service container and PHAZE_REDIS_URL env, mirroring
the existing postgres service pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(26): cover Phase 26 patch coverage gaps (94.92% β 96.36%)
Codecov flagged 68 lines missing in the Phase 26 patch. Adds 19 targeted tests
across 8 files; per-file patch coverage rises sharply:
- agent_worker.py: 68.29% β 97.56% (whoami retry exhaustion, role mismatch,
shutdown cleanup, module-import RuntimeError on missing PHAZE_AGENT_QUEUE)
- agent_client.py: 86.41% β 97.53% (upsert_files / put_metadata /
put_fingerprint happy paths)
- execution.py: 89.74% β 100% (4 best-effort log/PATCH failure paths)
- controller.py: 78.12% β 100% (shutdown disposes engine + closes client)
- agent_tracklists.py: 88.24% β 100% (409 concurrent-writer poll exhaustion)
- functions.py: 88.46% β 100% (malformed mood/style prediction skips)
- agent_files.py: 78.57% β 94.12% (non-music skip + enqueue failure swallow)
Two log-content assertions replaced with mock-call assertions because caplog
record propagation is fragile when other tests in the suite reconfigure root
logger handlers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the docker-images group with 1 update in the / directory: python.
Updates
pythonfrom 3.13-slim to 3.14-slim