v0.1.18.0 feat(marketing): image-edit (image-to-image) API for creatives, flag-gated#722
Merged
Merged
Conversation
…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
Contributor
There was a problem hiding this comment.
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]/edithandler + 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.
|
|
||
| - `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. |
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.
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_runreusing the regenerate submission path (per-stage profile pipeline scoped byregenerate_creative), carrying anedit_instruction+ the source image's Hermes-cache basename so the content-generator profile callsimage_generateon 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
POST /api/social-content/jobs/[jobId]/creatives/[creativeId]/edit(route + handler; flag-gated invisible 404)editCreativeAsImageEdit+ tenant/job-scopedresolveRuntimeSourceImageBasename(backend/marketing/regenerate-creative.ts);image-edit-env.tsflag helper; edit fields threaded throughexecution-port.ts,workflow-request.ts,ports/hermes.tsregenerate_creative.edit_instruction+source_image_basename;PROTOCOL_VERSION1.1.1 → 1.2.0 (older consumers strip the fields → plain regenerate)docker-compose.ymlaries-appenv +.env.example+ CLAUDE.md docsSafety (reviewed)
This branch went through
/review(4 specialists + Claude adversarial + Codex cross-model). Fixes applied in-branch:source_creative_id,source_run_id,edit_instruction) are JSON-encoded — was raw-interpolated for two of them.source_run_id: an explicit bodysource_run_idis validated against the job's own stage run ids (UI never sends it, so transparent to the real flow).protocol-version-bump-requiredfull-suite gate would have failed without it).creative_assetsrow (original preserved), exactly like regenerate.Tests
18 tests in
tests/edit-creative.test.ts(incl. directresolveRuntimeSourceImageBasenametraversal/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