Phase 28: Distributed Execution Dispatch#62
Merged
SimplicityGuy merged 41 commits intoMay 16, 2026
Conversation
Lock the visual + interaction contract for the Phase 28 frontend deliverables (progress card rework, per-agent rollup table, cross-FS fingerprint notice) onto the project's existing design system so the planner + executor consume one prescriptive spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦ty tests Wave 0 RED gate for Phase 28: create the eight test files Nyquist sampling needs to resolve test IDs without ModuleNotFoundError, plus the two new directories later waves depend on (tests/test_template_helpers/, src/phaze/templates/_partials/). - 7 module-level pytest.skip stubs citing the implementing plan (28-02..28-06) - tests/test_services/test_fingerprint_locality.py FULLY IMPLEMENTED (28-V-22 reject + 28-V-23 accept). Two reject tests currently FAIL because the BaseSettings validator doesn't yet exist β GREEN gate in the next commit. - tests/test_template_helpers/__init__.py + .gitkeep anchor new dirs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦ex field
Wave 0 GREEN gate for Phase 28 β implements D-12 (TASK-04 fingerprint URL
locality enforcement) and D-10 (ExecuteApprovedBatchPayload.sub_batch_index).
D-12 / TASK-04: BaseSettings now carries a `@field_validator("audfprint_url",
"panako_url")` that rejects any host not in the allow-list
`{localhost, 127.0.0.1, audfprint, panako}`. Both ControlSettings and
AgentSettings inherit the guard at construction time. A forged
PHAZE_AUDFPRINT_URL or PHAZE_PANAKO_URL pointing at an external host now
raises ValidationError BEFORE the app boots β closes T-28-01-S / T-28-01-I.
Error message cites XAGENT-01 (the deferred cross-fs requirement) so
operators reading the traceback see why their config was rejected.
D-10: ExecuteApprovedBatchPayload gains `sub_batch_index: int = 0` (0-based;
default preserves legacy callers). Enables Phase 28's per-agent group
chunking (Plan 28-04) where groups >500 proposals split into N sub-jobs
under the same parent batch_id, each carrying its 0-based index for
aggregator bookkeeping.
Verification:
- tests/test_services/test_fingerprint_locality.py: 6/6 PASS
(28-V-22 + 28-V-23: 2 reject + 4 accept).
- pre-commit (ruff/ruff-format/bandit/mypy) green on both touched files.
- Wave 0 stubs from previous commit still SKIP cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace Wave 0 pytest.skip stub with 13 tests against
src/phaze/services/execution_dispatch.py (does not yet exist).
- Covers 28-V-01 (groups_by_agent_id), 28-V-02 (revoked_agent_filtered_with_count),
and 28-V-03 (1000_proposals_split_into_2_chunks).
- Uses real PostgreSQL via the existing session fixture; unique
(agent_id, original_path) pairs avoid the uq_files partial-UQ collision.
- Includes parametrized chunk-math coverage for n in {0,1,499,500,501,999,1000,1500}.
β¦ests Replace the three module-level pytest.skip Wave 0 stubs with the full test suite enumerated in 28-V-10..28-V-17 + 28-V-25: - tests/test_schemas/test_agent_exec_batches.py: 16 unit tests covering the D-06 cross-field validator (failed_at_step iff terminal_step == "failed"), extra="forbid" enforcement, Literal narrowing, and the sub_batch_terminal default-False invariant. - tests/test_routers/test_agent_exec_batches.py: 17 contract tests covering the D-17 4-stage guard order (401 -> cross-tenant 403 BEFORE state read -> 404 -> non-participating 403 -> idempotency dedup), the D-07 counter-math rules (all 4 terminal_step branches x 3 failed_at_step paths), and the sub_batch_terminal status-promotion logic (complete / complete_with_errors / running unchanged). Includes a pure unit test for _compute_increments. - tests/test_services/test_agent_client_exec_batch_progress.py: 7 respx tests covering the URL contract, body serialization, 4xx-no-retry + 5xx-3x-retry tenacity policy inherited from PhazeAgentClient._request. All three modules currently fail with ModuleNotFoundError because the production modules (phaze.schemas.agent_exec_batches, phaze.routers.agent_exec_batches, PhazeAgentClient.post_exec_batch_progress) do not yet exist -- this is the TDD RED commit. GREEN lands in the next commit. Phase 28 D-05 / D-06 / D-07 / D-15 / D-17. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦s (GREEN)
- New src/phaze/services/execution_dispatch.py exports three helpers:
* get_approved_proposals_grouped_by_agent(session) -> dict[agent_id, list[ExecuteBatchProposalItem]]
SELECT + JOIN FileRecord JOIN Agent, WHERE status==APPROVED AND
Agent.revoked_at IS NULL; ORDER BY file.agent_id, proposal.created_at
for deterministic chunk boundaries; always populates
ExecuteBatchProposalItem.sha256_hash from FileRecord.sha256_hash
(RESEARCH L1).
* count_revoked_skipped_proposals(session) -> int
Companion counter that returns N for the controller banner copy
'Agent X revoked; N proposals skipped' (D-09 step 2).
* chunk_proposals(items, size=500) -> list[list[ExecuteBatchProposalItem]]
Pure list-slicing; returns [] for empty input and ceil(N/size) chunks
otherwise. _CHUNK_SIZE constant matches ExecuteApprovedBatchPayload
Field(max_length=500).
- Implements D-09 steps 1-3 from CONTEXT.md. The controller dispatch
rewrite (Plan 28-04) calls these to convert APPROVED rows into per-agent
per-chunk SAQ payloads via AgentTaskRouter.enqueue_for_agent.
- 28-V-01 (test_groups_by_agent_id), 28-V-02
(test_revoked_agent_filtered_with_count), 28-V-03
(test_1000_proposals_split_into_2_chunks) are now GREEN along with
17 additional tests covering edge cases.
β¦ method
Implements the Phase 28 D-05 / D-06 / D-07 / D-15 / D-17 contract end-to-end
as a single coupled change set:
PART A β `src/phaze/schemas/agent_exec_batches.py` (NEW)
`ExecBatchProgressPayload` Pydantic schema with `extra="forbid"` and a
`@model_validator(mode="after")` enforcing the D-06 cross-field invariant
(failed_at_step is required iff terminal_step == "failed").
PART B β `src/phaze/routers/agent_exec_batches.py` (NEW)
`POST /api/internal/agent/exec-batches/{batch_id}/progress` handler with
the D-17 4-stage guard (cross-tenant 403 BEFORE state read -> 404 batch
unknown -> 403 non-participating agent -> SET NX EX idempotency dedup),
D-07 counter math via the pure `_compute_increments` helper, pipelined
HINCRBY for one Redis round-trip, and the sub_batch_terminal-driven
promotion of `status` to `"complete"` / `"complete_with_errors"` when
`subjobs_completed == subjobs_expected`. The router is the SINGLE
mutation point for the `exec:{batch_id}` Redis hash (D-02) β agents
never write Redis directly.
PART C β `src/phaze/services/agent_client.py` (MODIFIED)
`PhazeAgentClient.post_exec_batch_progress(batch_id, payload)` method.
Funnels through the existing `_request` tenacity policy (D-11) so it
inherits the 4xx-no-retry / 5xx-with-retry / AgentApiError hierarchy
for free. Returns None (no response body).
PART D β `src/phaze/main.py` (MODIFIED)
Registers `agent_exec_batches.router` in `create_app()` alongside the
existing Phase 25-27 agent-internal routers.
Test scaffolding (Wave 0 stubs from Plan 28-01) is replaced with the full
suite β 41 tests now GREEN. Targets validation IDs 28-V-10..28-V-17 + 28-V-25.
Deviation: tests/test_routers/test_agent_exec_batches.py adds an autouse
fixture honouring `PHAZE_TEST_DATABASE_URL_28_02` so this plan's pytest
can use a worktree-dedicated `phaze_test_28_02` database when running
concurrently with Plan 28-03's pytest on the default `phaze_test`
database. Rule 3 blocker fix β no override touches the shared
`tests/conftest.py`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Plan 28-03 ships src/phaze/services/execution_dispatch.py with three exports (get_approved_proposals_grouped_by_agent, count_revoked_skipped_proposals, chunk_proposals) and replaces the Wave 0 stub at tests/test_services/test_execution_dispatch_grouping.py with 20 tests. - 28-V-01, 28-V-02, 28-V-03 GREEN. - TDD gate sequence honoured (RED commit e17c74c + GREEN commit 0dd94e8). - Pre-commit (ruff/ruff-format/bandit/mypy) green on both touched files.
Captures the Phase 28 D-05/D-06/D-07/D-15/D-17 contract delivery: - Endpoint URL + auth contract + 4-stage handler ordering. - ExecBatchProgressPayload schema fields + cross-field invariant. - D-07 counter-math invariant table (downstream Plan 28-05 contract). - 28-V-10..28-V-17 + 28-V-25 marked GREEN. - TDD gate compliance (RED ac0052b -> GREEN 3e012e0). - Two Rule-3 blocker deviations documented (redis-py mypy typing, parallel-worktree DB collision). - Self-check: all 7 files + 2 commit hashes verified present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦meta UUID lift Replaces the Wave 0 stub with the full RED-phase test surface for Plan 28-05: - test_success_emits_one_deleted_progress_post (28-V-06) - test_failure_emits_failed_progress_post_with_failed_at_step (28-V-07) - test_sha256_mismatch_maps_to_failed_at_verify - test_delete_failure_maps_to_failed_at_delete - test_sub_batch_terminal_set_on_last_item_only (28-V-08) - test_progress_post_failure_logs_warning_but_does_not_raise (D-16) - test_uuids_persisted_in_job_meta_on_first_run (L6/L22 + D-15) - test_uuids_reused_from_job_meta_on_retry (L6/L22 + D-15) - test_error_message_uses_step_reason_prefix (D-01) - test_execution_log_and_progress_use_distinct_uuids - test_legacy_ctx_without_job_does_not_break (backward-compat) - test_correct_sha256_still_succeeds (sanity) Tests fail today because tasks/execution.py does not yet call api.post_exec_batch_progress, does not persist UUIDs in ctx['job'].meta, and does not classify failure step. The GREEN commit lands those changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦ template partials (RED)
Wires the agent-side counterpart to Plan 28-02's progress endpoint:
- Adds one ``api.post_exec_batch_progress`` call per proposal at terminal
state (D-03). Success path posts ``terminal_step="deleted"``; failure path
posts ``terminal_step="failed"`` with ``failed_at_step`` derived from a
tracked ``current_step`` (copy/verify/delete) variable via the new
``_classify_failure_step`` helper (D-07 + RESEARCH L9).
- Sets ``sub_batch_terminal=True`` ONLY on the last item of the sub-batch so
the controller can detect ``subjobs_completed == subjobs_expected`` and
promote the batch status to ``complete`` / ``complete_with_errors`` (D-07).
- Persists BOTH ``execution_log_id`` AND ``progress_request_id`` per-proposal
UUIDs in ``ctx['job'].meta`` via ``await ctx['job'].update(meta=...)``. On
SAQ retry, the existing UUIDs are reloaded from meta -- ExecutionLog INSERT
dedupes via Phase 25 ON CONFLICT DO NOTHING, progress POST dedupes via Plan
28-02 ``SET NX EX 3600``. This closes the L6/L22 audit-row duplication bug
and delivers D-15. The meta-key convention is ``log_id:{proposal_id}`` /
``req_id:{proposal_id}`` (string-valued for SAQ JSON serialization).
- Reformats failed ``ExecutionLog.error_message`` as ``"<step>: <reason>"``
per the D-01 contract -- previously a raw ``str(exc)[:500]``.
- Progress POST failures after tenacity retries log WARNING and do NOT raise
(D-16) -- file ops have already been committed via ``patch_proposal_state``.
- Defensive fallback: legacy callers that pass ``ctx`` without a ``"job"``
key (the existing Phase 26 in-memory test fixtures) still execute with
fresh per-call UUIDs and a DEBUG log entry -- preserves regression coverage
from ``tests/test_tasks/test_execute_approved_batch.py``.
28-V-06, 28-V-07, 28-V-08 are GREEN; 28-V-09 regression (10 existing tests)
remains GREEN. 12 new tests + 10 regression tests = 22 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦meta UUID lift Records the RED/GREEN/REFACTOR sequence (commits 9cdc782 + a67b00a), the 4-transition current_step state machine, the ctx['job'].meta key convention (log_id:{proposal_id} / req_id:{proposal_id}), the SAQ retry-stable UUID lifecycle that closes L6/L22 + delivers D-15, and the upfront-meta-init deviation from the per-proposal incremental variant in the RESEARCH skeleton. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦SSE generator + add agents_table partial (GREEN)
Phase 28 D-09 + D-11 controller dispatch:
- start_execution now SELECT-groups approved proposals by FileRecord.agent_id
(via services/execution_dispatch.py from Plan 28-03), chunks each group at
500, seeds the exec:{batch_id} Redis hash (D-04 schema -- total, completed,
failed, copied, verified, deleted, subjobs_completed, subjobs_expected,
status, started_at, dispatch_summary JSON, agent:<id>:total/completed/failed
per-agent rollups), and enqueues one ExecuteApprovedBatchPayload sub-job per
(agent, chunk) via AgentTaskRouter.enqueue_for_agent. HSET + EXPIRE wrapped
in redis.pipeline(transaction=True) for atomicity (RESEARCH Pitfall 4).
- INFO log line "dispatch batch_id=<uuid> total=<n> n_agents=<m>
subjobs_expected=<k>" per D-11.
- Uses request.app.state.redis (decode_responses=True), NOT queue.redis.
Phase 28 D-04 + D-11 SSE generator:
- execution_progress switches reader from queue.redis to app.state.redis so
HGETALL returns str instead of bytes -- no more decode comprehension.
- Emits dispatch_summary on first connect ONLY (tracked via first_connect bool),
progress every tick, agents_table every tick.
- Closes on status in {"complete", "complete_with_errors"} (widened from the
Phase 25 "complete"-only check).
- Renders all three event payloads via _render_partial() helper that funnels
through Jinja2Templates.TemplateResponse(...).body.decode() -- avoids reaching
into templates.env directly so Semgrep XSS lint stays green.
UI templates (UI-SPEC C1 + C2 + C4):
- progress.html (REWRITE): outer sse-connect card with conditional revoked-
agents banner (orange surface, role="alert", pluralized copy per UI-SPEC),
dispatch_summary swap slot, aggregate counter row (TOTAL/COMPLETED/FAILED
with text-red-600 on FAILED when >0), agents_table inclusion, dual sse-close
for complete + complete_with_errors.
- agents_table.html (NEW): per-agent rollup table with PENDING / RUNNING /
COMPLETE / ERRORS status pill ladder, two-line agent cell (name + mono
slug), text-red-600 font-semibold on the Failed cell when value > 0,
sr-only caption "Per-agent execution progress" + aria-label on pills.
Renders the italic "No active sub-jobs." paragraph when agents list empty.
- dispatch_summary_inline.html (NEW): SSE payload partial for the
dispatch_summary event ("Dispatched N proposals across M agents (K sub-jobs)"
with proper pluralization).
- progress_row_inline.html (NEW): SSE payload partial for the progress event
(three labeled counter values).
Tests:
- 15 template-render tests (test_progress_partial.py) cover empty / single-
agent / multi-agent / COMPLETE / ERRORS / PENDING pill states, banner
singular/plural pluralization, dispatch_summary + agents_table + dual
sse-close slots, empty-state copy.
- 10 router integration tests (test_execution_dispatch.py) cover multi-agent
per-chunk enqueue (3 agents x [100, 600, 250] -> 4 sub-jobs with correct
sub_batch_index), dispatch_summary JSON in Redis hash with all D-04 fields,
24h TTL, INFO log emission, revoked-agents banner surfacing, collision
short-circuits dispatch (no Redis seed, no enqueue), and four SSE-generator
tests (aggregate progress, agents_table emission, dispatch_summary fires
once, complete + complete_with_errors close the stream).
- 4 pre-existing test_execution.py tests updated to the new contract
(app.state.redis instead of queue.redis; app.state.task_router instead of
app.state.queue.enqueue) -- the old single-queue dispatch path is gone.
All 28-V-04, 28-V-05, 28-V-18, 28-V-19, 28-V-20, 28-V-21 are GREEN.
377 / 377 tests in tests/test_routers + tests/test_services/test_execution_dispatch_grouping
+ tests/test_template_helpers pass (1 skipped, unchanged from before).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the Wave 0 module-level pytest.skip stub in tests/test_template_helpers/test_cross_fs_fingerprint_notice.py with eight real tests against the not-yet-created banner partial. Asserts the UI-SPEC C3 contract: Alpine.js x-data dismissal, role="status" (NOT alert), info glyph ⓘ (NOT warning ⚠), dismiss button with aria-label, no localStorage reference, both copy lines from the Copywriting Contract, and the inclusion contract on duplicates/list.html. Targets 28-V-24. Tests currently fail with TemplateNotFound -- the GREEN commit creates the partial and edits duplicates/list.html to include it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦traint
Lands the TASK-04 operator-visible disclosure surface for the v4.0 per-file-
server fingerprint locality limitation (CONTEXT.md D-13 + D-14):
- src/phaze/templates/_partials/cross_fs_fingerprint_notice.html: new
dismissible Alpine.js info banner (x-data="{ open: true }" / x-show /
@click="open = false"). role="status" (not alert -- the limitation is
by-design, not a problem). Info glyph ⓘ (not warning ⚠).
NO localStorage -- per-session dismissal only so the disclosure re-appears
on every page load. Matches UI-SPEC C3 verbatim.
- src/phaze/templates/duplicates/list.html: includes the new partial as the
first child of the space-y-6 div, above the page <h1>.
- PROJECT.md: adds an operator-facing paragraph to the Constraints section
documenting that audfprint/panako indices are per-file-server and cross-
file-server matching is XAGENT-01 (deferred).
- src/phaze/templates/_partials/.gitkeep: removed (the real partial replaces
the Wave 0 anchor; .gitkeep no longer needed once the directory has a
tracked sibling).
Flips 28-V-24 GREEN. Closes Phase 28 TASK-04 (the validator portion landed
in Plan 28-01; the operator-visible disclosure lands here).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦04 closure Records the Wave 3 outcome of Plan 28-06: - 28-V-24 GREEN (banner partial + dismiss attrs + role=status + no localStorage + inclusion) - Banner partial src/phaze/templates/_partials/cross_fs_fingerprint_notice.html - duplicates/list.html includes the partial above its <h1> - PROJECT.md Constraints paragraph documents XAGENT-01 (deferred) - Phase 28 TASK-04 fully closed (config validator from 28-01 + doc + banner here) Includes a "Recommended STATE.md entry" heading with the single bullet the orchestrator should append to .planning/STATE.md after wave merge (per the spawn directive that worktrees must not modify STATE.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Reportβ All modified and coverable lines are covered by tests. π’ Thoughts on this report? Let us know! |
Adds focused unit tests for the lines Codecov reported as uncovered (95.11% patch coverage, 13 missing lines): - tests/test_routers/test_execution_helpers.py (NEW): direct unit tests for the small pure helpers in routers/execution.py β _coerce_int edge cases, _agents_view_from_hash fallbacks, _render_partial memoryview/bytes body branches, _build_agents_view variants, the SSE 'waiting' + malformed-JSON branches, and start_execution enqueue-failure / empty-groups / collision-block short-circuits. No Docker required. - tests/test_tasks/test_execute_approved_batch_progress.py: adds failure-resilience tests for every best-effort audit / PATCH / progress call inside _execute_one plus the empty-scan_roots precondition β each asserts the WARN-and-continue contract. Coverage (isolated unit tests, no Docker): - src/phaze/routers/execution.py: 66.92% β 93.85% - src/phaze/tasks/execution.py: 100% (unchanged) Remaining uncovered lines on routers/execution.py (audit_log route + 2 SSE integration paths) are covered by the Docker-dependent test_execution.py / test_execution_dispatch.py suites in CI. 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.
Summary
Phase 28: Distributed Execution Dispatch β v4.0
Status: β Verified (25/25 Nyquist validation points Β· 5/5 requirements met)
Approving a batch that spans multiple file servers now results in each agent doing its own local copy-verify-delete while the application server preserves the write-ahead audit trail and presents unified progress to the operator.
POST /execution/startwas rewritten from a single-queue enqueue into per-agent fan-out: SELECT approved proposals β group byFileRecord.agent_idβ chunk at 500 β seedexec:{batch_id}Redis hash β enqueue one SAQ sub-job per (agent, chunk). A new agent-internalPOST /api/internal/agent/exec-batches/{batch_id}/progressendpoint mutates the hash via HINCRBY with cross-tenant 403 + Stripe-styleSET NX EXrequest-id idempotency. The agent task body fires per-proposal progress POSTs at terminal state with SAQ-meta-persisted UUIDs so retries reuse the sameexecution_log_idandprogress_request_id. The SSE generator now emitsdispatch_summaryon first connect, pushesagents_tableHTML every tick, and closes on eithercompleteorcomplete_with_errors. Finally, a dismissible Alpine.js banner discloses the v4.0 cross-file-server fingerprint-locality limitation (XAGENT-01) and a Constraints paragraph lands in PROJECT.md.Changes
Plan 28-01: Wave 0 unblocker
Test scaffolding (8 files for later waves),
audfprint_url/panako_urllocalhost-only allow-list validator (D-12 / TASK-04),ExecuteApprovedBatchPayload.sub_batch_index: int = 0schema field (D-10).Key files:
src/phaze/config.py(added_enforce_localhost_onlyvalidator)src/phaze/schemas/agent_tasks.pytests/test_*(scaffolding) +tests/test_services/test_fingerprint_locality.py(6 GREEN tests)Plan 28-02: agent-internal exec-batch progress endpoint
End-to-end agent-side ingress for batch progress:
AgentExecBatchProgressBodyschema, router with 4-stage D-17 guard order (401β403β404β403),SET NX EXidempotency lock,HINCRBYper D-07,sub_batch_terminalstatus promotion. Also addedPhazeAgentClient.post_exec_batch_progress()and main.py registration. 41 tests pass.Key files:
src/phaze/routers/agent_exec_batches.py(new, 196 lines)src/phaze/schemas/agent_exec_batches.py(new, 88 lines)src/phaze/services/agent_client.py(+25 lines)src/phaze/main.py(+4 lines)Plan 28-03: dispatch grouping service
Controller-side helpers for the fan-out β SELECT approved proposals JOINed with FileRecord + Agent, group by
agent_idvia in-Pythondefaultdict(list)(cheaper than SQLjsonb_aggat v4.0 scale of 1-5 agents Γ β€10K proposals), filter revoked agents (return skipped count for banner), chunk at 500. 20 tests pass.Key files:
src/phaze/services/execution_dispatch.py(new βget_approved_proposals_grouped_by_agent,count_revoked_skipped_proposals,chunk_proposals)Plan 28-04: per-agent fan-out + SSE extension
POST /execution/startrewritten as per-agent dispatch; SSE generator extended fordispatch_summary+agents_tableper-tick + close oncomplete_with_errors. 3 new Jinja partials rendered via_render_partial()helper (usesTemplateResponse(...).body.decode()per Semgrep XSS-lint requirements). 25 tests pass.Key files:
src/phaze/routers/execution.py(rewritten)src/phaze/templates/execution/partials/agents_table.html(new)src/phaze/templates/execution/partials/dispatch_summary_inline.html(new)src/phaze/templates/execution/partials/progress_row_inline.html(new)src/phaze/templates/execution/partials/progress.html(rewritten)Plan 28-05: agent task body progress POSTs + SAQ-meta UUID lift
_execute_onerewritten as a 4-step state machine (copy β verify β copy β delete) firing oneapi.post_exec_batch_progress(...)per terminal state. New_classify_failure_step(sha256 mismatch β"verify", otherwise current_step passthrough) and_load_or_seed_uuidshelpers; bothexecution_log_idandprogress_request_idUUIDs persisted intoctx['job'].metavia singleawait job.update(meta=...)β closes L6/L22, delivers D-15. D-01<step>: <reason>error_message format applied to both failed PATCH paths. D-16 fire-and-forget POSTs with WARNING-on-failure swallow. 22 tests pass.Key files:
src/phaze/tasks/execution.py(rewritten)Plan 28-06: TASK-04 disclosure surface
Dismissible Alpine.js banner on Duplicate Resolution page warning operators that cross-file-server fingerprint matching is deferred (XAGENT-01); Constraints paragraph appended to PROJECT.md. Per-session dismissal only (no localStorage). 8 tests pass.
Key files:
src/phaze/templates/_partials/cross_fs_fingerprint_notice.html(new)src/phaze/templates/duplicates/list.html(banner include).planning/PROJECT.md(Constraints paragraph)Requirements Addressed
batch_idexec:{batch_id}Redis hash; SSE serves unifiedtotal / completed / failedcountscomplete_with_errorsclose-event preserves failure-aware completion UX in the SSE streamVerification
28-VERIFICATION.md28-REVIEW.md)Key Decisions
ExecuteApprovedBatchPayload.sub_batch_index: int = 0lets the agent worker report which chunk of a per-agent dispatch it ownsaudfprint_url/panako_urllocalhost-only allow-list validator onBaseSettingsexecution_log_idandprogress_request_idper-proposal UUIDs persisted intoctx['job'].metaso SAQ retries reuse them (closes L6/L22)_render_partial()helper rather than inline f-strings β Semgrep XSS-lint requires this over bareEnvironment.get_template().render()Follow-ups
PHAZE_TEST_DATABASE_URL_28_02/_28_04env-var worktree-isolation workaround in test files (IN-01)href="#""Learn more" link on disclosure banner (IN-02)28-REVIEW.md(SET NX EX lost-event risk, IPv6::1validator gap, brittle"sha256 mismatch"string match, SSE never-closes-on-TTL, dispatch-and-revoked-count not in shared transaction, deadrevoked_agentstemplate context)π€ Generated with Claude Code