You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two errors found by the final whole-branch review, both load-bearing for the
post-merge deploy:
- The manual heartbeat kick can exit 0 without writing a batch (filter_window
drops guids already in the seen-state, and the daily run has marked them).
There is no --force; verify a fresh items-*.jsonl mtime, and prefer
--dry-run, which writes the jsonl but leaves state untouched.
- "Strictness is symmetric" is true for items but not for signals: the
normalizer has no inverse, so a reverted v1 Django serves a v2 artifact
verbatim until a v1 artifact is regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Hfd5GY7STjojT7kSrZSqF
-[ ]**Step 4:** Verify the wire (same prod base URL + bearer used for the #359 live verification): `/api/news/items/?limit=1` → 200, `schema_version: 2`, item has `editorial_score`; `/api/signals/news/` → 200, `schema_version: 2`, entries have `sentiment_score` (normalizer, while the newest artifact is still v1); `/api/signals/news/?as_of=2026-07-10` → also v2-normalized.
663
679
-[ ]**Step 5:** After the next 20-min signals run: newest `signals-*.json` on disk has `"schema_version": 2` and `sentiment_score` natively. Check ATL's Home panel: sentiment chips render (fallback path now reading `sentiment_score`), real items in the "Latest news" column, and **no `degraded` drift badge** — PR #110's `_alarm_if_all_dropped` must not fire, since `_feed_from_items` never reads the items `score`. (During the pre-kick items-404 window the panel silently shows the Phase-A representative feed with no badge — expected, not drift.)
664
-
-[ ]**Rollback note:** revert Heartbeat and Django **together** and kick another manual heartbeat run (strictness is symmetric — a v2 batch fails the old gate exactly as a v1 batch fails the new one). ATL's fallback keeps the panel alive in both directions until PR-2.
680
+
-[ ]**Rollback note:** revert Heartbeat and Django **together** and kick another manual heartbeat run (see the Step-2 correction — verify the kick actually wrote a batch). ATL's fallback keeps the panel alive in both directions until PR-2.
681
+
682
+
> **CORRECTED 2026-07-15 (final whole-branch review): the "strictness is symmetric" claim holds for items, NOT for signals.**
683
+
>
684
+
> -**Items — symmetric, as originally written.** Both gate copies key on `REQUIRED_FIELDS`, so a v2 batch is a
685
+
> missing-required-field poison pill to the reverted gate exactly as a v1 batch is to the new one. Fail-closed
686
+
> both ways, pinned by `test_legacy_score_only_story_poisons_batch_in_both_copies`.
687
+
> -**Signals — NOT symmetric.** The forward direction is protected by `_normalize_legacy_signal_entry`
688
+
> (v1 on disk → v2 on the wire). There is **no inverse**: reverted v1 Django has no normalizer, and
689
+
> `_load_artifact` never inspects `schema_version`, so it serves the newest **v2** artifact verbatim —
690
+
> `sentiment_score` and `schema_version: 2` on a wire whose reverted docs and consumers expect `score`.
691
+
> The revert only converges once a manual heartbeat run **and** a subsequent signals run have regenerated a
692
+
> v1 artifact; until then the signals wire emits v2 bodies from v1 code.
693
+
>
694
+
> Net: the forward deploy is safe for signals from the first request (that is what the normalizer buys); the
695
+
> **rollback** has a gap the forward path does not. Plan the rollback around regenerating a v1 artifact, not
0 commit comments