Skip to content

🔧 Split SEO metadata write-back from gates - #24

Merged
Jwaminju merged 10 commits into
mainfrom
codex/seo-metadata-apply-split
Jul 24, 2026
Merged

🔧 Split SEO metadata write-back from gates#24
Jwaminju merged 10 commits into
mainfrom
codex/seo-metadata-apply-split

Conversation

@Jwaminju

@Jwaminju Jwaminju commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

요약

SEO 평가 gate와 metadata 생성/적용 흐름을 분리합니다.

  • seo.json은 기존처럼 SEO 평가/루브릭 gate 결과만 담도록 유지했습니다.
  • metadata-suggestion.jsonERROR, PARTIAL, SKIPPED가 SEO gate failure로 번지지 않게 했습니다.
  • PARTIAL 상태에서도 safe frontmatter 필드(title, description, categories, image)는 자동 적용합니다.
  • canonical, hreflang, json_ld처럼 정책 판단이 필요한 필드는 정책값이 명확할 때만 적용합니다.
  • 자동 metadata apply job에서는 OPENAI_API_KEY를 주입하지 않아 deterministic/idempotent하게 동작하도록 했습니다.
  • metadata-suggestion.json이 없으면 workflow를 실패시키지 않고 SKIPPED, changed=false로 처리합니다.
  • PR report comment에 SEO metadata suggestion 요약을 접이식 섹션으로 표시합니다.
  • trusted reviewer가 metadata apply 댓글을 남기면 partial suggestion의 safe fields를 frontmatter에 적용할 수 있게 했습니다.
  • 정책값이 함께 제공되면 canonical / hreflang도 적용할 수 있게 했습니다.
  • metadata-only mutation은 deterministic 검증으로 확인해, full SEO rubric 재실행 때문에 push가 막히지 않게 했습니다.
  • SEO 모듈 담당자에게 전달할 input/output 기준 문서를 추가했습니다.

주요 변경

PR agent

  • SEO_OPENAI_REQUIRED를 평가/metadata 양쪽에 동시에 쓰던 흐름을 분리했습니다.
    • SEO_RUBRIC_OPENAI_REQUIRED: SEO 루브릭 gate용
    • SEO_METADATA_OPENAI_REQUIRED: metadata suggestion 생성용
  • scripts/hf_agent/apply_metadata_suggestion.py 추가
    • metadata-suggestion.json을 읽습니다.
    • suggestion 파일이 없으면 SKIPPED, changed=false로 종료합니다.
    • 자동 review 경로에서는 PARTIAL 상태여도 safe frontmatter fields를 적용합니다.
    • 같은 후보가 이미 frontmatter에 반영되어 있으면 NO_CHANGE, changed=false로 종료합니다.
    • trusted comment에서 명시적으로 metadata apply를 요청하면 PARTIAL 상태에서도 safe fields를 적용할 수 있습니다.
    • comment에 정책값이 있으면 canonical / hreflang도 적용할 수 있습니다.
  • HF Agent / Apply Metadata Suggestion job 추가
    • review/verifier가 green일 때만 실행됩니다.
    • 이 job에는 OPENAI_API_KEY를 주입하지 않습니다. 자동 재발화 경로에서 LLM 후보가 매번 달라져 commit loop가 생기는 것을 막기 위함입니다.
    • metadata 제안을 만들 때는 OpenAI rubric gate를 다시 돌리지 않습니다. SEO gate는 이미 앞선 review/verifier job에서 통과한 상태이기 때문입니다.
    • 적용 후 SEO/quality를 재검증하고 🔧 Update SEO metadata 커밋을 push합니다.
  • feedback mutation workflow 확장
    • trusted PR comment가 metadata apply / SEO metadata apply / 메타데이터 적용을 포함하면 metadata apply intent로 처리합니다.
    • PARTIAL suggestion에서는 title, description, categories, image만 safe fields로 적용합니다.
    • comment에 정책값을 명시하면 canonical / hreflang도 적용할 수 있습니다.
metadata apply
target_url: https://hugging-face-krew.github.io/sample/
source_url: https://huggingface.co/blog/sample
canonical_policy: self
translation_indexing: independent
target_locale: ko
source_locale: en

SEO metadata output

  • results/seo.md, results/seo.json은 SEO gate/report용으로 유지합니다.
  • results/metadata-suggestion.json은 metadata 생성 모듈 output으로 분리합니다.
  • PR comment에는 metadata-suggestion.json의 status, candidate, policy decision, warnings 요약만 표시합니다.
  • metadata-suggestion.json에는 계속 skill, conclusion을 넣지 않습니다.
  • metadata-suggestion.json 파일 자체는 블로그 repo에 commit하지 않습니다. 실제 SEO 적용은 _posts/*.md frontmatter 변경으로 남습니다.

SEO 모듈 담당자 리뷰 포인트

SEO 모듈 쪽에서 새로 큰 작업을 해야 하는 상태는 아닙니다. 이미 metadata-suggestion.json을 생성하고 있으므로, 이 PR에서는 PR agent가 그 산출물을 어떻게 소비할지 정리했습니다.

리뷰할 때는 아래를 봐주시면 됩니다.

  • docs/seo-metadata-module-io.md
    • metadata-suggestion.json 필드 의미가 SEO 모듈 의도와 맞는지
    • PARTIAL 상태에서도 safe frontmatter field 후보를 채워주는 기준이 맞는지
    • 정책 결정이 필요한 경우 needs_policy_decision으로 남기는 기준이 맞는지
  • skills/seo/tools/metadata_suggestion.py
    • candidate.title, candidate.description, candidate.categories, candidate.image가 deterministic fallback에서도 안정적으로 나오는지
    • target_files, commit_message가 정책 field까지 자동 적용 가능한 케이스에만 들어가는지
  • scripts/hf_agent/apply_metadata_suggestion.py
    • PR agent가 _posts/*.md frontmatter만 수정하도록 제한한 것이 적절한지
    • missing suggestion / idempotent no-change 처리가 적절한지
  • scripts/hf_agent/handle_pr_feedback.py
    • trusted comment 기반 metadata apply intent 처리와 safe field 범위가 적절한지

E2E

기존 번역 PR에서 검증했습니다.

1. Review/report E2E

2. Trusted comment metadata apply E2E

3. Idempotency E2E after reviewer feedback

4. Failed-safe E2E

참고: #24 최신 상태에서 #168에도 workflow를 돌려봤지만, independent verifier가 실패하여 metadata apply job까지 진행되지 않았습니다. 이는 “verifier가 green일 때만 metadata apply”라는 현재 조건과 일치합니다.

테스트

/Users/mjjwa/.local/bin/uv run --python 3.11 --with pytest --with pyyaml --with markdown --with beautifulsoup4 pytest tests/test_apply_metadata_suggestion.py tests/test_pr_review_workflow.py -q
# 20 passed

/Users/mjjwa/.local/bin/uv run --python 3.11 --with pytest --with pyyaml --with markdown --with beautifulsoup4 pytest tests skills/seo/tests -q
# 185 passed

@sim-so sim-so left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

리뷰 요약

전체적으로 머지 가능한 상태입니다. gate와 metadata write-back을 분리한 핵심 의도가 코드로 잘 구현되어 있고, 테스트와 계약 문서(docs/seo-metadata-module-io.md)도 꼼꼼합니다. 다만 1번 항목은 머지 전에 한 번 확인해 주세요.


✅ 의도대로 동작 확인된 부분

  1. metadata-suggestion.json은 블로그 repo에 커밋되지 않음 — push 단계가 git add -- "${{ inputs.file_path }}"_posts/*.md만 stage하고, suggestion JSON은 target/ 체크아웃 밖(results/)에 생성되어 애초에 커밋 대상이 될 수 없습니다.
  2. 실제 SEO 적용은 frontmatter 변경으로만 남음metadata.apply()가 frontmatter/body를 분리한 뒤 body는 그대로 두고 title/description/categories/image/canonical/hreflang만 반영합니다. 번역 본문 fidelity를 해치지 않습니다.
  3. advisory가 gate로 번지지 않음 — metadata suggestion의 ERROR/PARTIAL/SKIPPED가 SEO conclusion: fail로 이어지던 로직이 제거되어, "advisory는 report-only" 정책과 일치합니다.

🔴 머지 전 확인 필요 (1번)

자동 apply 후보의 비결정성 → 재발화(re-trigger) 루프 / Discord ready 미발화 위험

  • notify_readyneeds.metadata_apply.outputs.changed != 'true' 조건으로 바뀌어, metadata를 커밋한 run에서는 Discord ready를 보내지 않고 후속 run에서 보내는 설계입니다.
  • 이 설계가 성립하려면 후속 run의 apply가 반드시 changed == false(idempotent) 여야 합니다.
  • 그런데 metadata_apply 잡은 OPENAI_API_KEY를 넘기고, metadata_suggestion.py--openai-required와 무관하게 OPENAI_API_KEY만 있으면 LLM으로 title/description 후보를 생성합니다. → 재발화 run에서 후보가 미세하게 달라지면 changed=true가 반복되어 커밋 루프 + Discord ready 영구 미발화 가능성이 있습니다.
  • 참고: 자동 잡은 --manifest를 넘기지 않아 대개 PARTIAL로 SKIP될 가능성이 높지만(실제로는 거의 안 터질 수 있음), target repo가 정책 매니페스트를 실어 READY에 도달하면 위험이 살아납니다.
  • 권장 확인/보강:
    1. 실제 pull_request 트리거에서 자동 재발화 경로가 idempotent한지 확인 (E2E는 workflow_dispatch라 이 경로가 검증되지 않았을 수 있음)
    2. apply/verify 경로에서 후보를 결정적으로 만들기 (예: 이 잡에서 OPENAI_API_KEY 미주입 → deterministic fallback), 또는
    3. "현재 frontmatter가 후보와 이미 동일하면 skip" short-circuit, 또는 재발화 횟수 상한

🟡 개선 제안 (머지 차단 아님)

  • metadata_apply 잡 실패 시 전체 lifecycle이 red — suggestion 파일이 없으면 FileNotFoundError로 잡이 실패해, gate를 다 통과해도 워크플로가 실패로 표시될 수 있습니다. 파일 부재를 graceful하게 SKIP(changed=false) 처리하면 안전합니다.
  • 후속 스키마 — 앞으로 논의한 advisory: { frontmatter: [...], content: [...] } + auto_apply_candidate 형태는 이 PR 범위가 아니며, 추가 시 metadata_suggestion.py output shape + publish_pr_comment._metadata_summary + apply_metadata_suggestion.py를 함께 바꾸면 됩니다. (후속 이슈 권장)

👍 좋았던 점

  • SEO_OPENAI_REQUIREDSEO_RUBRIC_OPENAI_REQUIRED / SEO_METADATA_OPENAI_REQUIRED 분리가 깔끔하고 하위호환까지 챙김
  • failed-safe(검증 실패 시 push 안 함)가 스텝 순서상 올바르게 보장됨
  • 테스트 커버리지(intent 분기, safe-field 제한, policy override, PARTIAL/READY)가 촘촘함

name: HF Agent / Request Human Merge
needs: finalize
needs: [finalize, metadata_apply]
if: ${{ always() && needs.finalize.result == 'success' && needs.metadata_apply.outputs.changed != 'true' }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[머지 전 확인 필요] 이 조건은 "metadata 커밋이 발생한 run에서는 Discord ready를 보내지 않고, bot push로 재발화된 후속 run에서 보낸다"는 설계에 의존합니다.

  • 성립 전제: 후속 run의 apply가 반드시 changed == false(idempotent).
  • 위험: 후보가 LLM으로 생성되어 재발화 run마다 title/description이 달라지면 changed=true가 반복 → 커밋 루프 + Discord ready 영구 미발화 가능성.
  • 확인 요청: 실제 pull_request 트리거에서 재발화 경로가 idempotent한지 (E2E는 workflow_dispatch라 이 경로 미검증 가능성). 필요하면 "frontmatter가 후보와 동일하면 skip" short-circuit 또는 재발화 상한 추가 권장.

changed: ${{ steps.apply.outputs.changed }}
env:
GITHUB_TOKEN: ${{ secrets.KREW_BOT_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

위 재발화 이슈의 근본 원인 지점입니다. 이 잡이 OPENAI_API_KEY를 주입하면 metadata_suggestion.py--openai-required 없이도 LLM으로 후보(title/description)를 생성합니다.

자동 apply의 idempotency를 보장하려면 이 경로에서 후보를 결정적으로 만드는 방안을 검토해 주세요 (예: 이 잡에서 OPENAI_API_KEY를 넘기지 않고 deterministic fallback 사용).

- name: Apply approved metadata suggestion
id: apply
run: |
python -m hf_agent.apply_metadata_suggestion \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[개선 제안] results/metadata-suggestion.json이 없으면 apply_metadata_suggestionFileNotFoundError로 실패하고, 잡 실패 → gate를 다 통과해도 전체 워크플로 conclusion이 red가 될 수 있습니다.

review==success 게이팅이 대부분 막아주지만, suggestion 파일 부재를 graceful하게 SKIP(changed=false) 처리하면 더 견고합니다.

@Jwaminju

Copy link
Copy Markdown
Collaborator Author

소현님 리뷰 반영했습니다.

  • metadata_apply job에서 OPENAI_API_KEY / OPENAI_MODEL 주입을 제거했습니다. 자동 apply 경로는 deterministic fallback만 사용합니다.
  • PARTIAL 상태에서도 safe frontmatter fields(title, description, categories, image)만 자동 적용하도록 --allow-partial-safe-fields를 켰습니다.
  • metadata-suggestion.json이 없으면 workflow 실패 대신 SKIPPED, changed=false로 처리합니다.
  • 같은 metadata 후보가 이미 frontmatter에 반영되어 있으면 NO_CHANGE, changed=false가 되도록 idempotency 테스트를 추가했습니다.
  • #172 기준 E2E를 실제 feedback revision으로 다시 돌렸습니다: https://github.com/Hugging-Face-KREW/hf-workflow/actions/runs/30101109441
    • metadata apply job: success
    • Verify metadata update: skipped
    • Push metadata update: skipped
    • PR head SHA unchanged
    • finalize lifecycle: success
    • request human merge: success

테스트:

/Users/mjjwa/.local/bin/uv run --python 3.11 --with pytest --with pyyaml --with markdown --with beautifulsoup4 pytest tests/test_apply_metadata_suggestion.py tests/test_pr_review_workflow.py -q
# 20 passed

/Users/mjjwa/.local/bin/uv run --python 3.11 --with pytest --with pyyaml --with markdown --with beautifulsoup4 pytest tests skills/seo/tests -q
# 185 passed

@Jwaminju
Jwaminju force-pushed the codex/seo-metadata-apply-split branch from a704a74 to e475a14 Compare July 24, 2026 15:17
@Jwaminju
Jwaminju merged commit e9dbf27 into main Jul 24, 2026
1 check passed
@Jwaminju
Jwaminju deleted the codex/seo-metadata-apply-split branch July 24, 2026 15:26
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.

2 participants