load_inventory_channels() selected entries via 'youtube_mosaic' in entry
(key presence). save_inventory() always writes a youtube_mosaic column
that the SQL round-trip materializes as an empty dict {} on EVERY entry,
so the filter matched all 18,647 inventory entries and emitted ~18.6k
inline <img> links on a single line. That produced a 4MB v2-docs/index.md
(and 2.9MB docs/index.md), hanging both V1 and V2 index pages in the
browser. A prior revert masked it; the next SQL-backed sync re-broke it.
Fix: only treat an entry as a mosaic channel when youtube_mosaic is a
non-empty dict with a logo image (136 curated channels). Regenerated both
index files via reorganize_mosaic — docs/index.md is now byte-identical
to the last known-good state (52KB); v2-docs/index.md back to 41KB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The date field gh_pushed was written as the literal string 'N/A' when
GitHub data was unavailable (fast_enrich, gemini_utils). That non-date
string then reached datetime.fromisoformat() downstream. Now writes None
(which all readers already handle); gh_license keeps its 'N/A' string
sentinel since it is a displayed value, not a date.
Also hardens the twin fromisoformat() call in agentic_curator's MVQ
penalty check (guards N/A/None and wraps parsing) so a bad value can't
crash a curation batch.
Adds scripts/normalize_gh_pushed.py to clean the 281 existing inventory
entries (run in CI to avoid SQLite float-serialization churn — see
script docstring).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The V2 safety audit emitted hundreds of WARN lines and then aborted
entirely with 'NoneType object has no attribute lower':
- validate_mvq_compliance: gh_pushed of 'N/A' (and historic None/empty)
was passed to datetime.fromisoformat() -> 'Invalid isoformat string';
and gh_stars/stars stored as null hit 'None * int'. Now skips entries
with no usable push date and coalesces None before the star math.
- validate_linguistic_tagging: a null 'language' field caused
None.lower(), which aborted the whole report. Now coalesces to English.
- generate_audit_report: each mandate now runs in isolation so a single
bad entry can no longer kill the entire audit; failures are logged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removed `content-visibility: auto` on broad <ul>/<ol> selectors and
.v2-tag-section. On the slate (dark) theme it produced black flashes
while scrolling and made link lists below the index mosaic appear
collapsed/broken due to intrinsic-size mismatch causing scrollbar jumps.
The tags page already uses collapsed <details> (native render skipping),
so the optimization was redundant and harmful.
Bumped CSS cache-bust to ?v=2.9.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each (category × period) cell in news_digest.json now carries a hash of its
entry pool (sorted URLs). Before calling Gemini, the engine checks:
1. Hash matches stored hash (same inventory entries)
2. Last analyzed within MAX_STALENESS_DAYS=30
Both conditions true → reuse existing ranked list, 0 Gemini API calls.
Only cells where the inventory actually changed (new/removed entries) trigger
a fresh Gemini ranking.
Expected savings: re-runs with code-only changes → 0 API calls (down from 66).
New ingestion batch → only affected categories call Gemini (5-10 of 26 typical).
Stale refresh (>30d) → full refresh at most once per month.
Also:
- Remove day-level GitHub Actions cache for digest (redundant: news_digest.json
is committed to the repo and is the persistent store for _meta)
- Always run news_digest.py in both 04.1 and 09 workflows (it's cheap when 0
Gemini calls are needed)
- Trending badge now reads _meta.last_updated (actual analysis date) instead of
file mtime (which changes on every render/commit)
- Remove invalid extra_head key from v2-mkdocs.yml (left over from rollback)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. Skip page deletion and nav sync in --render-only mode
The CI publisher always uses --render-only but the pruning phase was
deleting pages not regenerated in a given pass (e.g. low-hit pages),
breaking nav references and corrupting the MkDocs build.
2. Protect dimension pages from deletion in full mode
Even in a full (non-render-only) run, pages defined in self.dimensions
are never deleted. Truly orphaned pages (not in dimensions AND not
generated) are the only ones pruned.
3. _sync_enterprise_navigation returns True/False
Deletion is gated on nav sync success. If nav sync fails, deletion
is skipped to prevent inconsistency (deleted files + stale nav).
Also fixes the fragile re.sub(r'nav:.*') regex by using string
indexing instead, preventing accidental truncation of extra_css etc.
4. Deploy workflow V2 sanity check
If V2 build produces fewer than 50 HTML pages or no index.html,
deploy falls back to V1-only instead of overwriting V1 with a
broken V2 build.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The publisher deleted ~30 v2-docs pages still referenced in v2-mkdocs.yml nav,
producing a broken MkDocs build. Rolling back to last known good state (v2.8.4).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove extra_head (not a valid MkDocs config key, was breaking V2 build)
- Fix digital-money.md redirect: finops.md was deleted by publisher,
redirect now points to kubernetes.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>