News & Results Refresh Loop
Keep dashboard-stock verdicts current. The detector finds material events; you refresh the dossier head window and — for an earning-power event — re-estimate IV, re-derive the verdict, and (under the re-rank guard) re-rank the LOCKED dashboard. The autonomy ladder never mutates the dashboard silently: every write goes through a sanctioned single-column writer (col N / col L) or the guarded full re-rank (82_rerank_guard.py: 3-tab backup + dry-run-diff + ticker-keyed checksum + auto-revert), and a FOREVER promotion always stops at the human gate.
Sweep universe = the FULL Action Dashboard (~241 names), derived LIVE from the dashboard each cycle (col B data rows, legend excluded), unioned with the held set so owned-but-off-dashboard names are never dropped. build_sweep_universe.py re-derives data/manifests/dashboard_universe.json from the live sheet at the start of every run, so a newly-onboarded dashboard row auto-joins the sweep next cycle with no manual step. The resolver exposes sweep_tickers() (dashboard ∪ held; degrades dashboard→held→legacy) for the sweep, and keeps held_tickers() (owned-only) for where the owned distinction matters (e.g. the AVOID-owned→SELL col-N word). The builder is READ-ONLY on the dashboard.
Golden rules (never violate)
- Only the HEAD WINDOW changes (frontmatter snapshot + one-line thesis, first ~4,000 chars). Research PROSE is frozen. Never tail-append —
26_build_lancedb_index.py embeds one ~4,000-char head vector, so tail edits are invisible (the Repco-CBI stale-flag lesson). The structured ## One-line thesis is what col N renders: VERDICT · ✅ best · ⚠️ worst · 📰 <date>: <signal> — the 📰 segment is verdict-NEUTRAL reportage from event_index.sqlite (coln_news_segment.py), never fabricated, always after the verdict word.
- Autonomy ladder — L0/L1 auto; L2 IV/conviction/verdict-DEMOTION/re-rank AUTO under the guard; FOREVER-promotion the SOLE gate (user override, 2026-06-25):
- L0 — stamp
last_event_checked: <today> on every swept dossier (proves "checked, nothing material" vs "never checked"). Auto.
- L1 — a one-line catalyst / "Why" refresh, NO grade change. Auto-apply; flows to the Sheet "Why" / col N via
extract_dashboard_why.py + 79_publish_col_n.py.
- COL-N IS PUBLISHED FOR EVERY ROW (user directive 2026-06-26) — including AVOID / board-contradiction /
needs_review names, analysed and filled exactly like any other stock. The binding Munger/Buffett CIO (vault) verdict is PRIMARY and is what col-N shows; the 56-director board panel is ADVISORY and NEVER vetoes col-N publication. 79 publishes whenever a VALID col-N can be synthesized (verdict-word + ✅ best + ⚠️ worst, optional 📰); the ONLY skips are verdict==NEEDS_REVIEW, a genuinely THIN dossier (BOTH best AND worst NEEDS_REVIEW), no rendered string, or head_overflow. A board contradiction / governance flag / within-tier needs_review does NOT block col-N — it is a CONTESTED GRADE that still publishes the current verdict's col-N and is RECORDED for human adjudication (78's board_contradictions + 81_audit_dashboard_consistency.py's report), not vetoed. 78 (checker) Gate 7 records the board (mis)alignment as a non-blocking signal; it never fails verification on a contradiction.
- L2 — IV change /
conviction_level change / verdict DEMOTION / within-tier flip / MoS recompute. NOW AUTO-applies AND auto-re-ranks the LOCKED dashboard via the guarded loop: an earning-power event passes coln_iv_event_gate.py → 71_refresh_orchestrator.py runs the targeted Stage-3 re-grade (binding consult + 80_estimate_iv.py + conviction gates + MoS recompute + verdict re-derive + board sanity-check) → re-freezes the head → 73_refresh_cycle.sh stage 3c invokes 82_rerank_guard.py --tag headwindow (once per cycle, after the reindex) to re-rank + publish behind a 3-tab backup + dry-run-diff + ticker-keyed checksum + auto-revert + maker≠checker. Stage 3c fires only when RERANK_AUTO=1 AND the post-reindex brain is healthy (SKIP_PUBLISH≠1) AND ≥1 L1 was applied this cycle. RERANK_AUTO is ENABLED in the LaunchAgent plist (2026-06-25) — auto-re-rank is LIVE on the scheduled loop (capability shipped PR #33, wired into 73 stage 3c, supervised first-enable done; first guarded re-rank applied: AWL→WATCH). The code default remains OFF (so a manual run without the env var dry-runs + asserts + prints the order-diff and STOPS; no live re-rank), but the scheduled cron now runs with RERANK_AUTO=1 set in the plist. 71 itself NEVER calls 82 — the re-rank is once-per-cycle in 73, not per IV event.
- SOLE human gate (for the GRADE, not for col-N) — a FOREVER promotion (Stage-4 adversarial inversion a cron can't do), plus any
needs_review / board-contradiction that would change the GRADE: routes to data/state/pending_reviews/<TICKER>.json, NEVER auto-applied. NOTE: this human gate is about the verdict/conviction GRADE only — col-N is still published for the current verdict of these contested rows (the board is advisory; see the L1 col-N rule above). A board-contradiction queues the GRADE for adjudication; it does NOT withhold the row's col-N.
- IV = earning power only. Price / FII / MF flows / ratings / buy-calls move live MoS + Upside ONLY —
coln_iv_event_gate.py returns False for them, so they NEVER trigger an IV re-grade or a re-rank.
- The vault is BINDING. Any conviction/verdict delta must be supported by
32_consult_brain.py --corpus binding citations (real slugs only, no fabrication).
- Maker ≠ checker. The refresh writer and the verifier are separate (Phase 4 scripts
71/72). Until those exist, run the refresh manually and verify in a separate pass.
- Never fabricate an event. Every event row carries a dated source URL. If the detector finds nothing, that is success — stamp L0 and stop. No early-exit "empty = bug".
- Recall canary is a hard floor. After any reindex, Recall@10 must hold ≥0.95; auto-revert the index on any drop.
Autonomy-ladder ratification. Ratified 2026-07-03 (PW-13): the daily loop auto-consults fresh (≤2d)
RED+MATERIAL events, hard-capped at CYCLE_CONSULT_BUDGET=25/cycle, RED-first-oldest-first. L1 auto-applies;
L2 IV/conviction/verdict changes including upgrades up through COMPOUND auto-apply under 82_rerank_guard.
FOREVER promotion remains the sole permanent human gate. Backlog (>2d) events consult only under DRAIN_GO=1
(founder-gated). The value-digest reports these changes (email); it never grades.
Workflow (one cycle)
- Build sweep universe —
build_sweep_universe.py re-derives data/manifests/dashboard_universe.json from the LIVE dashboard col B (data rows only; legend excluded; aliases resolved e.g. ZOMATO→ETERNAL). READ-ONLY on the dashboard, degrade-safe (keeps the last good manifest on a failed read). Runs FIRST in 73_refresh_cycle.sh so onboarded names auto-join this cycle.
- Detect —
70_detect_material_events.py over the sweep universe (sweep_tickers() = full dashboard ∪ held, ~241+) → data/state/event_index.sqlite. Materiality from data/config/event_taxonomy.json (RESULTS / DEAL / FILING / GOVERNANCE → INFO / MATERIAL / RED; GOVERNANCE-RED escalates). Recency window = since max(graded, last_event_checked). (--universe prints the swept set + dump coverage.)
- Triage — for each ticker: no MATERIAL event → L0 stamp only. MATERIAL → continue.
- Re-grade (head-window) — consult the binding brain (
32_consult_brain.py --corpus binding --step conviction|margin-of-safety|verdict), reconcile with the event, decide the delta tier. Earning-power event? coln_iv_event_gate.py arms the targeted Stage-3 IV re-grade in 71 (re-run 80_estimate_iv.py price-free + conviction gates + MoS/buy_below recompute + verdict re-derive + board sanity-check).
- Write back — L1: head-window edit via
apply_head_window_grade.py (HEAD_LIMIT=4000) → col N re-published (77→79). L2 IV/conviction/verdict-DEMOTION: 71 re-freezes the head with the new grade (under --apply), then 73 stage 3c invokes 82_rerank_guard.py --tag headwindow to re-rank + publish the whole set — gated by RERANK_AUTO=1 (default-OFF → dry-run-diff + assert + STOP) AND brain-healthy post-reindex (SKIP_PUBLISH≠1) AND ≥1 L1 applied this cycle. A stale live verdict/conviction cell (which the re-rank CARRIES byte-for-byte and 82 asserts against the dossier oracle) is first reconciled by the sanctioned col-D/col-M writer sync_col_dm.py → write_col_dm.mjs (default dry-run; COL_DM_DASHBOARD_WRITE=1 to write). FOREVER-promotion / needs_review / board-contradiction: proposal JSON to pending_reviews/, NOT the dossier head, NOT the dashboard.
- Reindex + canary —
26_build_lancedb_index.py; confirm Recall@10 ≥0.95 or auto-revert.
- Log — append a 🔄 Refresh-Log row (Ticker · Event date · Type · Severity · Source · Status · Days stale · Proposed Δ). When the re-rank guard is armed, the LOCKED dashboard is re-ranked through
82 (guarded + auto-revert); otherwise it is untouched and the proposed Δ waits.
- Stop condition — every swept ticker (full dashboard ∪ held)
last_event_checked == today AND ingest clean AND no schema/Recall failure AND every MATERIAL event is {stamped | applied-L1 | queued-L2}.
Human gate (the sole remaining one: FOREVER promotion)
Most L2 changes (IV, conviction, verdict DEMOTION, within-tier, MoS) now auto-apply and auto-re-rank under the guard. The sole human gate is a FOREVER promotion (plus a needs_review / board-contradiction that would change the GRADE) → queued to pending_reviews/<TICKER>.json. That gate is about the GRADE only — col-N for these contested rows is still published for the current verdict (the board panel is advisory and never vetoes col-N; a board-contradiction queues the grade for adjudication, it does NOT withhold the row's col-N). The user approves a queued proposal with one apply_head_window_grade.py <approved>.json run → then a WHOLE-SET re-rank (per stock-onboarding HARD RULE #2, now wired into the loop at 73 stage 3c via 82_rerank_guard.py). Kill-switch: loop-pause-all in STATE.md → scheduled runs no-op. RERANK_AUTO=1 is ENABLED in the LaunchAgent plist (2026-06-25) — the scheduled loop's stage 3c re-ranks live (guarded + auto-revert). The code default is still OFF, so a manual 82 run without the env var dry-runs + asserts + STOPS; the plist supplies RERANK_AUTO=1 for the cron.
Scope & cadence
FULL Action Dashboard (~241 names) ∪ held, DAILY (Phase 5 LaunchAgent 10:00 IST — com.invest.news-refresh StartCalendarInterval Hour 10; the separate price-alerts agent runs later at 16:00). The universe is re-derived LIVE from the dashboard each cycle (build_sweep_universe.py → dashboard_universe.json), so onboarded names auto-join with no manual step. Held-only (off-dashboard) names stay in via the union.
See REFERENCE.md for the exact commands, SQLite schema, taxonomy, file map, and phase status.
1---2name: news-results-refresh3description: News & Results Refresh Loop4---56# News & Results Refresh Loop78Keep dashboard-stock verdicts current. The detector finds material events; you refresh the dossier head window and — for an earning-power event — re-estimate IV, re-derive the verdict, and (under the re-rank guard) re-rank the LOCKED dashboard. The autonomy ladder never mutates the dashboard *silently*: every write goes through a sanctioned single-column writer (col N / col L) or the guarded full re-rank (`82_rerank_guard.py`: 3-tab backup + dry-run-diff + ticker-keyed checksum + auto-revert), and a FOREVER *promotion* always stops at the human gate.910**Sweep universe = the FULL Action Dashboard (~241 names), derived LIVE from the dashboard each cycle** (col B data rows, legend excluded), unioned with the held set so owned-but-off-dashboard names are never dropped. `build_sweep_universe.py` re-derives `data/manifests/dashboard_universe.json` from the live sheet at the start of every run, so a newly-onboarded dashboard row **auto-joins the sweep next cycle with no manual step**. The resolver exposes `sweep_tickers()` (dashboard ∪ held; degrades dashboard→held→legacy) for the sweep, and keeps `held_tickers()` (owned-only) for where the owned distinction matters (e.g. the AVOID-owned→SELL col-N word). The builder is READ-ONLY on the dashboard.1112## Golden rules (never violate)131. **Only the HEAD WINDOW changes** (frontmatter snapshot + one-line thesis, first ~4,000 chars). Research PROSE is frozen. Never tail-append — `26_build_lancedb_index.py` embeds one ~4,000-char head vector, so tail edits are invisible (the Repco-CBI stale-flag lesson). The structured `## One-line thesis` is what col N renders: `VERDICT · ✅ best · ⚠️ worst · 📰 <date>: <signal>` — the 📰 segment is verdict-NEUTRAL reportage from `event_index.sqlite` (`coln_news_segment.py`), never fabricated, always after the verdict word.142. **Autonomy ladder — L0/L1 auto; L2 IV/conviction/verdict-DEMOTION/re-rank AUTO under the guard; FOREVER-promotion the SOLE gate (user override, 2026-06-25):**15 - **L0** — stamp `last_event_checked: <today>` on every swept dossier (proves "checked, nothing material" vs "never checked"). Auto.16 - **L1** — a one-line catalyst / "Why" refresh, NO grade change. Auto-apply; flows to the Sheet "Why" / col N via `extract_dashboard_why.py` + `79_publish_col_n.py`.17 - **COL-N IS PUBLISHED FOR EVERY ROW** (user directive 2026-06-26) — including AVOID / board-contradiction / `needs_review` names, analysed and filled exactly like any other stock. The **binding Munger/Buffett CIO (vault) verdict is PRIMARY and is what col-N shows**; the **56-director board panel is ADVISORY and NEVER vetoes col-N publication**. 79 publishes whenever a VALID col-N can be synthesized (verdict-word + ✅ best + ⚠️ worst, optional 📰); the ONLY skips are verdict==NEEDS_REVIEW, a genuinely THIN dossier (BOTH best AND worst NEEDS_REVIEW), no rendered string, or head_overflow. A **board contradiction / governance flag / within-tier `needs_review` does NOT block col-N** — it is a CONTESTED GRADE that still publishes the current verdict's col-N and is RECORDED for human adjudication (78's `board_contradictions` + `81_audit_dashboard_consistency.py`'s report), not vetoed. 78 (checker) Gate 7 records the board (mis)alignment as a non-blocking signal; it never fails verification on a contradiction.18 - **L2** — IV change / `conviction_level` change / **verdict DEMOTION** / within-tier flip / MoS recompute. **NOW AUTO-applies AND auto-re-ranks the LOCKED dashboard** via the guarded loop: an earning-power event passes `coln_iv_event_gate.py` → `71_refresh_orchestrator.py` runs the targeted Stage-3 re-grade (binding consult + `80_estimate_iv.py` + conviction gates + MoS recompute + verdict re-derive + board sanity-check) → re-freezes the head → **`73_refresh_cycle.sh` stage 3c invokes `82_rerank_guard.py --tag headwindow`** (once per cycle, after the reindex) to re-rank + publish behind a 3-tab backup + dry-run-diff + ticker-keyed checksum + auto-revert + maker≠checker. Stage 3c fires only when `RERANK_AUTO=1` **AND** the post-reindex brain is healthy (`SKIP_PUBLISH≠1`) **AND** ≥1 L1 was applied this cycle. **`RERANK_AUTO` is ENABLED in the LaunchAgent plist (2026-06-25) — auto-re-rank is LIVE on the scheduled loop** (capability shipped PR #33, wired into 73 stage 3c, supervised first-enable done; first guarded re-rank applied: AWL→WATCH). The code default remains OFF (so a manual run without the env var dry-runs + asserts + prints the order-diff and STOPS; no live re-rank), but the scheduled cron now runs with `RERANK_AUTO=1` set in the plist. `71` itself NEVER calls 82 — the re-rank is once-per-cycle in 73, not per IV event.19 - **SOLE human gate (for the GRADE, not for col-N)** — a FOREVER *promotion* (Stage-4 adversarial inversion a cron can't do), plus any `needs_review` / board-contradiction that would change the GRADE: routes to `data/state/pending_reviews/<TICKER>.json`, NEVER auto-applied. NOTE: this human gate is about the verdict/conviction GRADE only — **col-N is still published for the current verdict** of these contested rows (the board is advisory; see the L1 col-N rule above). A board-contradiction queues the GRADE for adjudication; it does NOT withhold the row's col-N.20 - **IV = earning power only.** Price / FII / MF flows / ratings / buy-calls move live MoS + Upside ONLY — `coln_iv_event_gate.py` returns False for them, so they NEVER trigger an IV re-grade or a re-rank.213. **The vault is BINDING.** Any conviction/verdict delta must be supported by `32_consult_brain.py --corpus binding` citations (real slugs only, no fabrication).224. **Maker ≠ checker.** The refresh writer and the verifier are separate (Phase 4 scripts `71`/`72`). Until those exist, run the refresh manually and verify in a separate pass.235. **Never fabricate an event.** Every event row carries a dated source URL. If the detector finds nothing, that is success — stamp L0 and stop. No early-exit "empty = bug".246. **Recall canary is a hard floor.** After any reindex, Recall@10 must hold ≥0.95; auto-revert the index on any drop.2526**Autonomy-ladder ratification.** Ratified 2026-07-03 (PW-13): the daily loop auto-consults fresh (≤2d)27RED+MATERIAL events, hard-capped at CYCLE_CONSULT_BUDGET=25/cycle, RED-first-oldest-first. L1 auto-applies;28L2 IV/conviction/verdict changes including upgrades up through COMPOUND auto-apply under 82_rerank_guard.29FOREVER promotion remains the sole permanent human gate. Backlog (>2d) events consult only under DRAIN_GO=130(founder-gated). The value-digest reports these changes (email); it never grades.3132## Workflow (one cycle)330. **Build sweep universe** — `build_sweep_universe.py` re-derives `data/manifests/dashboard_universe.json` from the LIVE dashboard col B (data rows only; legend excluded; aliases resolved e.g. ZOMATO→ETERNAL). READ-ONLY on the dashboard, degrade-safe (keeps the last good manifest on a failed read). Runs FIRST in `73_refresh_cycle.sh` so onboarded names auto-join this cycle.341. **Detect** — `70_detect_material_events.py` over the sweep universe (`sweep_tickers()` = full dashboard ∪ held, ~241+) → `data/state/event_index.sqlite`. Materiality from `data/config/event_taxonomy.json` (RESULTS / DEAL / FILING / GOVERNANCE → INFO / MATERIAL / RED; GOVERNANCE-RED escalates). Recency window = since `max(graded, last_event_checked)`. (`--universe` prints the swept set + dump coverage.)352. **Triage** — for each ticker: no MATERIAL event → L0 stamp only. MATERIAL → continue.363. **Re-grade (head-window)** — consult the binding brain (`32_consult_brain.py --corpus binding --step conviction|margin-of-safety|verdict`), reconcile with the event, decide the delta tier. **Earning-power event?** `coln_iv_event_gate.py` arms the targeted Stage-3 IV re-grade in `71` (re-run `80_estimate_iv.py` price-free + conviction gates + MoS/buy_below recompute + verdict re-derive + board sanity-check).374. **Write back** — L1: head-window edit via `apply_head_window_grade.py` (HEAD_LIMIT=4000) → col N re-published (77→79). **L2 IV/conviction/verdict-DEMOTION**: `71` re-freezes the head with the new grade (under `--apply`), then **`73` stage 3c invokes `82_rerank_guard.py --tag headwindow`** to re-rank + publish the whole set — gated by `RERANK_AUTO=1` (default-OFF → dry-run-diff + assert + STOP) **AND** brain-healthy post-reindex (`SKIP_PUBLISH≠1`) **AND** ≥1 L1 applied this cycle. A stale live verdict/conviction cell (which the re-rank CARRIES byte-for-byte and 82 asserts against the dossier oracle) is first reconciled by the sanctioned **col-D/col-M writer `sync_col_dm.py` → `write_col_dm.mjs`** (default dry-run; `COL_DM_DASHBOARD_WRITE=1` to write). **FOREVER-promotion / needs_review / board-contradiction**: proposal JSON to `pending_reviews/`, NOT the dossier head, NOT the dashboard.385. **Reindex + canary** — `26_build_lancedb_index.py`; confirm Recall@10 ≥0.95 or auto-revert.396. **Log** — append a 🔄 Refresh-Log row (Ticker · Event date · Type · Severity · Source · Status · Days stale · Proposed Δ). When the re-rank guard is armed, the LOCKED dashboard is re-ranked through `82` (guarded + auto-revert); otherwise it is untouched and the proposed Δ waits.407. **Stop condition** — every swept ticker (full dashboard ∪ held) `last_event_checked == today` AND ingest clean AND no schema/Recall failure AND every MATERIAL event is {stamped | applied-L1 | queued-L2}.4142## Human gate (the sole remaining one: FOREVER promotion)43Most L2 changes (IV, conviction, verdict DEMOTION, within-tier, MoS) now auto-apply and auto-re-rank under the guard. **The sole human gate is a FOREVER *promotion*** (plus a `needs_review` / board-contradiction that would change the GRADE) → queued to `pending_reviews/<TICKER>.json`. That gate is about the GRADE only — **col-N for these contested rows is still published for the current verdict** (the board panel is advisory and never vetoes col-N; a board-contradiction queues the grade for adjudication, it does NOT withhold the row's col-N). The user approves a queued proposal with one `apply_head_window_grade.py <approved>.json` run → then a WHOLE-SET re-rank (per stock-onboarding HARD RULE #2, now wired into the loop at `73` stage 3c via `82_rerank_guard.py`). Kill-switch: `loop-pause-all` in `STATE.md` → scheduled runs no-op. **`RERANK_AUTO=1` is ENABLED in the LaunchAgent plist (2026-06-25)** — the scheduled loop's stage 3c re-ranks live (guarded + auto-revert). The code default is still OFF, so a manual `82` run without the env var dry-runs + asserts + STOPS; the plist supplies `RERANK_AUTO=1` for the cron.4445## Scope & cadence46FULL Action Dashboard (~241 names) ∪ held, DAILY (Phase 5 LaunchAgent **10:00 IST** — `com.invest.news-refresh` `StartCalendarInterval` Hour 10; the separate price-alerts agent runs later at 16:00). The universe is re-derived LIVE from the dashboard each cycle (`build_sweep_universe.py` → `dashboard_universe.json`), so onboarded names auto-join with no manual step. Held-only (off-dashboard) names stay in via the union.4748See [REFERENCE.md](REFERENCE.md) for the exact commands, SQLite schema, taxonomy, file map, and phase status.