Skip to content

Phase 28: Distributed Execution Dispatch#62

Merged
SimplicityGuy merged 41 commits into
mainfrom
gsd/phase-28-distributed-execution-dispatch
May 16, 2026
Merged

Phase 28: Distributed Execution Dispatch#62
SimplicityGuy merged 41 commits into
mainfrom
gsd/phase-28-distributed-execution-dispatch

Conversation

@SimplicityGuy
Copy link
Copy Markdown
Owner

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/start was rewritten from a single-queue enqueue into per-agent fan-out: SELECT approved proposals β†’ group by FileRecord.agent_id β†’ chunk at 500 β†’ seed exec:{batch_id} Redis hash β†’ enqueue one SAQ sub-job per (agent, chunk). A new agent-internal POST /api/internal/agent/exec-batches/{batch_id}/progress endpoint mutates the hash via HINCRBY with cross-tenant 403 + Stripe-style SET NX EX request-id idempotency. The agent task body fires per-proposal progress POSTs at terminal state with SAQ-meta-persisted UUIDs so retries reuse the same execution_log_id and progress_request_id. The SSE generator now emits dispatch_summary on first connect, pushes agents_table HTML every tick, and closes on either complete or complete_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_url localhost-only allow-list validator (D-12 / TASK-04), ExecuteApprovedBatchPayload.sub_batch_index: int = 0 schema field (D-10).

Key files:

  • src/phaze/config.py (added _enforce_localhost_only validator)
  • src/phaze/schemas/agent_tasks.py
  • 8 new test files under tests/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: AgentExecBatchProgressBody schema, router with 4-stage D-17 guard order (401β†’403β†’404β†’403), SET NX EX idempotency lock, HINCRBY per D-07, sub_batch_terminal status promotion. Also added PhazeAgentClient.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_id via in-Python defaultdict(list) (cheaper than SQL jsonb_agg at 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/start rewritten as per-agent dispatch; SSE generator extended for dispatch_summary + agents_table per-tick + close on complete_with_errors. 3 new Jinja partials rendered via _render_partial() helper (uses TemplateResponse(...).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_one rewritten as a 4-step state machine (copy β†’ verify β†’ copy β†’ delete) firing one api.post_exec_batch_progress(...) per terminal state. New _classify_failure_step (sha256 mismatch β†’ "verify", otherwise current_step passthrough) and _load_or_seed_uuids helpers; both execution_log_id and progress_request_id UUIDs persisted into ctx['job'].meta via single await 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

  • EXEC-01 β€” Group-by-agent dispatch enqueues one sub-job per (agent, chunk) under a shared batch_id
  • EXEC-02 β€” Each agent performs copy-verify-delete locally and PATCHes per-operation status; ExecutionLog write-ahead trail survives the HTTP boundary
  • EXEC-03 β€” Application server owns the exec:{batch_id} Redis hash; SSE serves unified total / completed / failed counts
  • EXEC-04 β€” complete_with_errors close-event preserves failure-aware completion UX in the SSE stream
  • TASK-04 β€” Operator disclosure of v4.0 fingerprint-locality limitation (banner + Constraints paragraph + URL validator)

Verification

  • Automated verification: passed (25/25 Nyquist points Β· 5/5 requirements Β· 122 tests)
  • Goal-backward check: all 8 phase truths in code, evidence cross-referenced in 28-VERIFICATION.md
  • Code review: 1 Critical + 6 Warnings + 5 Info (see 28-REVIEW.md)
  • Critical finding tracked: HINCRBY/HSET race in terminal status promotion β†’ issue Fix HINCRBY/HSET race in agent exec-batch terminal status promotionΒ #61 (must land before v4.0 scales to β‰₯3 concurrent sub-jobs per batch; classified as residual defect, not a phase-goal hole)

Key Decisions

  • D-04/D-07 read-then-write hash semantics β€” locked phase contracts do not constrain atomicity; Lua-script fix for the race condition (Fix HINCRBY/HSET race in agent exec-batch terminal status promotionΒ #61) preserves the contract
  • D-10 β€” ExecuteApprovedBatchPayload.sub_batch_index: int = 0 lets the agent worker report which chunk of a per-agent dispatch it owns
  • D-12 β€” audfprint_url/panako_url localhost-only allow-list validator on BaseSettings
  • D-15 β€” Both execution_log_id and progress_request_id per-proposal UUIDs persisted into ctx['job'].meta so SAQ retries reuse them (closes L6/L22)
  • D-16 β€” Fire-and-forget progress POSTs (WARNING-on-failure swallow because file ops already committed)
  • D-17 β€” Cross-tenant 403 fires BEFORE any Redis read to prevent timing side-channels (Phase 26-08 pattern)
  • In-Python grouping over SQL aggregation at v4.0 scale of 1-5 agents Γ— ≀10K proposals
  • SSE payloads as 3 Jinja partials via _render_partial() helper rather than inline f-strings β€” Semgrep XSS-lint requires this over bare Environment.get_template().render()

Follow-ups

  • Fix HINCRBY/HSET race in agent exec-batch terminal status promotionΒ #61 β€” Fix HINCRBY/HSET race in agent exec-batch terminal status promotion (CR-01)
  • Clean up PHAZE_TEST_DATABASE_URL_28_02 / _28_04 env-var worktree-isolation workaround in test files (IN-01)
  • Replace placeholder href="#" "Learn more" link on disclosure banner (IN-02)
  • Address 6 Warnings in 28-REVIEW.md (SET NX EX lost-event risk, IPv6 ::1 validator gap, brittle "sha256 mismatch" string match, SSE never-closes-on-TTL, dispatch-and-revoked-count not in shared transaction, dead revoked_agents template context)

πŸ€– Generated with Claude Code

SimplicityGuy and others added 30 commits May 14, 2026 14:43
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>
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).
SimplicityGuy and others added 10 commits May 15, 2026 15:59
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 &#9432; (NOT warning &#9888;), 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 &#9432; (not warning &#9888;).
  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
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

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>
@SimplicityGuy SimplicityGuy merged commit 30b8faf into main May 16, 2026
34 checks passed
@SimplicityGuy SimplicityGuy deleted the gsd/phase-28-distributed-execution-dispatch branch May 16, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant