Skip to content

feat(publish): wire sanitizer + output-first hero into orchestrator#6

Merged
ucguy4u merged 1 commit into
mainfrom
fix/publish-orchestrator-hookup
May 4, 2026
Merged

feat(publish): wire sanitizer + output-first hero into orchestrator#6
ucguy4u merged 1 commit into
mainfrom
fix/publish-orchestrator-hookup

Conversation

@ucguy4u

@ucguy4u ucguy4u commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the deferred follow-up from #5. Wires the publish sanitizer (already on main) into `Orchestrator.publish()` and adds an output-first hero image picker with terminal fallback.

Changes

`Orchestrator.publish()` now:

  1. Sanitizes body — calls `sanitize_body_for_publish()` before `client.publish()`. JSON command manifest, exec-proof block, and screenshot plan sections never reach WP. No-op fast path when body is clean.
  2. Output-first hero — prefers `output_.png` (command result) over `terminal_.png` (command line, duplicates body code block).
  3. Resilient fallback — if output upload raises `PublishError`, retries with terminal so a bad output proof never publishes a hero-less post.

Implementation notes

  • Single `iterdir()` partition replaces two glob calls (TOCTOU race fixed per codex review).
  • Body `model_copy` only fires when sanitizer mutates content.

Tests added (6)

test purpose
`strips_scaffolding_from_body_before_wp_call` sanitizer called, scaffolding gone from passed body
`clean_body_skips_body_model_copy` sanitizer called once with original body, no spurious mutation
`dirty_body_triggers_model_copy` sanitized body reaches `wp.publish`
`prefers_output_screenshot_as_hero` output picked over terminal when both present
`falls_back_to_terminal_when_no_output_image` terminal used when no output
`falls_back_to_terminal_when_output_upload_fails` resilience: retry terminal on output upload error

`WordPressClient` mock spec'd so signature drift fails fast.

Test plan

  • `pytest tests/test_orchestrator.py tests/test_publish_sanitizer.py` — all green
  • Full suite: 657 passed
  • Codex review (4 findings addressed: fallback regression, idempotency proof, glob race, unspec'd mock)
  • After merge: end-to-end `brewpress approve-publish` against staging WP confirms sanitized body reaches WP

Out of scope

Sandbox skip-empty-output rule (`sandbox_tutorial_media.py`) is independent and lands with the sandbox track.

🤖 Generated with Claude Code

Orchestrator.publish() now:
1. Calls sanitize_body_for_publish() (added in #5) before client.publish()
   so the JSON command manifest, exec-proof block, and screenshot plan
   sections never reach the WP draft. No-op fast path when body is clean.
2. Prefers output_*.png over terminal_*.png for the hero image.
   output_*.png shows command results; terminal_*.png shows the command
   line, which duplicates the body's code block.
3. Falls back to terminal_*.png when output upload raises PublishError,
   so a bad output proof never publishes a hero-less post.

Implementation notes:
- Single iterdir() partition replaces two glob calls (avoids TOCTOU race
  flagged in codex review).
- Body model_copy only fires when sanitizer actually mutates content.

Tests added (6 new):
- strips_scaffolding_from_body_before_wp_call
- clean_body_skips_body_model_copy (sanitizer called once, body unchanged)
- dirty_body_triggers_model_copy (sanitized body reaches wp.publish)
- prefers_output_screenshot_as_hero
- falls_back_to_terminal_when_no_output_image
- falls_back_to_terminal_when_output_upload_fails (resilience)

WordPressClient mock now spec'd so signature drift fails fast.

Closes the deferred follow-up from #5. Sandbox skip-empty-output rule
is independent and lands with the sandbox track.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@ucguy4u
ucguy4u merged commit bd843ff into main May 4, 2026
1 of 2 checks passed
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