09e9f3545f
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
dependency-audit / pip-audit (push) Has been cancelled
159 lines
5.9 KiB
YAML
159 lines
5.9 KiB
YAML
name: Verify Generated Artifacts
|
|
|
|
on:
|
|
schedule:
|
|
# Run at 8 AM UTC daily (2 hours after nightly e2e tests)
|
|
- cron: '0 8 * * *'
|
|
workflow_dispatch: # Allow manual trigger
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
verify:
|
|
name: Verify Artifacts
|
|
runs-on: ubuntu-latest
|
|
if: github.repository == 'teng-lin/notebooklm-py'
|
|
# Secret-bearing job. ``NOTEBOOKLM_AUTH_JSON`` and friends live only in
|
|
# the ``protected-readonly`` GitHub Environment (issue #1009), so the
|
|
# env binding is unconditional — every trigger sees the same secret
|
|
# values. Add a ``required reviewers`` rule on the environment if you
|
|
# want to block workflow_dispatch behind manual approval; scheduled
|
|
# runs would queue too, so the env carries no protection rules today.
|
|
# See docs/development.md → "Workflow secret gates".
|
|
environment: protected-readonly
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: "3.12"
|
|
cache: 'pip'
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # @ v7.0.0
|
|
|
|
- name: Install dependencies
|
|
# `uv sync --frozen` reproduces the lockfile-pinned dep tree. The
|
|
# inline verification script below only touches the public client API
|
|
# (no playwright / pytest / markdownify), so no extras are needed.
|
|
run: uv sync --frozen
|
|
|
|
# Fail-fast preflight. Without this, ``NotebookLMClient.from_storage()``
|
|
# below would die with a confusing "no storage" error when the secret
|
|
# resolves empty (issue #1009).
|
|
- name: Verify auth secret is present
|
|
env:
|
|
NOTEBOOKLM_AUTH_JSON: ${{ secrets.NOTEBOOKLM_AUTH_JSON }}
|
|
NOTEBOOKLM_GENERATION_NOTEBOOK_ID: ${{ secrets.NOTEBOOKLM_GENERATION_NOTEBOOK_ID }}
|
|
shell: bash
|
|
run: |
|
|
if [ -z "${NOTEBOOKLM_AUTH_JSON:-}" ]; then
|
|
echo "::error::NOTEBOOKLM_AUTH_JSON resolved to empty. Env binding or secret config is broken — see docs/development.md → Workflow secret gates."
|
|
exit 1
|
|
fi
|
|
if [ -z "${NOTEBOOKLM_GENERATION_NOTEBOOK_ID:-}" ]; then
|
|
echo "::error::NOTEBOOKLM_GENERATION_NOTEBOOK_ID resolved to empty."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Verify artifacts exist
|
|
env:
|
|
NOTEBOOKLM_AUTH_JSON: ${{ secrets.NOTEBOOKLM_AUTH_JSON }}
|
|
NOTEBOOKLM_GENERATION_NOTEBOOK_ID: ${{ secrets.NOTEBOOKLM_GENERATION_NOTEBOOK_ID }}
|
|
run: |
|
|
uv run python -c "
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
from notebooklm import NotebookLMClient
|
|
|
|
# Type ID to display name mapping
|
|
TYPE_NAMES = {
|
|
1: 'Audio',
|
|
2: 'Report', # Study Guide, Briefing Doc, Blog Post
|
|
3: 'Video',
|
|
4: 'Quiz/Flashcards',
|
|
5: 'Mind Map',
|
|
7: 'Infographic',
|
|
8: 'Slide Deck',
|
|
9: 'Data Table',
|
|
}
|
|
|
|
# Expected type IDs from generation tests
|
|
# Note: Type 2 is Reports (study guide), Type 4 is Quiz+Flashcards
|
|
EXPECTED_TYPES = {1, 2, 3, 4, 5, 7, 8, 9}
|
|
|
|
async def verify():
|
|
async with await NotebookLMClient.from_storage() as client:
|
|
nb_id = os.environ['NOTEBOOKLM_GENERATION_NOTEBOOK_ID']
|
|
print(f'Checking notebook: {nb_id}')
|
|
|
|
# List artifacts
|
|
artifacts = await client.artifacts.list(nb_id)
|
|
print(f'\nTotal artifacts: {len(artifacts)}')
|
|
|
|
# Group by type and status
|
|
by_type = {}
|
|
for a in artifacts:
|
|
key = a._artifact_type
|
|
if key not in by_type:
|
|
by_type[key] = []
|
|
by_type[key].append(a)
|
|
|
|
print('\nArtifacts by type:')
|
|
for t in sorted(by_type.keys()):
|
|
items = by_type[t]
|
|
type_name = TYPE_NAMES.get(t, f'Unknown({t})')
|
|
print(f' {type_name} (type {t}): {len(items)}')
|
|
for a in items:
|
|
status = a.status_str
|
|
variant_info = f', variant={a._variant}' if a._variant else ''
|
|
print(f' - {a.title} ({status}{variant_info})')
|
|
|
|
# Check expected types
|
|
found = set(by_type.keys())
|
|
missing = EXPECTED_TYPES - found
|
|
|
|
print(f'\nExpected types: {len(EXPECTED_TYPES)}')
|
|
print(f'Found types: {len(found)}')
|
|
|
|
if missing:
|
|
missing_names = [TYPE_NAMES.get(t, str(t)) for t in missing]
|
|
print(f'\nWARNING: Missing artifact types: {missing_names}')
|
|
|
|
# Check for completed artifacts
|
|
completed = sum(1 for a in artifacts if a.is_completed)
|
|
processing = sum(1 for a in artifacts if a.is_processing)
|
|
failed = sum(1 for a in artifacts if a.is_failed)
|
|
|
|
print(f'\nStatus summary:')
|
|
print(f' Completed: {completed}')
|
|
print(f' Processing: {processing}')
|
|
print(f' Failed: {failed}')
|
|
|
|
# List notes
|
|
notes = await client.notes.list(nb_id)
|
|
print(f'\nTotal notes: {len(notes)}')
|
|
for n in notes[:10]:
|
|
print(f' - {n.title or \"(untitled)\"}')
|
|
if len(notes) > 10:
|
|
print(f' ... and {len(notes) - 10} more')
|
|
|
|
# Fail if too many failures or no artifacts at all
|
|
if len(artifacts) == 0:
|
|
print('\nERROR: No artifacts found!')
|
|
sys.exit(1)
|
|
if failed > len(artifacts) // 2:
|
|
print(f'\nERROR: Too many failed artifacts ({failed}/{len(artifacts)})')
|
|
sys.exit(1)
|
|
|
|
print('\nVerification complete!')
|
|
|
|
asyncio.run(verify())
|
|
"
|