Skip to content

v0.1.18.0 feat(marketing): image-edit (image-to-image) API for creatives, flag-gated#722

Merged
DeliciousHouse merged 3 commits into
masterfrom
worktree-image-edit-api
Jun 24, 2026
Merged

v0.1.18.0 feat(marketing): image-edit (image-to-image) API for creatives, flag-gated#722
DeliciousHouse merged 3 commits into
masterfrom
worktree-image-edit-api

Conversation

@DeliciousHouse

Copy link
Copy Markdown
Owner

What

Adds an Aries-owned image-edit path: an operator changes an existing creative with a natural-language instruction instead of regenerating from scratch. Behind ARIES_IMAGE_EDIT_ENABLED (default OFF) — the route 404s and the review drawer's "Edit this image" section is hidden, byte-identical to the route not existing.

When ON, the instruction is routed to Hermes as a new aries_run reusing the regenerate submission path (per-stage profile pipeline scoped by regenerate_creative), carrying an edit_instruction + the source image's Hermes-cache basename so the content-generator profile calls image_generate on that existing image (image-to-image edit endpoint). An explicit "IMAGE EDIT EXECUTION CONTRACT" pins the agent to the edit tool. No Hermes-repo change.

Surface

  • Route: POST /api/social-content/jobs/[jobId]/creatives/[creativeId]/edit (route + handler; flag-gated invisible 404)
  • Backend: editCreativeAsImageEdit + tenant/job-scoped resolveRuntimeSourceImageBasename (backend/marketing/regenerate-creative.ts); image-edit-env.ts flag helper; edit fields threaded through execution-port.ts, workflow-request.ts, ports/hermes.ts
  • Protocol: additive optional regenerate_creative.edit_instruction + source_image_basename; PROTOCOL_VERSION 1.1.1 → 1.2.0 (older consumers strip the fields → plain regenerate)
  • UI: review drawer "Edit this image" section, gated server-side from the review page
  • Config: docker-compose.yml aries-app env + .env.example + CLAUDE.md docs

Safety (reviewed)

This branch went through /review (4 specialists + Claude adversarial + Codex cross-model). Fixes applied in-branch:

  • Prompt injection (multi-source confirmed): all operator-controlled values in the prompt contract (source_creative_id, source_run_id, edit_instruction) are JSON-encoded — was raw-interpolated for two of them.
  • Untrusted source_run_id: an explicit body source_run_id is validated against the job's own stage run ids (UI never sends it, so transparent to the real flow).
  • PROTOCOL_VERSION bump (the protocol-version-bump-required full-suite gate would have failed without it).
  • Fail-open resolver now logs a warning; tenant/job-scoped SQL with path-traversal + bad-tenant guards.
  • Edited image lands as a NEW creative_assets row (original preserved), exactly like regenerate.

Tests

18 tests in tests/edit-creative.test.ts (incl. direct resolveRuntimeSourceImageBasename traversal/tenant/fail-open coverage + negative paths). Green: typecheck, lint, verify, validate:execution-provider (52), validate:social-content (92), merged cleanly with #721 (video) and re-verified.

Rollout

Default OFF. Screenshot-verify a freshly edited + published post on a live tenant before flipping ARIES_IMAGE_EDIT_ENABLED — the open question is whether the content-generator reliably honors the edit contract; if it ever ignores the source, the feature degrades safely to a regenerate.

🤖 Generated with Claude Code

hermes-agent and others added 3 commits June 24, 2026 04:54
…gated

Route an operator's natural-language edit instruction for an existing creative
to the Hermes content-generator edit endpoint, reusing the regenerate submission
path. Behind ARIES_IMAGE_EDIT_ENABLED (default OFF): the route 404s and the UI
section is hidden, byte-identical to the route not existing.

- POST /api/social-content/jobs/[jobId]/creatives/[creativeId]/edit
- editCreativeAsImageEdit + tenant/job-scoped runtime source-image basename
  resolver (path-traversal + bad-tenant guards, fail-open with a warn log)
- IMAGE EDIT EXECUTION CONTRACT pins the content-generator to image_generate's
  edit endpoint; every operator-controlled value JSON-encoded (injection-safe)
- explicit body source_run_id validated against the job's own stage run ids
- additive optional regenerate_creative.edit_instruction + source_image_basename
  (PROTOCOL_VERSION 1.1.1 -> 1.2.0; older consumers degrade to a plain regenerate)
- review drawer "Edit this image" section, gated server-side by the flag
- 18 tests incl. resolver traversal/tenant guards + negative paths

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UY8LhVMywSJAjXWrZ6RPej
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UY8LhVMywSJAjXWrZ6RPej
Copilot AI review requested due to automatic review settings June 24, 2026 05:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a flag-gated “image edit” (image-to-image) flow to Aries social-content creatives: a new API endpoint submits a Hermes run that reuses the regenerate pipeline but carries an edit instruction (and optionally a resolved source-image basename) so Hermes edits an existing image instead of regenerating from scratch. The feature is default-OFF via ARIES_IMAGE_EDIT_ENABLED, and when disabled the endpoint returns a real 404 and the UI section is hidden.

Changes:

  • Add POST /api/social-content/jobs/[jobId]/creatives/[creativeId]/edit handler + backend submit path (editCreativeAsImageEdit) and source-image basename resolver.
  • Thread optional edit context (edit_instruction, source_image_basename) through workflow request plumbing and Hermes prompt contract; bump protocol version.
  • Add UI affordance in the review creative drawer (server-gated) plus a comprehensive new test suite for the edit flow.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
VERSION Bumps repo version marker to 0.1.18.0.
package.json Bumps package version to 0.1.18.0.
CHANGELOG.md Documents the new image-edit feature and rollout gate.
packages/aries-hermes-protocol/src/schemas.ts Bumps PROTOCOL_VERSION to 1.2.0 and adds optional regenerate creative edit fields in callback context schema.
backend/marketing/execution-port.ts Extends RegenerateCreativeContext with optional edit_instruction and source_image_basename.
backend/social-content/workflow-request.ts Serializes optional edit fields into regenerate_creative request context.
backend/marketing/image-edit-env.ts Adds isImageEditEnabled env flag helper for ARIES_IMAGE_EDIT_ENABLED.
backend/marketing/regenerate-creative.ts Implements editCreativeAsImageEdit and resolveRuntimeSourceImageBasename, including tenant/job scoping and fail-open behavior.
backend/marketing/ports/hermes.ts Adds an explicit “IMAGE EDIT EXECUTION CONTRACT” prompt block and threads optional fields into callback context.
app/api/social-content/jobs/[jobId]/creatives/[creativeId]/edit/route.ts Adds the Next.js API route wiring for the edit endpoint.
app/api/social-content/jobs/[jobId]/creatives/[creativeId]/edit/handler.ts Adds handler with invisible 404 gating, tenant auth, validation, and 202 submit response.
app/review/[reviewId]/page.tsx Reads the flag server-side and passes imageEditEnabled into the review screen.
frontend/aries-v1/review-item.tsx Adds imageEditEnabled prop and forwards it to the creative action drawer.
frontend/aries-v1/creative-action-drawer.tsx Adds “Edit this image” UI section, client-side submission, and state handling.
docker-compose.yml Wires ARIES_IMAGE_EDIT_ENABLED into the aries-app service environment.
.env.example Documents the new env var and rollout guidance.
CLAUDE.md Documents the new flag (but currently contains a protocol-schema-change inconsistency—see comment).
tests/edit-creative.test.ts Adds extensive coverage for flag gating, validation, resolver behavior, request serialization, and route behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CLAUDE.md

- `ARIES_NATIVE_REPLY_ENABLED=1` — rollout switch for native comment reply (qa-defect #598; `isNativeReplyEnabled`, `backend/integrations/meta-reply-env.ts`). Aries treats `1`/`true`/`yes`/`on` as enabled. Default OFF. When OFF the `POST /api/insights/comments/[commentId]/reply` endpoint is invisible — it returns a real 404 (not a stub) and touches no DB and no Graph API. When ON, an operator reply to a stored social comment is posted to Meta (Instagram `POST /{ig-comment-id}/replies`, Facebook `POST /{comment-id}/comments` — public comment reply, not Messenger/private_replies) via `replyToComment` (`backend/integrations/meta-reply.ts`), and on a confirmed Graph reply id the `insights_comments` row is marked (`is_replied=true`, `platform_reply_id`, `replied_at`). Tenant-isolated (the row is loaded `WHERE id=$1 AND tenant_id=$2`; a cross-tenant id 404s) and idempotent: the row is claimed (`is_replied` false→true) BEFORE the Graph call and rolled back only when the reply definitely never posted — the exact publish-path claim/rollback/outcome-unknown semantics (a 2xx with no reply id is `needs_manual_reconciliation`, never auto-retried). The Graph call runs OUTSIDE any held pool client (independent `pool.query` before/after). Responses are frontend-safe only (never the token or raw Graph error). Requires the `instagram_manage_comments` (IG) / `pages_manage_engagement` (FB) Graph scopes — requested in `backend/integrations/provider-registry.ts` but inert until Meta App Review grants them (external blocker) — plus the `platform_reply_id`/`replied_at` columns from `scripts/init-db.js` / `migrations/20260616000000_insights_comments_native_reply.sql` (applied on container start). Process-wide; default OFF.

- `ARIES_IMAGE_EDIT_ENABLED=1` — rollout switch for IMAGE EDIT (image-to-image) of an existing creative (`isImageEditEnabled`, `backend/marketing/image-edit-env.ts`). Aries treats `1`/`true`/`yes`/`on` as enabled. Default OFF. When OFF, the `POST /api/social-content/jobs/[jobId]/creatives/[creativeId]/edit` endpoint is invisible — it returns a real 404 (not a stub), touches no DB and no Hermes gateway — and the review drawer's "Edit this image" section is hidden (the drawer is byte-identical to today). When ON, an operator's natural-language edit instruction is routed to Hermes as a new aries_run that reuses the regenerate submission path (per-stage profile pipeline scoped by `regenerate_creative`), but carries an `edit_instruction` plus the source image's Hermes cache basename so the content-generator profile calls `image_generate` on that existing image (its image-to-image *edit* endpoint, selected deterministically by `use_edit = bool(source_images)`) instead of generating from scratch — an explicit "IMAGE EDIT EXECUTION CONTRACT" in the submission prompt pins the agent to the edit tool (mirroring the production stage's "MUST call image_generate" contract). `editCreativeAsImageEdit` (`backend/marketing/regenerate-creative.ts`) resolves the source basename from `creative_assets.storage_key` for `runtime_asset` rows only (framed/uploaded `ingested_asset` rows live under DATA_ROOT, not the Hermes cache, so they resolve to null and Hermes falls back to locating the source via `source_run_id`+`source_creative_id`); resolution is fail-open (any DB/parse error → null → still submits). The edited image lands as a NEW `creative_assets` row (same `variant_batch_id`/`variant_index`, newer `created_at`) via the unchanged production-callback ingest — the original row is preserved, exactly like regenerate. Tenant-isolated and flag-gated taste signal on edit (`rejected` on the brand visual-style lens, best-effort, no-op unless `ARIES_POST_EDIT_TASTE_LEARNING_ENABLED`). The review-page server component reads the flag and passes `imageEditEnabled` down to the client drawer. No `aries-hermes-protocol` schema change (edit fields ride the existing free-form `input` string + the optional `regenerate_creative` context); no Hermes-repo change. Wired into the `aries-app` service `environment:` block in `docker-compose.yml`. Process-wide; default OFF. **Before flipping in prod, screenshot-verify a freshly edited + published post on a live tenant** (only rendered output counts). The deterministic-enough open question — whether the content-generator agent reliably honors the edit contract — needs a live smoke test; if it ever ignores the source image the feature degrades safely to a regenerate.
@DeliciousHouse DeliciousHouse merged commit cad2f63 into master Jun 24, 2026
6 checks passed
@DeliciousHouse DeliciousHouse deleted the worktree-image-edit-api branch June 24, 2026 05:12
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.

3 participants