Skip to content

chore(dx): dev-reset PRD seed + pre-push lint fixes#525

Merged
thxforall merged 9 commits into
devfrom
chore/dev-reset-prd-seed
May 14, 2026
Merged

chore(dx): dev-reset PRD seed + pre-push lint fixes#525
thxforall merged 9 commits into
devfrom
chore/dev-reset-prd-seed

Conversation

@thxforall
Copy link
Copy Markdown
Contributor

Summary

  • dev-reset 시 PRD_DB_URL 환경변수가 있으면 PRD 데이터를 자동 시딩하도록 Justfile/seed-from-prod.sh 수정
  • pre-push hook 통과를 위해 기존 코드의 lint/clippy 에러 일괄 정리
  • rustls-webpki 보안 advisory(RUSTSEC-2026-0104) 대응

Changes

  • Justfile + scripts/seed-from-prod.sh: PRD 시딩 자동화
  • Web: prettier 포매팅, eslint-disable, .prettierignore 업데이트
  • Rust: clippy warnings 해소 (duplicate fields, too_many_arguments, disallowed_methods, doc_lazy_continuation 등)
  • config.rs: 테스트 모드에서 dotenvy .env.dev 로딩 스킵 (테스트 안정성)
  • deny.toml: RUSTSEC-2026-0104 예외 등록 (AWS SDK legacy TLS chain)

Test plan

  • cargo test — 1086 passed, 0 failed
  • cargo clippy — 0 errors
  • eslint app lib — 0 errors (warnings only)
  • prettier --check . — all matched
  • tsc --noEmit — passed
  • cargo deny check — advisories ok

🤖 Generated with Claude Code

thxforall and others added 9 commits May 14, 2026 17:54
- seed-from-prod.sh: add --yes/-y flag for non-interactive mode
- seed-from-prod.sh: delete editorial_pipeline_settings before import to avoid duplicate key error
- Justfile dev-reset: auto-detect PRD_DB_URL from packages/web/.env.local
- Justfile dev-reset: graceful fallback when decoded-backend network is absent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auto-fix prettier formatting across admin pages and add eslint-disable
for untyped Supabase entity table access. Attach cause to firecrawl
timeout error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove duplicate has_url fields in solutions/tests.rs
- Fix unnecessary cast in gemini_cost.rs
- Allow clippy::disallowed_methods for json!() macro in content_studio

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Allow disallowed_methods for json!() macro in raw_posts handlers
- Use next_back() instead of last() on DoubleEndedIterator
- Fix doc_lazy_continuation in solutions/spots service
- Allow too_many_arguments for create_solution_for_verify
- Allow type_complexity for test-only InMemoryStorage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ments)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…erence

Config tests that verify default values were failing because
dotenvy reloaded .env.dev inside from_env(), overriding remove_var calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
decoded-app Ready Ready Preview, Comment May 14, 2026 9:36am

@thxforall thxforall added the bump:patch Bug fixes / internal refactor label May 14, 2026
@thxforall thxforall merged commit 0b95cb2 into dev May 14, 2026
7 of 8 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in decoded-monorepo May 14, 2026
cocoyoon added a commit that referenced this pull request May 14, 2026
* feat(raw_posts): StarStyle.com adapter (#466) (#481)

* feat(raw_posts): StarStyle.com adapter (#466)

WordPress SSR fashion 사이트(starstyle.com) 어댑터 추가. wots(#465) 인프라
재사용 — discovery_target='raw_posts' 글로벌 피드 모델, PrelabeledData 로
vision 단계 우회.

핵심 차이: per-item 사진 URL 이 없어 thumbnail_url=None → items_thumbnail
processor 가 spots bbox 로 hero crop fallback path 사용. brand/title 분리는
보수적으로 product 통째로 (verify 단계 admin 처리).

- adapters/_starstyle_html.py: parse_post / parse_sitemap /
  decode_skimresources (skim affiliate 디코드)
- adapters/starstyle.py: thin httpx wrapper, fetch + sitemap discover
- scripts/backfill_starstyle_posts.py: sitemap → JSONL streaming → PostgREST
  bulk INSERT (wots 미러)
- service.rs: parse_starstyle_source — slug-sp{ID} 형식 검증
- admin UI: PLATFORM_FILTERS / PLATFORM_TABS / DiscoveryPipelineCard / URL builder
- migration: pipeline_settings starstyle row (모든 cycle OFF)
- 24 단위 테스트 추가 (parser + adapter + prelabeled round-trip)

Closes #466.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(starstyle): force HTTPS on og:image URL (#466)

starstyle 의 og:image 가 http:// 로 노출되는데 admin 은 HTTPS 라
mixed-content 로 이미지가 차단되던 문제. CDN 자체는 HTTPS 정상이라
파서 단계에서 https 강제 변환.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: retrigger CI with bump label

* fix(starstyle-backfill): mirror hero images to R2 before INSERT (#466) (#484)

starstyle 의 og:image 는 admin (HTTPS) 에서 직접 hotlink 가 막힌다:
  - HTTPS 요청 → 301 redirect to HTTP → mixed-content 차단
  - HTTP 요청 → User-Agent / Referer 가 비어 있으면 403

백필 시점에 boto3 로 upstream 이미지 다운로드 → R2 (RAW_POSTS_R2_BUCKET) 에
``starstyle/{shard}/{external_id}.jpg`` 키로 업로드하고, raw_posts.image_url
을 R2 public URL 로 저장한다. admin 은 R2 직접 fetch (HTTPS clean, hotlink
없음) → Vercel image-proxy 우회.

- HEAD 로 R2 객체 존재 확인 → 재실행 시 중복 업로드 skip (idempotent)
- 다운로드/업로드 실패 시 upstream URL 로 fallback (image_url 그대로)
- RAW_POSTS_R2_* env 미설정 시 mirror skip + warning
- PostData @DataClass(frozen=True) → frozen 제거 (image_url 교체 위해)

검증: 500 row 재백필, 479/500 R2 mirrored. 샘플 R2 URL HTTPS 200 확인.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(raw_posts): R2 cleanup on delete + storage rename (#466) (#486)

* release: starstyle R2 mirror fix (#466) (#485)

* feat(raw_posts): StarStyle.com adapter (#466) (#481)

* feat(raw_posts): StarStyle.com adapter (#466)

WordPress SSR fashion 사이트(starstyle.com) 어댑터 추가. wots(#465) 인프라
재사용 — discovery_target='raw_posts' 글로벌 피드 모델, PrelabeledData 로
vision 단계 우회.

핵심 차이: per-item 사진 URL 이 없어 thumbnail_url=None → items_thumbnail
processor 가 spots bbox 로 hero crop fallback path 사용. brand/title 분리는
보수적으로 product 통째로 (verify 단계 admin 처리).

- adapters/_starstyle_html.py: parse_post / parse_sitemap /
  decode_skimresources (skim affiliate 디코드)
- adapters/starstyle.py: thin httpx wrapper, fetch + sitemap discover
- scripts/backfill_starstyle_posts.py: sitemap → JSONL streaming → PostgREST
  bulk INSERT (wots 미러)
- service.rs: parse_starstyle_source — slug-sp{ID} 형식 검증
- admin UI: PLATFORM_FILTERS / PLATFORM_TABS / DiscoveryPipelineCard / URL builder
- migration: pipeline_settings starstyle row (모든 cycle OFF)
- 24 단위 테스트 추가 (parser + adapter + prelabeled round-trip)

Closes #466.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(starstyle): force HTTPS on og:image URL (#466)

starstyle 의 og:image 가 http:// 로 노출되는데 admin 은 HTTPS 라
mixed-content 로 이미지가 차단되던 문제. CDN 자체는 HTTPS 정상이라
파서 단계에서 https 강제 변환.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: retrigger CI with bump label

* fix(starstyle-backfill): mirror hero images to R2 before INSERT (#466) (#484)

starstyle 의 og:image 는 admin (HTTPS) 에서 직접 hotlink 가 막힌다:
  - HTTPS 요청 → 301 redirect to HTTP → mixed-content 차단
  - HTTP 요청 → User-Agent / Referer 가 비어 있으면 403

백필 시점에 boto3 로 upstream 이미지 다운로드 → R2 (RAW_POSTS_R2_BUCKET) 에
``starstyle/{shard}/{external_id}.jpg`` 키로 업로드하고, raw_posts.image_url
을 R2 public URL 로 저장한다. admin 은 R2 직접 fetch (HTTPS clean, hotlink
없음) → Vercel image-proxy 우회.

- HEAD 로 R2 객체 존재 확인 → 재실행 시 중복 업로드 skip (idempotent)
- 다운로드/업로드 실패 시 upstream URL 로 fallback (image_url 그대로)
- RAW_POSTS_R2_* env 미설정 시 mirror skip + warning
- PostData @DataClass(frozen=True) → frozen 제거 (image_url 교체 위해)

검증: 500 row 재백필, 479/500 R2 mirrored. 샘플 R2 URL HTTPS 200 확인.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(release): backend bump ai=1.5.1 api=0.10.1 [skip ci]

* feat(raw_posts): R2 cleanup on raw_post delete (#466)

raw_post 삭제 시 image_url 이 raw_posts R2 객체이고 verified posts 가
같은 URL 을 참조하지 않으면 R2 객체도 같이 삭제. verify 후에는 posts.
image_url 가 raw_post.image_url 그대로 복사되므로 (#333) — verified post
가 참조 중이면 보존해야 깨지지 않는다.

또한 storage 명명을 db 패턴(db / assets_db) 에 맞춤:
  - storage_client → operation_storage
  - 신규 RAW_POSTS_R2_* → assets_storage
  - AppConfig.storage / assets_storage, AppState.operation_storage /
    assets_storage 일관 매핑.

config:
  - StorageConfig 두 개 (storage / assets_storage), 별도 R2 버킷 wiring
  - RAW_POSTS_R2_{ACCOUNT_ID, ACCESS_KEY_ID, SECRET_ACCESS_KEY, BUCKET, PUBLIC_URL}

service::delete_item:
  - 시그니처 (assets_db, prod_db, storage, public_url, id) — 4개의 DI
  - raw_post.image_url 추출 → DELETE 진행
  - extract_assets_r2_key 로 R2 key 추출 (public_url prefix strip)
  - posts.image_url COUNT — 0 이면 storage.delete(key) 호출
  - 실패는 best-effort warn (이미 raw_post 삭제는 성공)

테스트: 4개 단위 (key 추출 - prefix/trailing slash/other domain/empty url).

solutions/tests.rs 의 AdminSolutionListQuery 빌드에 has_url=None 추가
(unrelated 사전 컴파일 에러 — 본 PR 의 cargo test --lib 통과 위해 보강).

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: decoded-ci <ci@decodedcorp.com>

* feat(web): add vton item and post selection

* fix(web): use local supabase fallback for posts

* fix(web): serve local vton seed images

* fix(web): seed local post images for vton qa

* docs: design profile tries detail modal

* feat(web): add try detail snapshots

* fix(raw_posts): preserve vision log thumbnails (#490)

Store the judged image URL with vision filter audit rows so Instagram skip decisions can still render admin thumbnails after raw_posts cleanup.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): remove missing supabase client export (#491)

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(verify): R2 relocate raw → operation at verify time (#466) (#492)

작업 1 (마이그레이션, 1080 row) 후속 — 신규 verify 데이터도 동일한 single-source
컨벤션 (r2.decoded.style/posts/{post_id} / r2.decoded.style/items/{solution_id})
유지하도록 verify_raw_post 흐름 수정.

기존: raw_post.image_url (assets R2 dev pub URL) 그대로 posts.image_url 복사
→ 운영 DB 가 dev 도메인 URL 들고 있는 버그.

새 흐름:
  1. prod_txn 진입 전 R2 copy:
     - post_id 사전 생성 → assets_storage.download(raw_key) →
       operation_storage.upload("posts/{post_id}")
     - 각 visible item: solution_id 사전 생성 → items thumbnail 동일 패턴
  2. prod_txn: create_post_from_raw / create_solution_for_verify 에 미리 생성한
     UUID + 새 operation URL 전달 (시그니처에 명시 인자 추가)
  3. assets_txn: raw_post.image_url 도 operation URL 로 갱신 (단일 진실 소스)
  4. 두 트랜잭션 commit 후 raw 객체 best-effort delete (warn 만, orphan 허용)

코드 변경:
  - StorageClient trait 에 `download(key) -> (bytes, Option<content_type>)` 추가
  - CloudflareR2Client GetObject 구현, NotFound 매핑
  - DummyStorageClient stub 구현
  - 신규 `raw_posts/relocate.rs` — relocate_raw_to_operation helper + 4 단위 테스트
  - extract_assets_r2_key → pub(crate) 격상 (relocate 가 재사용)
  - create_post_from_raw 시그니처: post_id, image_url 명시 인자 (내부 Uuid::new_v4 제거)
  - create_solution_for_verify 시그니처: solution_id 명시 인자
  - verify_raw_post: prod_txn 전 R2 copy 단계 + raw cleanup post-commit

실패 처리:
  - copy 도중 실패 → BadRequest/NotFound/ExternalService Err, prod_txn 미시작 (DB 부작용 0)
  - 부분 성공 (hero copy OK / item N copy fail) → op bucket 에 hero orphan
    잔존 (lifecycle rule / 별도 GC out of scope)
  - raw delete 실패 → warn 로깅만, verify 자체는 성공 유지

테스트:
  - 4 단위 (relocate happy/fallback/non-raw-rejection/not-found-propagation)
  - 24 회귀 통과 (parse_source_identifier_*, verify_*)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(wiki): remove unknown tags (profile, vton) from profile-tries spec (#494)

Wiki lint 가 docs/wiki/schema/tags.md 에 등록 안 된 태그 거부.
api / ui 만 남겨 dev→main 머지 차단 해제.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(cost-tracking): Gemini API per-call cost tracking + admin dashboard (#496)

* feat(cost-tracking): Gemini API per-call cost tracking + admin dashboard

ai-server 의 Gemini 호출 (raw_post 6 + editorial 7) 모두를 호출 시점에
DB 적립 + 어드민 대시보드에서 일/step/model/pipeline 별 spend 가시화.

핵심 결정:
- 단가는 코드 하드코딩 없음 — `gemini_pricing` DB 테이블 SOT (SCD-2,
  effective_from/to). 어드민 UI 에서 inline upsert.
- per-call `pricing_snapshot` 보존 — 단가 변경 후에도 과거 cost 재현 가능.
- fire-and-forget recorder — 추적이 본 파이프라인 latency/실패에 영향 0.
- 실패도 row 적립 (ok=false, error_class) — 안전·정책 차단, quota,
  parse_error, timeout, no_pricing 등 가시화.
- 5분 in-process pricing cache — 어드민 변경 후 최악 5분 stale 허용.

DB (`supabase-assets/migrations/20260514140000_gemini_cost_tracking.sql`):
- gemini_pricing  (SCD-2, 12 seed row — Pro / Flash / Flash-Lite / Flash-Image / grounding)
- gemini_usage_events (per-call, BRIN + composite index 시간순)
- gemini_spend_daily (view, 대시보드 read-side)

ai-server (`src/services/cost_tracking/`):
- `track_call(step, model, extract, coro)` — coroutine wrapper. usage_metadata
  자동 추출 + estimator (token × DB 단가 × cached 우대) + recorder INSERT.
- raw_post scheduler / post_editorial service 에서 `set_context()` 1회 호출
  → 이후 모든 Gemini 호출이 raw_post_id / post_id / pipeline 자동 적립.
- 13 call site wrap: items/subject/spots/nano_banana(hero+thumbnail)/url_grounded/url_filter
  (raw_post 6), design_spec/image_analysis/item_search/news_research/editorial/review/celeb_search (editorial 7).

api-server (`src/domains/admin/gemini_cost.rs`):
- GET /spend/{daily,by-step,by-model,by-pipeline,today,top-raw-posts}
- GET / POST /pricing  (POST 가 기존 active row close + 새 row insert TX)
- DELETE /pricing/{id} (retire)

web (`/admin/gemini-cost`):
- KPI row (Today / Yesterday / 7d / 30d)
- Daily spend AreaChart (7/14/30d period selector)
- Spend by step / model / pipeline (가로 Bar)
- Top expensive raw_posts (drill-down → /admin/raw-posts/[id])
- Pricing editor (inline upsert + history toggle + retire)
- /admin home 에 `<GeminiCostMini>` 카드 + sidebar 새 "Observability" 그룹

검증:
- migration syntax check (local DB BEGIN; ... ROLLBACK;)
- e2e unit test (scripts/cost_tracking_e2e_test.py) — Pro/Flash/Image/cached/
  failure/no-pricing 6 case 모두 단가 계산 정확 (1000 in × \$1.25/M + 500 out
  × \$10/M = \$0.006250 등 모두 ✓)
- 실측 (scripts/cost_probe.py) — 운영 raw_post 1건 풀 파이프라인:
  fixed \$0.041 + per-item \$0.075 = 3 items \$0.268, 5 items 추정 ~\$0.42
- ai-server import / api-server cargo check / web typecheck + lint 모두 clean

Out of scope (별도 트랙):
- Cloud Billing Catalog API 자동 sync (Vertex 이전 후)
- daily threshold alert (Slack/텔레그램)
- 비용 hot path 최적화 (thumbnail/grounded 감축) — 데이터 적립 후 결정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(cost-tracking): real-parser wrap verification script

기존 cost_tracking_e2e_test.py 는 `track_call` 을 직접 호출 (mock coroutine).
이 스크립트는 실제 `SubjectParser` 클래스를 import + 호출 → 그 내부에 박힌
wrap 코드 라인이 작동해서 DB row 가 적립되는지 검증한다.

검증 결과 (운영 Pinterest 이미지 1장, ~$0.0005):
  step=subject_parser, model=gemini-2.5-flash, pipeline=raw_post,
  prompt_tokens=813, completion_tokens=98, cost_usd=$0.000489,
  pricing_snapshot={input_token: 0.0000003, output_token: 0.0000025}

→ wrap 코드 경로 + context 자동 전파 + 단가 cache lookup + INSERT 모두
   실제로 작동함을 확인.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(content-studio): AI-powered content generation pipeline (#498)

* feat(content-studio): add admin content studio

* feat(content-studio): add llm generation and research design

* docs(content-studio): clarify research panel contract

* docs(content-studio): add research panel implementation plan

* Add content studio research workflow

* docs(content-studio): add asset and short-form plan

* feat(content-studio): add asset and short-form schemas

* feat(content-studio): add asset planning helpers

* feat(content-studio): add asset plan api

* feat(content-studio): add asset and short-form panels

* feat(content-studio): add OpenAI image generation with editorial prompts

- Add /api/v1/content/assets/images route (admin-only) that runs OpenAI
  Image API per asset prompt and uploads results to Supabase Storage.
- Switch buildAssetPlan prompts to format-specific editorial / Vogue
  composition guides (4:5 cover, 9:16 spread, 1:1 portrait, 16:9
  thumbnail, 2:3 lookbook). Inject artist + wardrobe references and
  forbid on-image text or logos.
- Add content-studio-assets public storage bucket migration.
- AssetPanel: Generate Images button, image preview rendering,
  Download link per generated asset, failure list surface.

* feat(content-studio): inject research keywords + expand allowed domains

- buildAssetPlan now passes researchRun into image prompts when
  useResearchInCopy is on. Prompts cite trend topics from insights
  and adapt the scene cue based on packet.context (photoshoot, stage,
  runway, street, studio).
- Strengthen FORMAT_COMPOSITION away from solo-portrait bias toward
  full magazine-spread layouts with environment, props, and intentional
  negative space for typography.
- Expand DEFAULT_ALLOWED_DOMAINS so common Firecrawl results
  (instagram, pinterest, medium, dazed, i-d, business-of-fashion, etc.)
  pass the policy filter instead of returning "No allowed research
  sources were found." Configurable via
  CONTENT_STUDIO_RESEARCH_ALLOWED_DOMAINS.

* feat(content-studio): image-to-image edits + clickable insight evidence

Adds OpenAI image-edit endpoint support so generated images preserve
the look of the source post.

- openai-client: new fetchImageBytesFromOpenAIEdit posts multipart to
  /v1/images/edits with up to 4 reference images. generateAssetImage
  now routes to edit endpoint when referenceImageUrls are passed,
  falling back to plain generation on failure.
- /api/v1/content/assets/images route accepts { plan, packet,
  useReferenceImages }. When useReferenceImages is true, packet
  sourceImage and detectedItem thumbnails are forwarded as references.
- AssetPanel: "Use post images (image-to-image)" toggle (default on)
  alongside the Generate Images button.
- ResearchPanel: insight cards now render evidenceRefs as clickable
  domain chips linking to the source URL.

* feat(content-studio): bake headline into generated image (Instagram cover style)

* fix(content-studio): research falls back to top web results if no whitelisted source matches

* fix(content-studio): align asset test assertion with editorial prompt structure

The image prompt now uses packet.hook (concept) instead of packet.title,
matching the editorial magazine prompt format introduced in b55dfb8.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(admin): verify observability — stats page + daily-digest nag (#499)

`users.is_admin=true` 사용자별 raw_post verify 카운트 추적 + 어드민 UI
+ daily-digest 텔레그램 메시지에 통합 (어제 0건 admin 에 부드러운 독촉).

## api-server (`/api/v1/admin/verify-stats`)
- cross-DB join — operation DB users.is_admin + assets DB raw_posts.verified_by/at
- Returns per-admin (today / yesterday / 7d / 30d) + totals + nag_admin_ids
- 어제 0건 → needs_nag=true

## web (`/admin/verify-stats` + home)
- 페이지: KPI cards + per-admin table + nag banner
- /admin home 작은 카드 (오늘 / 어제 / 7d / nag count) — nag 있으면 amber border
- Sidebar Observability 그룹에 항목 추가

## daily-digest (Telegram cron)
- `.github/workflows/daily-digest.yml` — postgresql-client + 2 read-only DSN secret
  (ASSETS_DATABASE_URL_RO, OPERATION_DATABASE_URL_RO; 없으면 silent skip)
- `daily-digest.sh` — psql 2회 (admin list + verified_at GROUP BY)
  → jq zip → DATA_JSON 에 `admin_verify_stats` / `admin_verify_totals`
  → Claude Haiku prompt 에 "needs_nag 인 admin 에 부드러운 독촉 톤" 지시
  → BODY 에 deterministic verify table fallback (LLM 누락 시에도 ⚠️ 표시)

## 검증
- api-server `cargo check` + `cargo fmt --check` ✓
- web `bun run lint` ✓ (verify-stats 신규 파일 clean)
- daily-digest 스크립트 `bash -n` syntax ✓ + jq zip 로직 fixture test ✓

## 필요한 후속 작업
- 두 read-only DSN secret 을 GH repo secrets 에 추가
  (없으면 verify 섹션만 silent skip, 기존 daily-digest 정상 작동)
- 운영팀: read-only DB user 생성 권장 (admin 권한 불필요)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(content-studio): DB persistence for local dev parity (#510)

* feat(seed): add editorial/magazine sample data for local dev

post_magazines 2건 + posts 연결로 supabase db reset 시
홈 EditorialMagazine/EditorialCarousel에 샘플 데이터가 표시됨.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(content-studio): add Supabase migration for content_packets/variants tables

Local dev parity — mirrors SeaORM m20260507_000001 so tables exist
without Rust API when SKIP_DB_MIGRATIONS=1. Includes seed data with
one packet and three channel variants (instagram, youtube, x).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(types): regenerate Supabase types with content_packets/variants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(content-studio): Supabase direct CRUD for local dev without Rust API

Add db.ts helper (upsert/list/get packets, upsert variants, update
variant status) and wire all content API routes to use Supabase as
fallback when API_BASE_URL is absent or upstream returns 5xx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(content-studio): lower maxDuration to 60s for Vercel Hobby plan limit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(types): add content_packets/variants types without full typegen

Manual type addition to avoid overwriting PRD-only tables (item, post,
image) that local dev schema lacks. Replaces reverted full typegen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(content-studio): destructure auth for proper type narrowing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(content-studio): destructure auth in POST/GET packets for type narrowing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(content-studio): destructure admin in approve/reject/generate-variants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: cast JSONB fields to Json type for Supabase type compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(editorial): thumbnail regen button + pipeline error surfacing (#526)

Admin 매거진 draft 페이지에서 썸네일 단독 재생성 트리거 + 파이프라인 에러
가시화. abc34c2f-... 케이스 같이 generate_thumbnail 노드가 실패해도 status=
draft 로 마감되어 admin 이 원인을 못 봤던 결함 해결.

ai-server
- inbound.proto: RegenThumbnail RPC (article_id, hint) 추가, pb2 재생성
- regen_thumbnail_service.py: ARQ task — layout 로드 → generate_thumbnail
  단독 호출 → 결과/에러를 editorial_articles + editorial_article_events 에
  기록 (실패해도 thumbnail_url 보존)
- metadata_servicer: RegenThumbnail handler — 즉시 enqueue 후 응답
- worker: regen_thumbnail_job 등록
- generate_thumbnail._build_prompt: 옵션 hint 파라미터 추가, state 의
  regen_hint 픽업해서 'ADDITIONAL DIRECTION FROM REVIEWER' 블록 주입
- publish_node: graph state 의 error_log 를 editorial_articles.error_log
  jsonb 컬럼에 append (기존 누락 버그 fix — soft-fail 정책은 보존)

api-server
- proto/ai.proto sync + decoded_ai_grpc/client.rs regen_thumbnail wrapper
- POST /api/v1/admin/editorial-articles/{id}/regen-thumbnail 핸들러
  - status guard: draft / failed 만 허용
  - editorial_article_events INSERT (step=regen_thumbnail, note=requested
    + hint snippet) → gRPC enqueue

web
- useRegenThumbnail mutation hook + proxy route
- ArticleErrors 컴포넌트: error_log jsonb 배열을 빨간 배너로 렌더
- drafts/[id]/page.tsx: 썸네일 헤더에 ⟳ 아이콘 버튼 + 클릭 시 인라인
  textarea (선택 hint) + 재생성 트리거. 메인 컬럼 상단에 ArticleErrors 배치

Verification
- ai-server: 실패 경로 E2E 통과 (error_log append + event INSERT + hint
  capture), success 경로 SQL 통과 (OpenAI 빌링 한도로 실 호출 검증은 추후)
- api-server: cargo check + fmt --check 통과
- web: typecheck 신규 에러 0건

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(dx): dev-reset PRD seed + pre-push lint fixes (#525)

* chore(dx): dev-reset auto-seeds from PRD when PRD_DB_URL available

- seed-from-prod.sh: add --yes/-y flag for non-interactive mode
- seed-from-prod.sh: delete editorial_pipeline_settings before import to avoid duplicate key error
- Justfile dev-reset: auto-detect PRD_DB_URL from packages/web/.env.local
- Justfile dev-reset: graceful fallback when decoded-backend network is absent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(web): resolve pre-existing lint errors (prettier + eslint)

Auto-fix prettier formatting across admin pages and add eslint-disable
for untyped Supabase entity table access. Attach cause to firecrawl
timeout error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(web): fix prettier format + add .next.bak to prettierignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(api-server): resolve pre-existing clippy errors

- Remove duplicate has_url fields in solutions/tests.rs
- Fix unnecessary cast in gemini_cost.rs
- Allow clippy::disallowed_methods for json!() macro in content_studio

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(api-server): resolve remaining clippy errors for pre-push hook

- Allow disallowed_methods for json!() macro in raw_posts handlers
- Use next_back() instead of last() on DoubleEndedIterator
- Fix doc_lazy_continuation in solutions/spots service
- Allow too_many_arguments for create_solution_for_verify
- Allow type_complexity for test-only InMemoryStorage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(api-server): allow too_many_arguments for create_post_from_raw

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(api-server): resolve final clippy errors (println + too_many_arguments)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(api-server): skip dotenvy in test mode to prevent .env.dev interference

Config tests that verify default values were failing because
dotenvy reloaded .env.dev inside from_env(), overriding remove_var calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(api-server): ignore RUSTSEC-2026-0104 for AWS SDK legacy TLS chain

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* chore(release): backend bump ai=1.6.0 api=0.11.0 [skip ci]

* fix(daily-digest): use single quotes for UUID array literals in psql (#511)

#509 의 verify-stats 섹션이 SQL 오류로 실패. ARRAY["..."] 는 PostgreSQL
에서 identifier (column) 로 해석되어 ARRAY['...']::uuid[] 가 정답.

jq 의 join 구분자도 "," → ',' 로 변경. counts_json 빈 문자열 fallback 추가.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: decoded-ci <ci@decodedcorp.com>
Co-authored-by: thxforall <113906780+thxforall@users.noreply.github.com>
Co-authored-by: philippe <rhkr9693@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bump:patch Bug fixes / internal refactor

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant