name: 04.1. V2 Publisher on: push: branches: - develop paths: - 'docs/**' workflow_run: workflows: ["03.3. V2 Video Hub Builder"] types: - completed workflow_dispatch: inputs: restore_cache: description: 'Restore inventory from cache (Warning: May overwrite recent manual changes)' required: false type: boolean default: false permissions: contents: write pull-requests: write concurrency: group: develop-git-write-lock cancel-in-progress: false jobs: publish-v2: runs-on: ubuntu-latest if: | github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, 'bot/v2-elite-sync')) || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') steps: - name: Repository Synchronization uses: actions/checkout@v7 with: ref: develop fetch-depth: 0 - name: Python 3.11 Environment Provisioning uses: actions/setup-python@v6 with: python-version: '3.11' cache: 'pip' - name: Restore Incremental Inventory Cache if: ${{ github.event.inputs.restore_cache == 'true' }} uses: actions/cache/restore@v6 with: path: data/inventory.yaml key: inventory-v2-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | inventory-v2- - name: Installation of Dependencies run: | pip install --no-cache-dir pydantic PyGithub httpx fake-useragent pytz python-dotenv pyyaml tenacity - name: Execute Video Portal Generator env: PYTHONPATH: ${{ github.workspace }} run: | python -u src/v2_video_portal.py - name: Execute Mosaic Reorganization env: PYTHONPATH: ${{ github.workspace }} run: | python src/reorganize_mosaic.py - name: Run Deduplication Scan env: PYTHONPATH: ${{ github.workspace }} run: | python -u -c "import asyncio; from src.dedup import run_dedup; asyncio.run(run_dedup(dry_run=False))" || echo "Dedup scan skipped" - name: Run Enrichment Pipeline env: PYTHONPATH: ${{ github.workspace }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | python -u -m src.enrichment || echo "Enrichment pipeline skipped (no token or error)" - name: Generate News Digest (Incremental) env: PYTHONPATH: ${{ github.workspace }} GEMINI_API_KEY_1: ${{ secrets.GEMINI_API_KEY_1 }} GEMINI_API_KEY_2: ${{ secrets.GEMINI_API_KEY_2 }} run: | # The engine loads news_digest.json from the repo, checks per-cell # hashes, and only calls Gemini for categories with new entries or # results older than MAX_STALENESS_DAYS (30). Typical re-runs with # no inventory changes cost 0 Gemini API calls. python -u -m src.news_digest || echo "News digest generation skipped (no API key or error)" - name: Generate RSS Feed env: PYTHONPATH: ${{ github.workspace }} run: | python -u -m src.rss_generator || echo "RSS generation skipped" - name: Run V2 Publisher (Render-Only) env: PYTHONPATH: ${{ github.workspace }} PYTHONUNBUFFERED: "1" run: | python -u -m src.v2_optimizer --render-only if [ -f v2_safety_report.md ]; then cat v2_safety_report.md >> pr_description.md else echo "No safety report generated." > pr_description.md fi - name: Consolidate README Metrics env: PYTHONPATH: ${{ github.workspace }} run: | python -m src.readme_updater python -m src.safety_readme - name: Commit and Push V2 Elite Updates env: PYTHONPATH: ${{ github.workspace }} run: | git config --global user.name "Nubenetes Bot" git config --global user.email "bot@nubenetes.com" git add -A if git diff --staged --quiet; then echo "No automated changes to commit." exit 0 fi git commit -m "feat: sync V2 elite curated edition and README metrics [skip ci]" OUR=$(git rev-parse HEAD) # Deterministic publish loop (replaces the fragile rebase + --ours/--theirs # self-healing that could hang on a conflict and discard the bot's metrics). # The render is authoritative for its generated surface, so each attempt: # 1. hard-resets to the freshest remote tip (no conflict possible), # 2. overlays our generated outputs verbatim (docs/, v2-docs/, inventory, # news_digest) — source configs in data/ are left untouched, # 3. regenerates README.md (a pure function of the inventory) on top, # 4. commits and pushes; on rejection it retries from the new tip. GENERATED="docs v2-docs data/inventory.yaml data/inventory.sql data/news_digest.json" for i in {1..5}; do echo "Publish attempt $i/5..." git fetch origin develop git reset --hard origin/develop git checkout "$OUR" -- $GENERATED python -m src.readme_updater python -m src.safety_readme git add -A if git diff --staged --quiet; then echo "Remote already matches this render. Nothing to push." exit 0 fi git commit -m "feat: sync V2 elite curated edition and README metrics [skip ci]" if git push origin develop; then echo "Push succeeded on attempt $i." exit 0 fi echo "Attempt $i rejected — remote advanced, retrying from fresh tip..." sleep $((i * 3)) done echo "::error::V2 publish push failed after 5 attempts (persistent write contention on develop)." exit 1 - name: Create or Update Promotion PR to Master env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git pull origin develop --rebase # Check if a PR already exists PR_EXISTS=$(gh pr list --head develop --base master --json number --jq '.[0].number') if [ -z "$PR_EXISTS" ]; then echo "Creating new PR from develop to master..." gh pr create \ --base master \ --head develop \ --title "🚀 Release: Agentic V2 Portal Update" \ --body "Automated promotion from develop to master. This PR includes updated V2 Elite portal content, AI-enriched metadata, and synchronized README metrics. Please review and merge to deploy to production." else echo "PR #$PR_EXISTS already exists. No action needed (it will auto-update with the push)." fi - name: Upload Architecture and Decision Logs if: always() uses: actions/upload-artifact@v7 with: name: v2-agentic-logs path: | v2_file_audit.md v2_decision_matrix.md v2_safety_report.md if-no-files-found: ignore - name: Persist Incremental Inventory to Cache if: always() uses: actions/cache/save@v6 with: path: data/inventory.yaml key: inventory-v2-${{ github.run_id }}-${{ github.run_attempt }}