feat(portfolio-truth): --portfolio-truth-allow-empty-notion carry-forward#121
Conversation
…-forward The nightly portfolio-maintenance job has failed every night since 2026-06-23: it runs `report --portfolio-truth` with no NOTION_TOKEN, and the data-safety guard correctly refuses to overwrite existing truth (142 Notion context rows) with a 0-row headless run. This froze portfolio-truth-latest.json and degraded the personal-ops ghra hub spoke. Add an opt-in --portfolio-truth-allow-empty-notion flag: when live Notion is unavailable, carry forward the prior artifact's per-project advisory context (load_prior_notion_context) instead of dropping to zero, so the refresh updates risk/activity/git signals without losing advisory data. The flag suppresses the drop guard (operator explicitly opted into empty-Notion publishing); source_summary records notion_context_carried_forward for provenance; a logger.warning fires on carry-forward so a real Notion regression stays observable. Default (flag off) behavior is unchanged: the guard still protects against silent Notion drop. Tests: reconstructor units, publish-level carry-forward + guard-still-fires + flag-suppresses-guard, CLI report-subcommand parse, and end-to-end via main().
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 323b26902f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| legacy_rows = load_legacy_registry_rows(legacy_registry_path) | ||
| notion_context = load_safe_notion_project_context() if include_notion else {} | ||
| notion_context_carried_forward = False | ||
| if include_notion and not notion_context and notion_context_fallback: |
There was a problem hiding this comment.
Preserve intentional empty Notion results
When this flag is used with a valid Notion token against a reachable Projects data source that legitimately has zero context rows, load_safe_notion_project_context() returns {} and this condition treats that the same as an unavailable Notion bootstrap. The publish then restores every advisory row from portfolio-truth-latest.json, so intentional row removals or an intentionally empty Notion source cannot be reflected while the scheduled flag is enabled; the fallback should only apply when the live load is unavailable, not when it succeeds empty.
Useful? React with 👍 / 👎.
Problem
The nightly
portfolio-maintenancelaunchd job has failed every night since 2026-06-23. It runsreport saagpatel --portfolio-truthwith noNOTION_TOKEN, and the data-safety guard inportfolio_truth_publish.pycorrectly refuses to overwrite the existingoutput/portfolio-truth-latest.json(142 Notion context rows) with a 0-row headless run. The result: portfolio truth froze at 2026-06-19, and the downstream personal-opsghrahub spoke degraded to 8 days stale.The guard's error message already pointed at the intended escape ("run with an explicit no-Notion path"), but that path was never actually implemented as a flag.
Fix
Add an opt-in
--portfolio-truth-allow-empty-notionflag (on both thereportsubcommand and the legacy top-level parser). When live Notion is unavailable, it carries the prior artifact's per-project advisory context forward (load_prior_notion_context) instead of dropping to zero, so a headless refresh updates risk / activity / git signals without losing advisory data.notion_contextseam inbuild_portfolio_truth_snapshot, gated oninclude_notion.source_summary.notion_context_carried_forwardrecords provenance, and alogger.warningfires whenever carry-forward activates so a real Notion regression stays observable.Verification
ruff check src/ tests/clean.report-subcommand parse, and an end-to-end run viamain().load_prior_notion_contextagainst the real frozen artifact: reconstructs 131 carry-forward rows.Activation (follow-up, not in this PR)
After merge, the nightly job command needs
--portfolio-truth-allow-empty-notionappended, and the checkout the job runs from must carry this change.