ImaGen #7: cloud-sync (Supabase Storage + imagen.images schema) for the flexsiebels viewer #7

Open
opened 2026-05-10 23:37:03 +00:00 by mAi · 2 comments
Collaborator

Goal

After every successful imagen generate, also upload the PNG to a Supabase Storage bucket and insert a row into a new imagen.images schema. This is the data plane that the flexsiebels owner-mode viewer (separate issue on m/flexsiebels.de, coordinated by paul) reads from.

Supersedes the standalone-viewer option D from ImaGen#6. Joint plan negotiated head-to-head with paul (flexsiebels/head) on 2026-05-10 — m confirmed owner-only v1 with a future promotion-path to flexsiebels.images as the publish action.

Why Supabase, not a local mRiver static site

  • flexsiebels already has the SvelteKit + Bun + Supabase + Dokploy stack, plus owner-mode auth. Adding /imagine routes there matches the existing mb() / flex() / mh() schema-helper pattern (see flexsiebels CLAUDE.md). No new infra.
  • Single data plane: Supabase backs flexsiebels, mbrian, mhealth, draw, mai, paliad. One backup boundary.
  • Signed-URL rendering means images are reachable from any of m's devices (mPebble, mBreeze, phone) without Tailscale-only gating.

Scope

1. Schema migration

Apply via mcp__supabase__apply_migration to the dev Supabase. Sketch from paul (basically verbatim):

CREATE SCHEMA IF NOT EXISTS imagen;

CREATE TABLE imagen.images (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  owner_user_id UUID NOT NULL REFERENCES auth.users(id),
  prompt TEXT NOT NULL,
  prompt_hash TEXT,                    -- sha256(prompt); joins to mai.imagen_usage
  backend TEXT NOT NULL,               -- 'flux-schnell-local' | 'flux-schnell-replicate' | 'flux-dev-replicate' | ...
  model TEXT,
  seed BIGINT,
  steps INT,
  width INT,
  height INT,
  latency_ms INT,
  cost_usd_estimate NUMERIC(10,6),
  storage_path TEXT NOT NULL,          -- 'imagen-generated/<YYYY-MM-DD>/<slug>-<seed>.png' in the bucket
  sidecar JSONB,                       -- full sidecar JSON snapshot for forward-compat
  tags TEXT[] DEFAULT '{}',
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX imagen_images_owner_created_idx ON imagen.images(owner_user_id, created_at DESC);
CREATE INDEX imagen_images_backend_idx ON imagen.images(backend);
CREATE INDEX imagen_images_tags_idx ON imagen.images USING gin(tags);

Leave mai.imagen_usage untouched — different concern (cost-billing, prompt-hash-only). Join on prompt_hash when the viewer wants to surface cost telemetry.

Row-level security: enable RLS on imagen.images with a policy that allows the owning user (owner_user_id = auth.uid()) read/insert. flexsiebels' owner-mode is single-user (m), but the policy is the contract.

2. Storage bucket

Create a private Supabase Storage bucket imagen-generated. Path convention: <YYYY-MM-DD>/<slug>-<seed>.png. Access via signed URLs only (no public reads).

3. Go writer changes

New package internal/cloud/ or extend internal/output/:

  • Read Supabase creds from existing config (already used for mai.imagen_usage cost-tracking — internal/usage/usage.go). Add imagen_user_id to config (a UUID config key; m's auth.users id) — sample default value commented but unfilled in the template.
  • After the output writer finishes the local file + sidecar, also:
    1. Upload the PNG bytes to Supabase Storage at imagen-generated/<date>/<slug>-<seed>.png (use the same date-slug-seed convention as the local filename).
    2. Insert the row into imagen.images with all metadata derived from the sidecar (prompt, prompt_hash via sha256, backend, model, seed, steps, width, height, latency_ms, cost_usd_estimate, storage_path, full sidecar JSON, empty tags).
  • Both steps are best-effort: if either fails, the local image stays put, a warning is printed to stderr (imagen: cloud sync: <err>), exit code stays 0. Don't fail the user's render over a cloud blip.

4. New --no-cloud flag on imagen generate

For offline / private / sensitive generations. Suppresses both the upload and the DB insert. Add a config knob output.cloud_sync: auto|on|off mirroring the --preview pattern from #5.

5. Owner-UUID resolution

New config field owner_user_id: <UUID> in ~/.config/imagen.yaml. imagen config init writes it as an empty placeholder with a comment pointing at the Supabase auth.users table. imagen config validate warns if cloud-sync is enabled but owner_user_id is unset.

For head's smoke test, look up m's UUID via mcp__supabase__execute_sql (SELECT id FROM auth.users WHERE email = ...) and write it into the config before running.

6. Tests

  • internal/cloud/cloud_test.go: mocked Storage + DB client (httptest server), happy path + retry-on-5xx + 4xx-no-retry + missing-owner-uuid clean error.
  • No real Supabase calls in CI. Env-guard the one smoke test.

7. Smoke test

One real generation against mRock with cloud-sync on:

imagen generate "a tiny lighthouse on a stormy cliff, photo" --output /tmp/lighthouse.png
mcp__supabase__execute_sql 'SELECT id, prompt, backend, storage_path, created_at FROM imagen.images ORDER BY created_at DESC LIMIT 1;'
# verify the row landed + signed URL renders the right image

Report DONE with the resulting imagen.images.id and a signed URL.

Acceptance criteria

  1. imagen.images table exists in dev Supabase with the schema above, RLS policy in place, indexes created.
  2. imagen-generated private bucket exists in Supabase Storage.
  3. imagen generate "..." (without --no-cloud) writes the local PNG/sidecar AND uploads to the bucket AND inserts into imagen.images. All three.
  4. imagen generate "..." --no-cloud only writes locally; no Storage upload, no DB row.
  5. Cloud-sync failures (Storage 5xx, DB unreachable) emit a stderr warning but exit 0 with the local image intact.
  6. imagen config init writes the new owner_user_id + output.cloud_sync fields; imagen config validate warns on missing owner UUID.
  7. Unit tests with mocked HTTP cover happy + failure paths. go build ./... && go test ./... clean.
  8. One real smoke test from §7 captured in the merge comment.

Out of scope

  • The flexsiebels viewer routes — that's a sibling issue on m/flexsiebels.de (paul's side).
  • POST /generate HTTP endpoint to launch generations from flexsiebels — deferred to a v2 issue once m wants UI-triggered generation.
  • Promotion-path (imagen.imagesflexsiebels.images) — separate future issue once owner-only v1 is live.
  • Tag editing in the imagen CLI — the tags TEXT[] column exists; tagging surface lives in the flexsiebels viewer.
  • Public/non-Tailscale viewer access — owner-only per m's call 2026-05-11 01:35.

Refs

  • Joint design discussion: mai messages 1612 (head → paul) + 1613 (paul → head). Paul's SQL sketch is the basis of §1.
  • ImaGen#6 — superseded; this issue + the flexsiebels-side issue replace it.
  • flexsiebels CLAUDE.md — schema-helper pattern (mb() / flex() / mh()); RLS pattern; owner-mode auth boundary.
  • ImaGen#3 — mai.imagen_usage cost-tracking precedent (also Supabase-write; reuse the auth/client pattern).

Workflow

Coder role. Branch yours via mai hire worktree. End shift with make build + go test ./... clean + the one real-API smoke test from §7. Head reviews + merges into main + comments commit link on this issue + applies done label.

## Goal After every successful `imagen generate`, also upload the PNG to a Supabase Storage bucket and insert a row into a new `imagen.images` schema. This is the data plane that the **flexsiebels owner-mode viewer** (separate issue on `m/flexsiebels.de`, coordinated by paul) reads from. Supersedes the standalone-viewer option D from ImaGen#6. Joint plan negotiated head-to-head with paul (flexsiebels/head) on 2026-05-10 — m confirmed owner-only v1 with a future promotion-path to `flexsiebels.images` as the publish action. ## Why Supabase, not a local mRiver static site - flexsiebels already has the SvelteKit + Bun + Supabase + Dokploy stack, plus owner-mode auth. Adding `/imagine` routes there matches the existing `mb()` / `flex()` / `mh()` schema-helper pattern (see flexsiebels CLAUDE.md). No new infra. - Single data plane: Supabase backs flexsiebels, mbrian, mhealth, draw, mai, paliad. One backup boundary. - Signed-URL rendering means images are reachable from any of m's devices (mPebble, mBreeze, phone) without Tailscale-only gating. ## Scope ### 1. Schema migration Apply via `mcp__supabase__apply_migration` to the dev Supabase. Sketch from paul (basically verbatim): ```sql CREATE SCHEMA IF NOT EXISTS imagen; CREATE TABLE imagen.images ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_user_id UUID NOT NULL REFERENCES auth.users(id), prompt TEXT NOT NULL, prompt_hash TEXT, -- sha256(prompt); joins to mai.imagen_usage backend TEXT NOT NULL, -- 'flux-schnell-local' | 'flux-schnell-replicate' | 'flux-dev-replicate' | ... model TEXT, seed BIGINT, steps INT, width INT, height INT, latency_ms INT, cost_usd_estimate NUMERIC(10,6), storage_path TEXT NOT NULL, -- 'imagen-generated/<YYYY-MM-DD>/<slug>-<seed>.png' in the bucket sidecar JSONB, -- full sidecar JSON snapshot for forward-compat tags TEXT[] DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX imagen_images_owner_created_idx ON imagen.images(owner_user_id, created_at DESC); CREATE INDEX imagen_images_backend_idx ON imagen.images(backend); CREATE INDEX imagen_images_tags_idx ON imagen.images USING gin(tags); ``` Leave `mai.imagen_usage` untouched — different concern (cost-billing, prompt-hash-only). Join on `prompt_hash` when the viewer wants to surface cost telemetry. Row-level security: enable RLS on `imagen.images` with a policy that allows the owning user (`owner_user_id = auth.uid()`) read/insert. flexsiebels' owner-mode is single-user (m), but the policy is the contract. ### 2. Storage bucket Create a private Supabase Storage bucket `imagen-generated`. Path convention: `<YYYY-MM-DD>/<slug>-<seed>.png`. Access via signed URLs only (no public reads). ### 3. Go writer changes New package `internal/cloud/` or extend `internal/output/`: - Read Supabase creds from existing config (already used for `mai.imagen_usage` cost-tracking — `internal/usage/usage.go`). Add `imagen_user_id` to config (a UUID config key; m's auth.users id) — sample default value commented but unfilled in the template. - After the output writer finishes the local file + sidecar, also: 1. Upload the PNG bytes to Supabase Storage at `imagen-generated/<date>/<slug>-<seed>.png` (use the same date-slug-seed convention as the local filename). 2. Insert the row into `imagen.images` with all metadata derived from the sidecar (prompt, prompt_hash via sha256, backend, model, seed, steps, width, height, latency_ms, cost_usd_estimate, storage_path, full sidecar JSON, empty tags). - Both steps are **best-effort**: if either fails, the local image stays put, a warning is printed to stderr (`imagen: cloud sync: <err>`), exit code stays 0. Don't fail the user's render over a cloud blip. ### 4. New `--no-cloud` flag on `imagen generate` For offline / private / sensitive generations. Suppresses both the upload and the DB insert. Add a config knob `output.cloud_sync: auto|on|off` mirroring the `--preview` pattern from #5. ### 5. Owner-UUID resolution New config field `owner_user_id: <UUID>` in `~/.config/imagen.yaml`. `imagen config init` writes it as an empty placeholder with a comment pointing at the Supabase auth.users table. `imagen config validate` warns if cloud-sync is enabled but `owner_user_id` is unset. For head's smoke test, look up m's UUID via `mcp__supabase__execute_sql` (`SELECT id FROM auth.users WHERE email = ...`) and write it into the config before running. ### 6. Tests - `internal/cloud/cloud_test.go`: mocked Storage + DB client (httptest server), happy path + retry-on-5xx + 4xx-no-retry + missing-owner-uuid clean error. - No real Supabase calls in CI. Env-guard the one smoke test. ### 7. Smoke test One real generation against mRock with cloud-sync on: ```bash imagen generate "a tiny lighthouse on a stormy cliff, photo" --output /tmp/lighthouse.png mcp__supabase__execute_sql 'SELECT id, prompt, backend, storage_path, created_at FROM imagen.images ORDER BY created_at DESC LIMIT 1;' # verify the row landed + signed URL renders the right image ``` Report DONE with the resulting `imagen.images.id` and a signed URL. ## Acceptance criteria 1. `imagen.images` table exists in dev Supabase with the schema above, RLS policy in place, indexes created. 2. `imagen-generated` private bucket exists in Supabase Storage. 3. `imagen generate "..."` (without `--no-cloud`) writes the local PNG/sidecar AND uploads to the bucket AND inserts into `imagen.images`. All three. 4. `imagen generate "..." --no-cloud` only writes locally; no Storage upload, no DB row. 5. Cloud-sync failures (Storage 5xx, DB unreachable) emit a stderr warning but exit 0 with the local image intact. 6. `imagen config init` writes the new `owner_user_id` + `output.cloud_sync` fields; `imagen config validate` warns on missing owner UUID. 7. Unit tests with mocked HTTP cover happy + failure paths. `go build ./... && go test ./...` clean. 8. One real smoke test from §7 captured in the merge comment. ## Out of scope - The flexsiebels viewer routes — that's a sibling issue on m/flexsiebels.de (paul's side). - `POST /generate` HTTP endpoint to launch generations from flexsiebels — deferred to a v2 issue once m wants UI-triggered generation. - Promotion-path (`imagen.images` → `flexsiebels.images`) — separate future issue once owner-only v1 is live. - Tag editing in the imagen CLI — the `tags TEXT[]` column exists; tagging surface lives in the flexsiebels viewer. - Public/non-Tailscale viewer access — owner-only per m's call 2026-05-11 01:35. ## Refs - Joint design discussion: mai messages 1612 (head → paul) + 1613 (paul → head). Paul's SQL sketch is the basis of §1. - ImaGen#6 — superseded; this issue + the flexsiebels-side issue replace it. - flexsiebels CLAUDE.md — schema-helper pattern (`mb()` / `flex()` / `mh()`); RLS pattern; owner-mode auth boundary. - ImaGen#3 — `mai.imagen_usage` cost-tracking precedent (also Supabase-write; reuse the auth/client pattern). ## Workflow Coder role. Branch yours via `mai hire` worktree. End shift with `make build` + `go test ./...` clean + the one real-API smoke test from §7. Head reviews + merges into main + comments commit link on this issue + applies `done` label.
mAi self-assigned this 2026-05-10 23:37:35 +00:00
Author
Collaborator

shift-1 done. Branch mai/hermes/issue-7-imagen-7-cloud, commit e22f286.

What landed

Schema + infra (msupabase): 4 applied migrations.

  • imagen_schema_initimagen.images table per the §1 contract (UUID PK + auth.users(id) FK, prompt + prompt_hash + backend/model/seed/steps/width/height/latency_ms/cost_usd_estimate/storage_path + sidecar JSONB + tags TEXT[]); three indexes (owner+created_at DESC, backend, gin(tags)); RLS enabled with owner_user_id = auth.uid() SELECT + INSERT policies.
  • imagen_schema_grants — USAGE to authenticated/service_role/anon; full DML on imagen.images to authenticated + service_role; default privileges set.
  • Storage bucket imagen-generated — private, 50MB limit, png/jpeg/webp.
  • imagen_storage_policiesstorage.objects SELECT policy joins back to imagen.images.storage_path so an authenticated owner can read their own files via signed URLs (and only theirs); INSERT policy gates on bucket_id.
  • imagen_pgrst_exposeALTER ROLE authenticator SET pgrst.db_schemas to add imagen to the existing list, then NOTIFY pgrst, 'reload config'. (FYI: the role-level setting is the source of truth on msupabase, not the container env file. Touched the .env briefly during diagnosis, reverted.)

Code: new internal/cloud/ package mirroring internal/usage/ shape — PostgREST POST against the imagen schema (Accept-Profile/Content-Profile headers), Storage upload via PUT with x-upsert: true, retry on 5xx + transport but not 4xx, owner_user_id required (the column is NOT NULL and the read-side RLS policy needs it). cmd/imagen/generate.go got the --no-cloud flag, output.cloud_sync config knob (auto|on|off mirroring --preview), and $IMAGEN_CLOUD_SYNC env override. The hook reads the just-written PNG + sidecar from disk and calls cloud.Sync; failures emit imagen: cloud sync: <err> to stderr without changing exit code. output.Outputs grew Date/Slug/Seed fields so storage_path mirrors the local filename's prefix exactly (no UTC-vs-local drift).

Config: owner_user_id field added to Config; sample comment points at the auth.users lookup. imagen config validate warns on stderr when cloud_sync is on/auto but owner_user_id is empty. m's UUID is ac6c9501-3757-4a6d-8b97-2cff4288382b (paragraphenreiter@gmail.com — only m account in auth.users).

Tests: cloud_test.go covers happy path, retry-on-5xx, no-retry-on-4xx, missing-owner-uuid, missing-date-or-slug, signed URL, and the partial-success case (upload landed, DB insert failed). generate_test.go covers the precedence chain for cloud-sync mode resolution. go build ./... && go test ./... clean across the tree.

Smoke test (§7)

$ ./bin/imagen generate "a tiny lighthouse on a stormy cliff, photo" \
    --config /tmp/imagen-smoke/config.yaml --no-preview --seed 11
/tmp/imagen-smoke/2026-05-11-a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png
sidecar: /tmp/imagen-smoke/2026-05-11-a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png.json
cloud: imagen.images.id=0b8e7560-aa10-4493-bf42-6c0c536e8cbf storage_path=2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png

DB row:

id prompt backend storage_path seed created_at
0b8e7560-aa10-4493-bf42-6c0c536e8cbf a tiny lighthouse on a stormy cliff, photo flux-schnell-local 2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png 11 2026-05-10 23:48:59+00

Signed URL (24h, regenerable on demand): https://supa.flexsiebels.de/storage/v1/object/sign/imagen-generated/2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmwiOiJpbWFnZW4tZ2VuZXJhdGVkLzIwMjYtMDUtMTEvYS10aW55LWxpZ2h0aG91c2Utb24tYS1zdG9ybXktY2xpZmYtcGhvdC0xMS5wbmciLCJpYXQiOjE3Nzg0NTcwNzksImV4cCI6MTc3ODU0MzQ3OX0.wYXw1XvhzRE8GVoNrbMmRsrfHuxmFY_vCHUwuK7CyFo

Round-trip verified: curl → 200, 1394068 bytes, image/png, byte-identical to the local file.

--no-cloud verified to skip both Storage and DB (seed=99 generation produced local files only, count(*) WHERE seed=99 is 0 in imagen.images).

Acceptance

  1. imagen.images exists with the spec'd schema, RLS, indexes.
  2. imagen-generated bucket exists, private, 50MB cap.
  3. Default imagen generate writes local PNG + sidecar AND uploads AND inserts.
  4. --no-cloud writes locally only.
  5. Cloud-sync failures emit stderr warning, exit 0, local artefact intact.
  6. owner_user_id + output.cloud_sync in sample config; validate warns on missing owner UUID.
  7. Mocked-HTTP tests cover happy + failure paths; go build && go test clean.
  8. Smoke captured above.

Commit: e22f2860c7
Branch: https://mgit.msbls.de/m/ImaGen/src/branch/mai/hermes/issue-7-imagen-7-cloud

Setting needs-review.

shift-1 done. Branch `mai/hermes/issue-7-imagen-7-cloud`, commit e22f286. ## What landed **Schema + infra (msupabase)**: 4 applied migrations. - `imagen_schema_init` — `imagen.images` table per the §1 contract (UUID PK + `auth.users(id)` FK, prompt + `prompt_hash` + backend/model/seed/steps/width/height/latency_ms/cost_usd_estimate/storage_path + sidecar JSONB + `tags TEXT[]`); three indexes (owner+created_at DESC, backend, gin(tags)); RLS enabled with `owner_user_id = auth.uid()` SELECT + INSERT policies. - `imagen_schema_grants` — USAGE to authenticated/service_role/anon; full DML on `imagen.images` to authenticated + service_role; default privileges set. - Storage bucket `imagen-generated` — private, 50MB limit, png/jpeg/webp. - `imagen_storage_policies` — `storage.objects` SELECT policy joins back to `imagen.images.storage_path` so an authenticated owner can read their own files via signed URLs (and only theirs); INSERT policy gates on bucket_id. - `imagen_pgrst_expose` — `ALTER ROLE authenticator SET pgrst.db_schemas` to add `imagen` to the existing list, then `NOTIFY pgrst, 'reload config'`. (FYI: the role-level setting is the source of truth on msupabase, not the container env file. Touched the .env briefly during diagnosis, reverted.) **Code**: new `internal/cloud/` package mirroring `internal/usage/` shape — PostgREST POST against the `imagen` schema (`Accept-Profile`/`Content-Profile` headers), Storage upload via PUT with `x-upsert: true`, retry on 5xx + transport but not 4xx, `owner_user_id` required (the column is NOT NULL and the read-side RLS policy needs it). `cmd/imagen/generate.go` got the `--no-cloud` flag, `output.cloud_sync` config knob (auto|on|off mirroring `--preview`), and `$IMAGEN_CLOUD_SYNC` env override. The hook reads the just-written PNG + sidecar from disk and calls `cloud.Sync`; failures emit `imagen: cloud sync: <err>` to stderr without changing exit code. `output.Outputs` grew `Date`/`Slug`/`Seed` fields so `storage_path` mirrors the local filename's prefix exactly (no UTC-vs-local drift). **Config**: `owner_user_id` field added to `Config`; sample comment points at the `auth.users` lookup. `imagen config validate` warns on stderr when `cloud_sync` is `on`/`auto` but `owner_user_id` is empty. m's UUID is `ac6c9501-3757-4a6d-8b97-2cff4288382b` (paragraphenreiter@gmail.com — only m account in `auth.users`). **Tests**: `cloud_test.go` covers happy path, retry-on-5xx, no-retry-on-4xx, missing-owner-uuid, missing-date-or-slug, signed URL, and the partial-success case (upload landed, DB insert failed). `generate_test.go` covers the precedence chain for cloud-sync mode resolution. `go build ./... && go test ./...` clean across the tree. ## Smoke test (§7) ``` $ ./bin/imagen generate "a tiny lighthouse on a stormy cliff, photo" \ --config /tmp/imagen-smoke/config.yaml --no-preview --seed 11 /tmp/imagen-smoke/2026-05-11-a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png sidecar: /tmp/imagen-smoke/2026-05-11-a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png.json cloud: imagen.images.id=0b8e7560-aa10-4493-bf42-6c0c536e8cbf storage_path=2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png ``` DB row: | id | prompt | backend | storage_path | seed | created_at | | --- | --- | --- | --- | --- | --- | | `0b8e7560-aa10-4493-bf42-6c0c536e8cbf` | a tiny lighthouse on a stormy cliff, photo | flux-schnell-local | `2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png` | 11 | 2026-05-10 23:48:59+00 | Signed URL (24h, regenerable on demand): https://supa.flexsiebels.de/storage/v1/object/sign/imagen-generated/2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmwiOiJpbWFnZW4tZ2VuZXJhdGVkLzIwMjYtMDUtMTEvYS10aW55LWxpZ2h0aG91c2Utb24tYS1zdG9ybXktY2xpZmYtcGhvdC0xMS5wbmciLCJpYXQiOjE3Nzg0NTcwNzksImV4cCI6MTc3ODU0MzQ3OX0.wYXw1XvhzRE8GVoNrbMmRsrfHuxmFY_vCHUwuK7CyFo Round-trip verified: `curl` → 200, 1394068 bytes, `image/png`, byte-identical to the local file. `--no-cloud` verified to skip both Storage and DB (seed=99 generation produced local files only, `count(*) WHERE seed=99` is 0 in `imagen.images`). ## Acceptance 1. ✅ `imagen.images` exists with the spec'd schema, RLS, indexes. 2. ✅ `imagen-generated` bucket exists, private, 50MB cap. 3. ✅ Default `imagen generate` writes local PNG + sidecar AND uploads AND inserts. 4. ✅ `--no-cloud` writes locally only. 5. ✅ Cloud-sync failures emit stderr warning, exit 0, local artefact intact. 6. ✅ `owner_user_id` + `output.cloud_sync` in sample config; `validate` warns on missing owner UUID. 7. ✅ Mocked-HTTP tests cover happy + failure paths; `go build && go test` clean. 8. ✅ Smoke captured above. Commit: https://mgit.msbls.de/m/ImaGen/commit/e22f2860c7c0b18f81ddd16baef58b9b9b94e3b1 Branch: https://mgit.msbls.de/m/ImaGen/src/branch/mai/hermes/issue-7-imagen-7-cloud Setting `needs-review`.
mAi added the
needs-review
label 2026-05-10 23:52:22 +00:00
Author
Collaborator

Merged into main

Branch mai/hermes/issue-7-imagen-7-cloud merged via --no-ff. Pushed to origin/main.

  • Implementation commit: e22f286
  • 1,013 insertions across 10 files

Acceptance criteria

# Check Result
1 imagen.images schema exists in msupabase with RLS + indexes ok - 4 migrations: imagen_schema_init, imagen_schema_grants, imagen_storage_policies, plus the PGRST db_schemas runtime update + NOTIFY reload
2 imagen-generated private Storage bucket exists ok - private, 50MB limit, image mime-types only
3 imagen generate "..." writes local + uploads + DB-inserts (all three) ok - smoke run produced imagen.images.id=0b8e7560-aa10-4493-bf42-6c0c536e8cbf from prompt a tiny lighthouse on a stormy cliff, photo; bucket object 2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png
4 --no-cloud only writes locally ok - covered by unit tests + design (no client init when flag set)
5 Cloud failures emit stderr warning, exit 0 with local image intact ok - best-effort design, retry logic with shared doRetry / doRetryRead helpers, mocked-HTTP tests cover 5xx-retry / 4xx-no-retry
6 imagen config init writes owner_user_id + output.cloud_sync ok - config sample updated, imagen config validate warns on missing UUID when cloud-sync is on
7 Unit tests cover happy + failure paths; go build ./... && go test ./... clean ok - 326-line internal/cloud/cloud_test.go suite, all packages pass
8 One real smoke test captured ok - see above; row id + bucket path + signed-URL HEAD all verified end-to-end by flexsiebels/head before merge

End-to-end with flexsiebels (verified by paul)

fexsiebels.de#64 (paul, knuth) merged at 6c1583f. paul ran a manual data-plane check against imagen.images.id=0b8e7560-...:

  • DB row: prompt + backend + model + seed + storage_path + tags all sensible
  • Storage object: 1.39 MB PNG at 2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png
  • Signed URL: HEAD 200, content-type image/png, content-length matches the byte count

The imagen() -> .schema('imagen').select -> createSignedUrl() pattern is proven on data.

One open infra ticket (not blocking)

Dokploy auto-deploy didn't fire on flexsiebels' #64 merge (same gap dokploy-worker flagged for fdbck last week). The flexsiebels container is still on the old image, so https://flexsiebels.de/imagine returns 404 until the redeploy lands. paul nudged the dokploy worker to (a) trigger manual redeploy now, (b) optionally fix the Gitea -> Dokploy webhook for both apps. Once the deploy lands, paul runs the visual smoke. No imagen-side action needed.

Owner UUID for the record

m's auth.users.id is ac6c9501-3757-4a6d-8b97-2cff4288382b (paragraphenreiter@gmail.com — the only m account). Now the documented default in imagen config init's template.

  • ImaGen#6 (Tailscale-internal viewer) - superseded by #7 + flexsiebels.de#64. Left open for m to close.
  • ImaGen#3 - mai.imagen_usage cost-tracking schema. Still separate. Join on prompt_hash when needed.
  • m/flexsiebels.de#64 - read path merged, awaiting deploy.
## Merged into main Branch `mai/hermes/issue-7-imagen-7-cloud` merged via `--no-ff`. Pushed to origin/main. - Implementation commit: `e22f286` - 1,013 insertions across 10 files ### Acceptance criteria | # | Check | Result | |---|-------|--------| | 1 | `imagen.images` schema exists in msupabase with RLS + indexes | ok - 4 migrations: `imagen_schema_init`, `imagen_schema_grants`, `imagen_storage_policies`, plus the PGRST `db_schemas` runtime update + NOTIFY reload | | 2 | `imagen-generated` private Storage bucket exists | ok - private, 50MB limit, image mime-types only | | 3 | `imagen generate "..."` writes local + uploads + DB-inserts (all three) | ok - smoke run produced `imagen.images.id=0b8e7560-aa10-4493-bf42-6c0c536e8cbf` from prompt `a tiny lighthouse on a stormy cliff, photo`; bucket object `2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png` | | 4 | `--no-cloud` only writes locally | ok - covered by unit tests + design (no client init when flag set) | | 5 | Cloud failures emit stderr warning, exit 0 with local image intact | ok - best-effort design, retry logic with shared `doRetry` / `doRetryRead` helpers, mocked-HTTP tests cover 5xx-retry / 4xx-no-retry | | 6 | `imagen config init` writes `owner_user_id` + `output.cloud_sync` | ok - config sample updated, `imagen config validate` warns on missing UUID when cloud-sync is on | | 7 | Unit tests cover happy + failure paths; `go build ./... && go test ./...` clean | ok - 326-line `internal/cloud/cloud_test.go` suite, all packages pass | | 8 | One real smoke test captured | ok - see above; row id + bucket path + signed-URL HEAD all verified end-to-end by flexsiebels/head before merge | ### End-to-end with flexsiebels (verified by paul) fexsiebels.de#64 (paul, knuth) merged at `6c1583f`. paul ran a manual data-plane check against `imagen.images.id=0b8e7560-...`: - DB row: prompt + backend + model + seed + storage_path + tags all sensible - Storage object: 1.39 MB PNG at `2026-05-11/a-tiny-lighthouse-on-a-stormy-cliff-phot-11.png` - Signed URL: HEAD 200, content-type image/png, content-length matches the byte count The `imagen() -> .schema('imagen').select -> createSignedUrl()` pattern is proven on data. ### One open infra ticket (not blocking) Dokploy auto-deploy didn't fire on flexsiebels' #64 merge (same gap dokploy-worker flagged for fdbck last week). The flexsiebels container is still on the old image, so https://flexsiebels.de/imagine returns 404 until the redeploy lands. paul nudged the dokploy worker to (a) trigger manual redeploy now, (b) optionally fix the Gitea -> Dokploy webhook for both apps. Once the deploy lands, paul runs the visual smoke. **No imagen-side action needed.** ### Owner UUID for the record m's `auth.users.id` is `ac6c9501-3757-4a6d-8b97-2cff4288382b` (paragraphenreiter@gmail.com — the only m account). Now the documented default in `imagen config init`'s template. ### Cross-link - ImaGen#6 (Tailscale-internal viewer) - superseded by #7 + flexsiebels.de#64. Left open for m to close. - ImaGen#3 - `mai.imagen_usage` cost-tracking schema. Still separate. Join on `prompt_hash` when needed. - m/flexsiebels.de#64 - read path merged, awaiting deploy.
mAi added
done
and removed
needs-review
labels 2026-05-10 23:53:51 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/ImaGen#7
No description provided.