tootsies

a discord bot for the tootsies server. ask, recap, discuss, ship features by typing.


Project maintained by mejasonmejason Hosted on GitHub Pages — Theme by mattgraham

Classifier audit: every keyword list, string match and regex that makes a decision

First audit: 2026-09-18. Owner ask: “audit repo wide where we are using dictionary and string matching and regex for classification and decisions, enumerate exhaustively; we should go one by one and see possibly replace them all with Haiku.”

Refresh: 2026-10-18 (wave 19), at origin/main fba1952. Owner rule for the refresh: “regex should almost be exact closed set; model (Haiku or Jev) should do the rest for open sets and word lists; feed the lists to the model as input.”

This document has three parts:

Part 0: the 2026-10-18 refresh

The rule and the verdicts

The rule is in docs/ENGINEERING.md, “The deterministic / model-judged boundary”. In short (stronger wording, owner 2026-10-19): code only for a CLOSED set checked by an exact match; a model (Haiku, Sonnet or Jev) decides meaning, with the list as its input; a safety floor may stay as code with a model on top, and the floor only blocks. The old list is not deleted. It becomes input to the model, as reference examples, as candidate labels, or as the closed label set. The old rule stays as the fallback when the model errors, and the surface keeps its fail direction.

verdict meaning
KEEP-CLOSED Code. An exact match on a closed set: a fixed format (a number, a date, a URL, a ticker, a key), our own enum or payload field, an upstream field with a fixed vocabulary, a structural parse, or a parser of a model-output protocol (EMPTY, TRACK:, a closed label answer). A number check and an exact folded identity join are here too.
MODEL-HAIKU Move the decision to Haiku (ClaudeClient.classify, or a field on a Haiku call that already runs). The old list goes into the prompt.
MODEL-SONNET Move the decision to Sonnet. Use it only when Haiku was measured worse on the same real inputs; the reason cites that measurement. The old list goes into the prompt.
MODEL-JEV Move the decision to Jev (utils/typesafe_client.py, a typed Noul, Choice or Score). The old list goes into the state.
KEEP-PROTECTED Code until the owner signs off: settlement and money math, constitution.py and the memory fence, the permission allowlist, and the names / aliases privacy match. A safety floor is here too: a code check that only BLOCKS a banned or false post, never approves one alone, with a model reading what passes (owner, 2026-10-19). Its reason names the ban or false claim. A relevance or quality filter is not a floor.
MEASURED-KEEP Code for an open set, in one of two cases. (1) A model was measured on real inputs and was worse; the reason states both results. (2) The site has no real traffic (the #4522 owner rule): the reason states the 30-day count of its decisions and the count it was taken from, for example 0 drops in 30 days (of 1,586 rows). Not debt. The registry reason starts #<issue or PR> measured:. Re-measure when a better model ships or the count grows. The exact contract is in tests/test_classifier_registry.py.
DONE A model already decides every outcome. This includes a list that only NOMINATES: it widens the candidate set, and a model decides each candidate. A list that decides any outcome ALONE (a fast accept, a fast drop, an admission filter whose miss means no model sees the item) is not DONE for that branch.
OWNED In-flight work owns the site. It is listed, and it is in no batch.

Two old reasons to keep a word list are no longer enough on their own. “Hot path”: Jev answers in about 79 ms warm at about $0.042 per 1,000 calls. “A model judge runs after it”: a list that drops or ships before the judge still decides alone.

Haiku, Sonnet or Jev is a call on the merits, not a fixed rule. Haiku knows world facts (acts, titles, chart names), can extract text through a JSON method, and has a durable cache. Jev answers several typed questions about one text in one request and is fast, but it writes no text, and it is weak at arithmetic, counting, dates, long state, adversarial text, a bias toward the first Choice option, and recent music facts. The refresh therefore puts Jev only where volume or latency earns it: per-post or per-message labels, and small identity picks on a closed candidate list. Every other MODEL site is Haiku, or Sonnet where Haiku was measured worse on the same inputs (MODEL-SONNET). Contract and limits: docs/ARCHITECTURE.md lines 21-28 and docs/INTEGRATIONS.md, “TypeSafe Jev”.

The numbers

Semantic sites per partition, 2026-10-18. A site is a symbol, or a group of symbols that make one decision together. The partitions are the seven reports in Part 2.

partition semantic structural KEEP-CLOSED MODEL-HAIKU MODEL-JEV KEEP-PROTECTED DONE OWNED
P1 music, newsroom, identity, feeds 158 ~225 70 48 2 2 27 9
P2 markets, betting, sports, media 131 ~191 76 13 4 8 8 22
P3 conversation, X, gates, output checks 94 ~205 32 17 3 7 21 14
P4 charts, cinema, ops, infra 72 ~150 38 11 0 5 11 7
P5 the 16 remaining files 57 54 37 9 0 0 9 2
P6a files the first audit never read (charts, X tags) 61 68 20 17 0 0 5 19
P6b arcade, Jev callers, runtime scripts 39 ~100 13 12 5 1 3 5
total 612 ~990 286 127 14 23 84 78

A few sites appear in two partitions, because a caller and its helper sit in different files (the games artist credit, the discourse sports gate, the YouTube leg check). The batch plan below merges them, so each site has ONE owner.

Comparison with 2026-09-18:

measure 2026-09-18 2026-10-18
semantic sites ~410 612
sites to move to a model (old HAIKU + HYBRID / new MODEL) ~107 141
already model-decided or nominate-only (DONE) (not counted) 84
kept as code ~300 309 (286 closed, 23 protected)

The count grew for three reasons. About 45 new runtime files landed (the arcade, the Jev callers, x_artist_tags, billboard_stories, chart_history, the qualifier work). utils/music_news.py grew from 4,680 to 7,752 lines and utils/output_checks.py from about 1,400 to 2,658. And the rule changed: sites that were KEEP for “hot path”, “behind a judge” or “tiny list” are now MODEL (16 of them in P1 alone).

Cost baseline. Axiom, 7 days to 2026-10-18, claude_api events: Haiku 24,902, Sonnet 2,113, Opus 71. Jev (typesafe_api): 68 in 30 days, all engagement_labels. The whole batch plan adds about 300 to 400 Haiku calls a day (cached), about 2,000 Haiku calls a week, and 400 to 1,500 Jev calls a day for one batch (CB-09, about $0.02 to $0.06 a day). It does not change the cost picture.

Findings

  1. The common new shape is a regex that OVERRIDES a model field. post_states_forecast turns the classifier’s is_projection=false into true, _calls_title_a_song turns stream_shape=album into a song, figure_is_passed_threshold swaps on judge OR regex, and _STREAM_SCOPED_RE vetoes before the stream fields read. Each one was added after a measured classifier miss. Under the new rule the override is a second model read (CB-01, CB-08), not a longer word list.
  2. A fast accept is the largest model gap in the market lanes. take_names_leader_only ships a chart take with no model call when the leader’s name is present, no rival leg is present, and none of about 18 negation words appears. The leader judge ran 2 times in 7 days. “drops to No. 2” is not in the negation list. No take was declined in 30 days, so this is a reasoned case, not an incident (CB-02).
  3. unnamed chart cuts lose real stories. 10 of 130 unnamed cuts in 30 days name a chart the owner keeps (“Billboard Global Charts” 4 times, “Billboard’s R&B/Hip-Hop chart” for an album record 3 times, “Spotify Global” or “Spotify US Weekly” 3 times). That is about 8% (CB-01).
  4. Several word lists almost never fire, and their miss side is not measured. post_states_forecast 0 recoveries, misstated_chart_frame 0 drops, _LATER_WEEK_RE 1 drop, and most desk shape fences 0 drops in 30 days. “0 fires” is not evidence that a list is right. Each batch that converts such a list does a dry run on 30 days of real text first.
  5. Some shipped model checks have no production traffic yet. 0 calls in 30 days: luminate_subject (B13), house_genre (C5), reference_film_pick (B15), riaa_same_release, wiki_namesake, album_track_match, chimein_score, pplx_found_answer, and the Jev guess_grade and mention_shadow paths. kworb_act_check had 1 event. Most have a plain reason (no input of that shape, a muted surface, a cache warmed by the dry run). luminate_subject is the one to check: all 10 callers of fetch_desk_readings pass a client, but 968 market_subject events ran while the B13 dry run sent 50 of 121 live events to the model. Not verified; listed as a follow-up below.
  6. The Jev engagement labels have no reader. /stats stores questions_labeled, laugh_messages_labeled and hype_messages_labeled (#4318), but no code reads them. utils/playercard.py still shows the shape-rule counts, so the shape rules still decide every stat a member sees. Only the 2026-10-18 catch-up was labeled, so the reader condition (labeled_messages == messages) is false for every member until one full labeled rebuild runs (CB-14). Wave 20 shipped the reader (engagement.labeled_stat); the rebuild is the owner’s to run.
  7. The memory fence’s code check has no runtime caller. output_checks.memory_note_violations is described (here and in CLAUDE.md) as the code half of the memory fence. Only tests call it. The fence today is the prompt and the model choice. This is KEEP-PROTECTED, so a change needs the owner. Wired by #4528 (owner merge): cogs/memory.py:_tag_and_store now calls scrub_memory_note and re-checks with memory_note_violations before every store, and drops a note that still fails.
  8. Corrections to the first audit. utils/release_age.py was listed with 0 semantic sites; its iTunes row title check is a fuzzy identity join and the only date path without a band confirm (81 refusals in 30 days, CB-04). The A13 note “every cert carried a body in cert_id” is out of date: 31 of 52 cert_out_of_scope cuts had an empty body and the prose regex decided them (all 31 were right). utils/sportsdata/ was not inventoried; it holds the settlement identity and is KEEP-PROTECTED. market_subject_image.artist_subject_name is gone, and api_sports._team_logo_from_rows and radio.standing are now exact (#3453, #3454).

Code fixes found on the way (not model work)

These are bugs or dead code in sites the refresh read. Each is a small deterministic change. The batch that owns the file does the fix first, or a separate PR does it.

site finding fix
utils/output_checks.py _ATTRIBUTION_TAG_RE (_HITS_PUBLISHER) All 8 attribution_tag desk rejects in 30 days were correct genre-board posts naming “HITS’ Top 50”. The module comment says that name must pass; the possessive arm matches it. Exclude “HITS’ Top 50” (a chart name) from the possessive arm. First commit of CB-03. Done (wave 20 Part A, #4526): the 8 real lines pass, the publisher credit still fails.
utils/billboard.py chart_context The desk subject reads “Release – Artist”, so the containment test almost never matches a row. The Billboard standing line is probably missing from most market takes. No event measures it. Pass the parsed artist and release; accept on credit_matches + exact title. First step of CB-18. Done (wave 20 Part A): 35 real compound subjects, old 0 / new 9 standing lines, 0 wrong on the live charts.
cogs/market_drop.py _companion_names_artist Substring identity: “Future” matches “Future Islands”. Parse the exact “BY " slot of Kalshi's leg title. **Done (wave 20 Part A): `_LEG_CREDIT_RE` ("Will by have above"), 1247 of 1247 real legs parse; 2 false accepts fixed (Ashe / Tinashe, Beck / Becky Hill), 0 worse.**
utils/markets.py classify_intent, looks_like_sports, _looks_like_music Raw substring tests, reproduced: “general-chats”, “stats”, “conflict” (“nfl”), “nuclear” (“ucl”), “comma” (“mma”), “whats” (“ats”) read as sports; “Trap”, “Songs of Freedom”, “Grape” read as music. Word boundaries. This becomes the fallback rule of CB-19. Done (CB-19, #4549): markets.has_cue_word; detect_league had the same bug and is fixed too.
arcade fighter safety_reject (utils/arcade/fighter_models.py) A substring test on the provider error text sent 29 members to a final safety_reject in 30 days. The Grok word list (“reject”, “policy”) is wider than the OpenAI one. The event stores only “HTTP 400”, so the 29 cannot be checked. Read the provider’s JSON error code exactly; store the code on the event. Done (wave 20 Part A): is_refusal accepts on an OpenAI refusal code exactly, keeps the word test as the fallback (no stored body shows the other codes yet), the body cut is 2000 characters, and detail.error_code is on the event.
image_gen._is_safety_reject Same shape on the OpenAI path. Read the body’s code field (content_policy_violation).
dead code BillboardClient.find(); the strict=False loose mode of kworb.find_chart_entry / _find_by_artist / find_song_entry; kalshi_channel_routing, derive_kind_tags, kalshi_player_props and the SGO tool’s league fast accept (0 /ask Kalshi or SGO tool calls in 30 days, the drop passes topic=None); output_checks.check_text, has_tool_narration, think_piece_hits, has_context_tag_leak, has_track_line; artist_roster.stale_entries. Delete, or leave and record. Do not convert dead code to a model. Wave 20 Part A: deleted BillboardClient.find(). Left and recorded: the output_checks dispatcher family (has_context_tag_leak is a guard that has no caller, so wiring it is a follow-up, not a deletion); the kworb strict=False mode (no production caller, but a parameter on 8 signatures); artist_roster.stale_entries (its docstring keeps it for a manual refresh); the Kalshi routing and SGO league paths (reachable code with 0 traffic, in utils/markets.py, which another executor owns).
utils/cinema_news_stories.py story_signature A cinema story dropped as a duplicate emits no event, so a wrong merge cannot be counted. Add a skip event (reason signature_dup). Part of CB-29. Done (wave 20): wire_collision (decision allow / drop, reason allowed / signature_dup / judge_error) on all three desks.

The CI guard (tests/classifier_registry.txt, #4521)

This document is the narrative register. The machine-checked register is tests/classifier_registry.txt, and tests/test_classifier_registry.py enforces it in the lint-typecheck job. The test finds every module-level re.compile(...) and every module-level word list (3 or more string literals) in the runtime code, and it fails when a site has no line in the registry. A line is path:NAME VERDICT reason:

verdict meaning
CLOSED-SET a closed set: a format, a code, a ticker, our own keys. A reason that starts not a decision: marks a site that decides nothing (display text, a prompt, a tool schema, field names).
PROTECTED a deliberate deterministic fence (constitution, safety, money, privacy)
MODEL-DONE a model decides; this regex / list is only the model-error fallback or the model’s input. Not debt. The reason starts purpose=<name> or jev=<name>, and the test checks that <name> is a purpose="<name>" literal or a *PURPOSE = "<name>" constant in cogs/, utils/ or claude_client.py.
MEASURED-KEEP an open set that stays code, in one of two cases: a model was measured on real production inputs and was worse, or the site has no real traffic (the #4522 owner rule). Not debt. The reason starts #<issue or PR> measured: and states the result (for example #4536 measured: Haiku 21 wrong of 719, Jev 11 wrong, code 0 wrong) or the 30-day count and its base (for example #4574 measured: 0 drops in 30 days (of 1,586 rows)); the test checks that shape. Re-measure when a better model ships or the count grows.
MODEL-FALLBACK an open set that a batch here will move to a model; the code decides today. Debt. The reason names the batch (CB-xx), the owner (owned ...), MODEL:, or done: for a converted site not yet tied to one purpose.
UNCLASSIFIED the 2026-10-18 baseline, not yet reviewed
OWNED-PENDING an owner in _PENDING_OWNERS is converting it now (today: #4412)

The guard sees module-level sites only, so its count (1468 at the baseline) differs from the semantic-site count of this document (612), which also counts inline patterns, substring tests and fuzzy ratios inside functions and leaves out the closed-set sites. The registry review (2026-10-18, this refresh). The seven partition passes gave each of the 1065 UNCLASSIFIED baseline lines a verdict. The registry now holds:

verdict lines note
CLOSED-SET 1122 228 of them decide nothing (not a decision:): 85 claude_client tool schemas, prompt text, display and label maps, canned replies
PROTECTED 22 settlement identity and status sets, the constitution, the permission allowlist, the identity-inference fence, the arcade likeness checks
MODEL-FALLBACK 284 149 in a batch (CB-01 … CB-31), 64 done: (a model already decides; 62 of them later moved to MODEL-DONE, see below), 65 owned by in-flight work (W19-B1 22, #4412 21, W19-B3 8, #4422 7, W19-C1 4, #4428 3), 6 MODEL: with no batch (5 of them a dead path)
OWNED-PENDING 40 #4412, unchanged
UNCLASSIFIED 0  

So of the 1065 baseline lines, 228 were not classifiers at all, and 1065 - 228 = 837 were real decision sites. _DEBT_CEILING is 284. Each batch that converts a site deletes its line (or moves it to MODEL-DONE when the code stays as the fallback or the model’s input), and lowers the ceiling.

MODEL-DONE (2026-10-18, later the same day). 62 of the 64 done: lines moved to MODEL-DONE with the purpose that decides them, so _DEBT_CEILING went down by 62. Two stay MODEL-FALLBACK: utils/apple_music.py:_EDITION_TAG_RE and _PACKAGING_ZONE_RE. Their reason names “the vision media judge”, but no one purpose could be tied to it.

The count of MODEL-FALLBACK plus UNCLASSIFIED lines is a ratchet (_DEBT_CEILING). The count must equal the ceiling, and the ceiling may only go down. A conversion PR moves its line from MODEL-FALLBACK to MODEL-DONE and lowers the ceiling by one in the same PR. A conversion PR passes without a registry edit: a line whose site is gone prints a note and does not fail. Delete such a line and lower the ceiling in a later PR.

Sites owned by in-flight work (in no batch)

owner sites
Qualifier epic #4412 (coordinator session_01QT9u9qKYuMvE23gt9Szz8j) utils/qualifier_trim.py: every word list, including the #4516 detectors time_span, joins_as_only, states_most (fba1952), _RANK_RE / _RANK_ORDINAL (#4471) and the release noun / verb lists; utils/qualifier_check.py (4 sites) and check_claim_qualifier; utils/cinema_numbers.py (result-verb lists, PR #4513); output_checks has_career_claim, has_alltime_claim, every ungrounded_* claim check (old C8), unsupported_rank_claims; banger_subject (feeds check_source_qualifier, #4467); market_outcome.check_news_qualifier; qualifier_trim.restates_cut at market_alert.
bugs-classifier-review #4428 market_cards.take_rank_conflict, swap_to_stated_alt (the card-vs-take regexes, #4383).
bugs-classifier-review #4422 / B4 output_checks._SELF_CORRECTION_RE, has_self_correction, decline_narration_hits, _MARKET_DECLINE_RE, claude_client._SELF_CORRECTION_MARKERS, the wheel narration lists, and every drop_self_correction / has_self_correction call site (market_drop, market_alert, sports_boards, discourse icebreaker).
wave 19, B3 + A16 utils/retrospective.py (_FRAMES, states_past_interval, frames_as_past, the interval arithmetic) and its callers in sports_desk and music_news._row_is_a_different_record; utils/chart_history.py _HEDGE_RE.
wave 19, C1 + A17 artist_watch _ALIASES, _CREDIT_ONLY, canonical_credit, s_variants, lenient_tier; music_news wire_spelling, grounded_subject, named_by_mention, artist_grounded_in_wire, corrected_name, title_grounded_in_wire, the new ungrounded_quoted_titles; cog _artist_in_tier / _recover_bare_artist.
wave 19, B1 The whole market_cards hero cluster: _OUTCOME_VERBS, outcome_split and its helpers (_field_split, outcome_subject, outcome_caption, prime_outcome_splits), named_rank, title_rank, winner_rank, award_slot, split_rank_authority, event_title_split, statement_title, how_much_title, subject_hero; market_drop._slot_figure / _kalshi_event_subject; market_alert._ending_soon_subject / _clean_authority; alert_triggers.outcome_label. The refresh read the B1 scope as this whole cluster (the old B1 row); the B1 executor should confirm.

Re-review: the text classifiers merged 2026-10-17 and 2026-10-18 (#3410)

Owner ask, 2026-10-18: review the new regex and word-list classifiers again, and move each FUZZY one to a model check. A later owner rule the same day is stronger: a regex stays only for an exact closed set (a format such as “#N”, a key); an open word list goes to the model, with the list as the model’s input, and the old rule stays as the fallback on a model error. Every site below was measured on real production text, old rule against model, before any change.

The data:

site decision evidence
utils/market_cards.py take_rank_conflict (_TAKE_RANK_RE, _FORMER_RANK_RE, _ORDINALS, the 4-word window, #4428) MODEL (Haiku take_rank_place), rule as fallback The “#N” hero stays an exact-format regex. The place words, the former-rank words and which song a place belongs to go to Haiku, which returns the place as a NUMBER; code compares it with the hero. 42 real “#N” market cards of 30 days with a take, 2 runs: the rule and the model both match the hand labels on all 42 (1 real conflict, Dai Dai), 0 flips. 7 hard cases from real takes: the rule is right on 2, the model on 6 (“X by Y is No. 2” past the 4-word window, “from No. 17 to No. 10” under “#17”, “debut at No. 2”, and “first” not given to the next song). The model miss is “with Britney Spears second at 8%”, a case I wrote from a real take that said “next”. About 1.4 calls a day, cached, under $0.01 a month. A reason-first answer measured worse (4 flips, 3 false conflicts) and was not shipped.
utils/music_news.py release_windows (_RELEASE_WINDOW_RE) via cogs/music_news._first_week_window (#4428) MODEL (Haiku release_window), rule as fallback 139 real texts (the 22 newsroom first-week takes of 30 days, 80 with a window phrase, 30 without, and the misses), 2 runs, 0 flips: 132 agree with the rule, all 22 first-week takes included. The 7 changes are all better: “first 10 days” and “nine days into its first month” name no window (the rule said “week” and “month”), “first chart day” and “release day” read “day”, and a week’s count “ten days into its first month” names none. 0 worse. About 1 call a day, cached.
utils/output_checks.py _SELF_CORRECTION_RE / _CORRECTION_MARKER (#4422 added “let me correct” and “let me fix”) see the B4 PR 0 hits in 11,294 composed takes and 1,317 shipped posts. A looser scan (39 texts with “wait”, “actually”, “let me”, “i need” and similar words) found 0 self-corrections it misses: all 39 are titles or plain words. A model check would add one call per composed post for 0 catches. A model is also the wrong tie-break here: the desk scorer read “Let me correct that: …” as a clean post on 39 of 48 calls (#4422). Known risk: the line-start arm would drop a post that opens with an unquoted title such as “Hold On, We’re Going Home” or “Wait for Me”. Both charted in the last 60 days. 0 such drops in 30 days.
utils/music_markets.py release_card_subject (#4428) KEEP An identity key (_album_key) on our own fields, not free text.
utils/qualifier_trim.py has_qualifier (_QUALIFIER_RE, _RANK_RE) review only: KEEP recommended on #4412 (the qualifier-epic coordinator owns these files) Sample of 609 agreed wire tweets: the gate passes 311 (194 real, 117 not) and blocks 298 (2 real). It is a recall-first gate in front of the Sonnet qualifier judge, and the judge keeps the false hits (“‘one of the best’ is a personal opinion”, 3 of 24 qualifier_check rows on 2026-10-18). A model pre-gate on the 311 was measured. Haiku (classify) skipped 18 and 14 real qualifiers of 194, with 8 flips between its runs and a p50 of 492 ms. Jev (Noul, cut 0.5) skipped 26 and 25, with 7 flips and a p50 of 786 ms. A skipped real qualifier ships unchecked, so both pre-gates are worse than the regex.
utils/qualifier_trim.py _RANK_ORDINAL in _RANK_RE (#4471) review only: KEEP recommended on #4412 (the qualifier-epic coordinator owns these files) 26 hits in 10,618 tweets; 24 of the 25 agreed are real rankings. The 1 false hit, “the sixth Her Loss song to hit the mark”, counts songs on one album.
utils/qualifier_trim.py _RELEASE_NOUN_RE / _RELEASE_VERB_RE (#4471) review only: KEEP recommended on #4412 (the qualifier-epic coordinator owns these files) 0 exclusions in 10,618 tweets. A closed phrase shape (“the second single to be released”).
utils/qualifier_check.py _FIRST_RE + _SPOTIFY_GLOBAL_RE (#4502) review only: KEEP recommended on #4412 (the qualifier-epic coordinator owns these files) 8 fires in 10,618 tweets, all Global Spotify first or only claims. It only adds kworb evidence for the judge and makes no verdict.
utils/qualifier_trim.py cut checks from 6b6611c (is_cut, leaves_dangling, _INTENSIFIERS, _ABBREVIATIONS, _PRONOUNS) review only: KEEP recommended on #4412 (the qualifier-epic coordinator owns these files) They check the structure of the judge’s own edit (a word subsequence of the claim). That is a fence on a model output, not a judgment.
open PR #4516 qualifier_trim.states_most finding (#4412) It fires on 352 of 1,843 qualifier tweets, and 85 of them are ranked claims (“the 2nd most-streamed R&B artist ever”). The rule it adds (“wrong only when the research names an act with MORE”) is false for a ranked “most”. Reported on #4412 before merge. time_span (153 fires) and joins_as_only (6 fires) read correctly on a sample.

The other text sites merged in that window are our own enums (_QUALIFIER_TRIGGERS, MARKET_ROOM_SURFACES, _DEBUT_LANES, _OCCASION_CHECKED_ART), number formats (figure_value, swap_to_stated_alt) and length floors (too_short_to_ship on three cogs, #4500). They are structural.

The batch plan

33 batches, ordered by user impact times brittleness (H = a miss ships a wrong public post, card or answer, or the list has a record of incidents and review rounds). Each batch is a set of sites that share a file or one code path. Each row gives the model shape, the input (with the old list), the expected calls a day, and the fail direction the change must keep.

Rules for every batch (from Part 1, still in force):

batch files sites count shape input (incl. old list) calls/day fail direction impact x brittleness from notes
CB-01 newsroom chart-story read utils/music_news.py, cogs/music_news.py, claude_client.py (new method) chart_reportable KEPT / CUT tables, post_states_forecast (5 verb lists), post_states_later_week, names_edition, _FUTURE_RE, misstated_chart_frame._TRANSIENT_RE (retire into music_desk_score), _calls_title_a_song, mark_album, cog _is_tour_subject 9 One cached Haiku JSON per chart / milestone story: family (closed set: kept + cut family names + OTHER + NONE), tense (settled / forecast / later_week), edition (bool). Small Haiku labels ALBUM / SONG, MARKED / UNMARKED, TOUR / RECORD. post + claim + subject; the KEPT / CUT tables with the owner notes as the label list; the verb lists as examples ~55 chart scope: model error = regex (fail closed); a CUT regex hit still vetoes; tense: error = regex H x H P1-B3 Dry run on the 130 unnamed claims and 30 days of chart stories, 2 runs. Shares files with CB-08 and CB-05: one executor, or CB-01 first. Done 2026-10-18: see “CB-01 result” below.
CB-02 chart-take fabrication gate (DONE, wave 20) utils/market_subject_image.py, cogs/market_drop.py, cogs/market_alert.py, utils/youtube_grounding.py (reads market_cards.is_music_chart_market) is_youtube_daily_chart_market, is_netflix_rank_market, is_chart_slot_market (+ _MUSIC_SLOT_LEAD_RE), is_music_chart_market as gate input, take_names_leader_only (+ _NON_AFFIRMATIVE_RE), take_references_leg, take_names_a_leg, _chart_slot_gateable, _leader_check 9 (1) Haiku classify per event ticker: SLOT_TITLES / FIELD / BINARY / COUNT / OTHER, cached 30 days. (2) Every gated take goes to the existing market_take_names_leader (market_take_check); the phrase test becomes a logged agreement signal. (3) A leg no-match asks the same judge before the drop. event title, leg title, 3 leg labels, series tags, the regex verdicts as a hint; the take with leader and rival labels; the negation list as examples ~10-20 cached + <=10 judge (1) error = regex; (2), (3) judge error = drop (fail closed, today) H x M P2-A, P6a-4 is_music_chart_market sits in the W19-B1 file (lines 161-189, outside the cluster): read it, or rebase it on MUSIC_SUBTAGS; do not edit the cluster.
CB-03 owner-ban prose fences utils/output_checks.py, claude_client.py (rubric lines + one classify wrapper) _ATTRIBUTION_TAG_RE / attribution_tag_hits, confidence_hedge_hits, self_count_credit_hits, anonymous_credit_hits, has_cross_source_comparison (+ names_hits_source), has_stream_unit_comparison, has_row_arithmetic, _PRIOR_POSITION_RE phrase arms 8 Haiku classify on each regex HIT (per fence: CREDIT / CHART_NAME, DISCLAIMER / WRONG_CREDIT / OTHER, COMPARES / OK, SUM_OR_RATIO / OK, PRIOR / CURRENT_ONLY); one hard-fail line per ban in the desk scorer rubrics for the miss side (0 calls), as _IDENTITY_RULE did the line, the owner steer, the matched phrase; the regex lists as examples ~1 drop on model error (the regex verdict) H x M P3-B1 First commit: the “HITS’ Top 50” code fix. prior_position is on the #4422 lanes: confirm with that owner first. The callers sit in other batches’ files; this batch changes only output_checks and the shared rubric. Done (wave 20, CB-03): ClaudeClient.owner_ban (purpose owner_ban) reads every regex hit at every runtime caller; no answer keeps the drop. 16 registry lines are MODEL-DONE; _HITS_SOURCE_RE stays MODEL-FALLBACK (the Perplexity context strip reads it alone). Measured on 30 days: hit side 748 distinct real hits, 0 true bans cleared in 4 runs; 21 false row-arithmetic hits cleared (the band TWICE, Tito Double P, “Don’t Think Twice”, chart names with “combined”). Miss side: a YES/NO probe on 860 shipped lines found 0 real misses and 59 false flags, so no scorer rubric line was added.
CB-04 release date iTunes row check utils/release_age.py _catalog_release_date iTunes row title check (deezer.album_title_matches) 1 Existing deezer.confirm_album_rows -> confirm_band_rows (catalog_title_match, durable cache); the exact pass stays the free accept CONVERTED (wave 20): release_age._band_confirmed. 94 real (title, artist) pairs: the iTunes rows gave 2 band passes, both wrong records (a 2010 “GTA IV” EP for “Grand Theft Auto VI: The Album”, a track for “Live at Carnegie Hall”); the judge refused both, 2 of 2 runs. requested title + row title; album_match_band picks the residue ~1-3 refuse = undated = no debut claim; model error removes the row H x M P5-A Feeds the debut gates of music_alert, the music_desk called shot and the X gate. Check whether resolve_music_row already asked about the same row (then it is a cache hit).
CB-05 market and projection identity utils/music_news.py (market block), utils/music_markets.py, utils/hits.py market_leg_candidates / _event_conflicts / _topic_tokens / _MARKET_ROLES / _MARKET_ALIASES, poly_event_matches, threshold_metric, restate_market_claim, related_market_context, _subject_tier_hits, hits.projection_row 7 Haiku closed-set pick (pick_market_event shape): EVENT1..n / NONE over every non-empty candidate set, not only a tie; SAME / DIFFERENT for the act substring; ROW1..n / NONE for HITS rows candidates as the closed list (overlap order kept); the role and synonym tables as prompt text ~3 no pick = drop (absent) H x M P1-B4 The N5 dry run found 0 ties in 59 claims; the value is a check on the top pick, which nothing checks today. Done (CB-05): the event pick runs on every candidate set with a year / role safety floor; a non-equal Luminate subject match gets Haiku SAME / DIFFERENT (reading_subject). threshold_metric, restate_market_claim, related_market_context and hits.projection_row stay code by measurement. 2 registry lines to MODEL-DONE, 5 to MEASURED-KEEP. See “CB-05 measurement”.
CB-06 Billboard story figures utils/billboard_stories.py (+ callers cogs/chart_history.py _tick_story, music_markets reveal) _anchors (_MOVE_VERBS, _OTHER_CHART_RE, _LEADER_HINT_RE), verify_rows, leader_row_for, _artist_agrees / credit_parties, _QUESTION_RE 5 Haiku JSON extract per story (rank -> units + the quoted sentence); Haiku YES / NO / UNSURE for the headline leader; code checks that each figure appears verbatim story paragraphs, the printed top-10 rows; the verb and chart-name lists as examples <1 fail closed: no figure; model error = regex parse H x M P6a-1 In 30 days 399 of 510 backfill weeks parsed only part of the story (323 ok, 76 failed). Replay the 12-story fixture set and the 30-day backfill weeks. Converted 2026-10-18: billboard_story_figures (Haiku JSON per story; code checks the sentence, the number and the rank) and billboard_leader_check (YES / NO / UNSURE on the headline); the page road’s credit check reuses count_same_record. Replayed on all 107 stored weeks (1,070 top-10 rows, 2 runs): the regex fills 936, the model read about 1,035; 0 to 1 rows lost per run, both run-to-run flips; 2 rows where the figure changed, both corrections. About $0.085 per full replay. The 30-day “partial” count was retries of the same 67 incomplete weeks, not 399 distinct weeks.
CB-07 catalog identity residue utils/apple_music.py (matcher block), utils/deezer.py (_artist_overlaps) _title_matches non-equal fast accept, _credit_similar / _artist_matches, release_tag_mismatch 55-word zone list, _is_compilation, _artist_is_billed 5 Widen C2’s match_catalog_title to every non-equal pass; new cached SAME / DIFFERENT on a ratio-only credit pass; reuse catalog_release_name for a zone the list does not classify; cached COMPILATION / OWN_RELEASE per collectionName both titles / credits + track; the zone and compilation lists as examples ~15-50 (cached) refuse the row (absent); compilation: error = regex M-H x H P1-B5 Shares apple_music.py with CB-10 (other functions). Result 2026-10-18: the title band (every non-equal pass) and the credit check (catalog_credit_match, iTunes ratio-only + Deezer overlap-only) shipped; _is_compilation and the zone list stay code, measured (Haiku lost on compilation 1 better / 2 worse; the zone list made 0 wrong alone-decisions).
CB-08 classifier and verify field folds claude_client.py (classify_music_news, verify_music_claim prompts), utils/music_news.py and cogs/music_news.py readers _RUNG_CLAIM_RE, figure_is_rank_word, figure_unit_phrase, the threshold_passed OR-branch, cert_claim_format, cert_reportable prose branch, cert_prose_names_other_body / cert_prose_is_riaa, _STREAM_SCOPED_RE, rank_list_tag (14 regexes), claim_chart_kind, cog _first_week_kicker 11 Fields on the existing Haiku calls: market_is_rung, figure_kind, figure_unit, cert_format, cert body as any authority + territory, stream_shape=subset, rank_list names the list; trust threshold_passed alone; claim_chart_kind from chart_key the claim and post the classifier reads; each list as prompt examples 0 added no field or error = regex M x H P1-B2 One field per PR, 2 runs each (A14 measured drift). Cert fields last (31 of 31 cuts right). After CB-01.
CB-09 wire admission utils/music_news.py (cue block), cogs/music_news.py _gather_candidates _NUMBER_CUES / looks_like_numbers_story, _CHART_CUES zero-strength drop 2 Jev Noul “this post reports a music number about an act” on each post the cues DROP; P >= 0.5 joins the bottom bucket, where the Haiku slate rank decides post text; the cue lists and 4 real non-stories (Gaza funeral, Gen Z stat, “A$AP”, a film gross) as reference 400-1,500 Jev (cached per post id, ~$0.02-0.06 a day) Jev error = drop (today) M x M P1-B1 The one Jev batch in the newsroom: hundreds a day, one short text. Run one shadow week first (emit_shadow), because the miss side is not measured. SHADOW SHIPPED (r2c): Jev Noul on every cue-dropped post of the X pass, report-only (typesafe_shadow purpose=wire_admission with the post id). Measured on 1,635 real cue-dropped posts: 11 real music numbers; Jev 11/11, 16 false yes, 2 flips; Haiku 11/11, 19-20 false yes, 11 flips. The registry lines stay MODEL-FALLBACK until the shadow week is read and the switch ships.
CB-10 version tags on bulk rows utils/release_type.py, utils/apple_music.py (version block), cogs/music_alert.py, cogs/music_desk.py, utils/song_pool.py, utils/deezer.py (pick_clip), cogs/games.py, cogs/x_game.py is_side_version* bulk consumers, _BAD_VERSION_RE / _is_guessable_title, _ALT_VERSION_RE / _is_clean_original 3 Nominate: a regex-flagged title goes to the cached release_version_kind; rendition_marker stays a hard RECUT (measured: the model lost on it, #4268) title + artist; the marker lists as examples +30-100 (cached 30 days) error = regex verdict M x M P1-B6 Re-measure the music_alert debut lanes first: C3 found 0 better and 2 wrong there before #4268 fixed that shape. Touches cogs/games.py at the pool filter only (CB-15 owns the grader). Result 2026-10-18: NOT converted, measured. On 1,212 real flagged titles (2 runs, 12 flips) release_version_kind was worse on every bulk site: guess pool 27 wrong of 29 disagreements (tributes and karaoke called NEW, live cuts EDITION), side-version rows about 12 worse and 1 better of 803. The six measured lists are MEASURED-KEEP (#4575; only flagged titles were measured, so their misses are not, and the registry reasons say so); _RADIO_EDIT_RE (one exact phrase) is CLOSED-SET; a PLAIN / OTHER label for _ALT_VERSION_RE is #4571; the live + remaster = EDITION finding on the announce paths is #4570.
CB-11 desk caption and qualifier fences utils/catalog_stats.py, utils/milestone_qualifier.py, cogs/music_desk.py (_RESERVED_OTHER_CHART) rank_claim_mismatch phrase half, is_unofficial_rank + source lists, _NO_QUALIFIER_MARKERS tail, _RELATIVE_SCOPE_MARKERS, _RESERVED_OTHER_CHART 5 Fold source_class + scope into qualifier_verify; one cached Haiku OK / NAMES_RIVAL / OTHER_METRIC on a catalog caption; pass the chart identity into topic_dedup the proved rank fact; the aggregator / official source lists; our own past post ~2 drop on error (captions, qualifiers); dedup: error = regex M x M P1-B7 Agree with the #4412 coordinator first: verified_qualifier calls qualifier_check. Done 2026-10-19: see “CB-11 result” below. Caption: Haiku catalog_caption. Qualifier: Haiku qualifier_labels, NOT a field on qualifier_verify (measured worse). _NO_QUALIFIER_MARKERS and _RESERVED_OTHER_CHART are MEASURED-KEEP. qualifier_check is not touched.
CB-12 album metric and title read utils/output_checks.py _METRIC_FOREIGN_WORDS / metric_foreign_word, album_metric_named / same_album_metric_posts / _STREAMS_WORDS, missing_release_title / _title_forms 3 Haiku label cached per post text: PURE_SALES / UNITS / STREAMS / NONE / SEVERAL; YES / NO “does LINE name TITLE” only when the exact fold misses the line; the word table as reference; the title with its edition forms ~60 today’s verdicts on error M x M P3-B2 0 ship-side drops in 30 days; the value is in the dedup read (the Popstar units settle that never posted). Same file as CB-03: run after it, or one executor. Done (wave 20, CB-12): ClaudeClient.album_metric (purpose album_metric, cached 7 days per text) reads the metric in the music desk and alert dedup reads (utils/album_metric.py, the ScheduledPoster._sched_dedup_history_read hook) and on a wrong-metric word at both ship gates; no answer uses the word table. Measured on 300 real posts, 2 runs: 17 disagreements, model right on 15, wrong on 1 (kept in the list, the safe side), 0 effective flips. The title read was NOT converted: all 4 fold misses in 2,959 real lines were a possessive (“Silver Sands Marina’s”), fixed in code (_POSSESSIVE_S_RE); after the fix a YES/NO read would make 0 calls.
CB-13 reissue check cogs/music_releases.py _reissue_keys: _REISSUE_RE fast yes, _REISSUE_ASK_RE miss branch 2 Existing release_is_reissue (REISSUE / NEW, cached) title + artist + release date + the release_version_kind label; the matched word as a hint ~10-20 error = regex (a regex word omits; no word = NEW) M x M P5-C The list grew after C7 (“super deluxe”, “expanded edition”, 2026-10-06); “(2026 Mix)” has no word. Re-run the C7 set first: if wrong omits rise, convert only the fast-yes branch. Wave 20, measured, NOT converted: 110 real titles (72 album_total queries in 30 days + 69 fresh Apple Top Albums rows in US/GB/CA/AU, 2026-10-18), 2 Haiku runs. The current rule made 0 errors (2 of 2 reissues omitted, 0 new titles omitted). Asking release_is_reissue for every title added 3 wrong omits (Group Therapy, While The Flood Rages, The Shel Talmy Recordings, each in 1 of 2 runs) and fixed nothing. Both regexes re-verdicted CLOSED-SET with the count; _DEBT_CEILING 284 -> 282.
CB-14 engagement stats read the Jev labels utils/playercard.py, utils/engagement.py (reader helper), cogs/stats.py (one full rebuild) is_question, is_own_laugh, is_caps 3 Jev, already running (#4318). The work is the reader switch plus one full labeled rebuild. cleaned message text; the shape rules stay as the stored keys and the fallback 0 added; one rebuild ~15k requests (~$3.60); ~70 a day catch-up per member: shape key unless labeled_messages == messages M x M P6b-1 is_caps -> “hype” changes a label members see: owner call. Reader shipped (wave 20): engagement.labeled_stat reads questions_labeled and laugh_messages_labeled for a fully labeled member; caps stays on the shape rule. Measured: the live eval (64 authored cases, 2 rounds) scored the shape rules 0.781 / 0.875 / 0.766 (question / laugh / hype) and Jev 0.984 on each; the 2026-10-18 production catch-up flipped 76 question, 13 laugh and 110 hype verdicts of 1,350 Jev-labeled messages. Real member text was not hand-labeled (a session may not read member messages). The full rebuild is NOT run yet: 709,777 messages, so about 35.5k requests, about $8.50 and about an hour at the limiter (the ~15k / $3.60 figure assumed 300k). Owner runs /stats rebuild:True.
CB-15 guess artist credit cogs/games.py, utils/guess_grading.py _artist_named (longest token + 0.82 ratio), _names_credit, _title_credit_artists / _NON_ARTIST_CREDIT_WORDS 3 Jev Noul names_artist in the request the title check already sends (0 added requests) the guess; the credited acts, features and remix credits as the closed candidate list 0 added (29 rounds in 30 days, none since 2026-09-18) Jev no answer = today’s rule M x M P6b-2, P3-B6 Ask only when the rule nominates a credit hit. The X game scores no artist. Result 2026-10-18: shipped (names_artist / names_guest); 56 guesses x 2 runs: false credits 15 -> 1 HARD, 17 -> 1 EASY, 1 true credit lost.
CB-16 chart_history store reads utils/chart_history.py, utils/artist_priors.py _SUBJECT_HEAD_RE, _TITLE_BY_RE / _POSSESSIVE_RE / _subject_parts, the opener skip list, _chart_named, chart_history._credited, artist_priors._credited / same_act 6 Haiku JSON extract per Nora post (cached by tweet id); Haiku ONE / COLLAB (reuse K2 kworb_act_check) the post; the verb list and the two subject shapes as examples; the credit, the asked act, the ranked-act set 10-20 + <5 model error = regex parse / old split; a figure not verbatim is dropped M x M P6a-2 After W19-B3 (parse_nora_text holds _HEDGE_RE). Bumping _NORA_SCAN_VERSION re-reads the 24-month backfill once (a few thousand cached calls). Do not edit the artist_watch alias table (W19-C1). Converted 2026-10-18: nora_extract (Haiku JSON per post, code checks every field word for word) and chart_credit_split (Haiku lists a credit’s artists, code matches the asked act exactly). The ONE / COLLAB reuse was dropped: a YES / NO label admitted “Ella” on 7 of 9 “Ella Langley” rows, and the list shape covers the duo case too. 553 real Nora posts: 45 better, 3 worse, 10 neither; 131 real credit pairs: 17 better, 1 worse. The real re-read is about 40 twitterio pages and 250 Haiku calls (their searchable history starts 2026-05). artist_priors stays code: no runtime caller. Fixes #4533. Follow-up #4591: a “#2.” list number that opens a line counts as a rank only on a line that names the read’s act or title (_quote_ranks), so a quote that runs over a numbered debuts list cannot store another row’s list number. Replayed on 138 figure posts x 2 runs: 0 ranks lost.
CB-17 album variant on every pair utils/album_milestones.py _relation (_VARIANT_WORDS_RE, _DISTINCT_RE) 1 Existing Haiku album_variant_relation on every pair (drop the fast paths in _group_releases) both titles; the word-list reading as a hint 3-5 the first week, then <1 error = word list; no verdict = withhold the count M x M P6a-3 CONVERTED (r2c): every pair goes to album_variant (new wording, labels VARIANT / DISTINCT / UNSURE); album_milestones._combine joins it with the word list: the model decides a pair the list cannot place, a placed pair counts only when the model agrees, an error keeps the list. 311 labelled real pairs: before 275 right / 36 wrong, after 281 / 12 / 18 withheld. 2 registry lines to MODEL-DONE. Detail: “CB-17, CB-20, CB-24 measurement” below.
CB-18 Billboard chart context utils/billboard.py (+ 2 callers in cogs/music_desk.py) chart_context 1 Code fix first (see the table above), then Haiku SAME / DIFFERENT on containment-only hits subject (artist, release); candidate row title, artist, chart name <20 after exact accepts and a per-week cache no chart line (absent) M x M P4-A Replay one week of desk subjects first and count containment-only hits. Touches music_desk.py: sequence with CB-11 and CB-23.
CB-19 room and board cue lists utils/markets.py, cogs/discourse.py, cogs/chimein.py (call sites) _looks_like_music / _MUSIC_MARKET_CUES (fast keep), classify_intent as the discourse routing prefilter, looks_like_sports / _SPORTS_WORD_RE (icebreaker gate), arbitrated_match(catch=False) x4 4 (a) music-cue hits go to the existing market_runner_up_check; (b) one cached Haiku SPORTS / PREDICTION_MARKET / OTHER per channel id + name + topic hash (7 days), shared by both discourse gates; (c) catch=True on the 4 dedup sites (existing topic_duplicate) event title + category, the cue list as music examples; channel name, parent, topic, _SPORTS_PATTERNS / _PM_PATTERNS as examples; post + history <2 + <5 + ~7 (a) error = keep the event; (b) error = the keyword rule, repaired to word boundaries; (c) no verdict = ship M x M P2-B, P5-B, P3-B4 Discourse is mostly muted (2 scored takes, 1 icebreaker in 30 days). classify_intent stays the _classify fallback (DONE). The catch=False reason was cost (#2083); measured volume is ~7 posts a day. Shipped (wave 20): (a) as planned, 31 live nominated titles, 0 errors old and new, 0 flips; (b) only the icebreaker gate (room_market_kind): the compose routing gate is on the retired path (#4119); (c) all 4 sites. See the CB-19 note below.
CB-20 Versuz manual card song match scripts/versuz_card_manual.py _similarity, _closest, option_song 2 Haiku classify SONG1..n / NONE over the played songs; the exact fold stays the free accept option text; the unused played songs as labels manual, ~20-40 a night NONE / error: the option keeps its own text M x M P6b-3 A person reads the PNG before posting. MEASURED-KEEP (r2c): on the 44 real options of the 2 hand-made nights the title similarity is 44/44; Haiku 41-43/44, Sonnet 42/44. Not shipped. Detail below.
CB-21 kworb video match and functional audio utils/kworb.py find_video_match unique containment branch, _BASE_LABEL_RE qualifier drop; is_functional_audio regex drop branch 3 Haiku SAME / DIFFERENT cached 14 days per (label, video id); reuse functional_audio_check on each distinct name the regex drops leg label (with the dropped qualifier), video title, channel, event title; name + matched phrase ~1 + ~0.1 DIFFERENT / error: keep the row with unique=False, so the X vision gate decides; audio: error = drop (today) H x L P4-B, P4-D Do not drop the art on DIFFERENT; give it to the existing gate. Stay off the 1,000-row bulk path. Shipped (wave 20): the video match now asks kworb_video_same on every unique phrase or qualifier match whose title is not an exact fold (kworb.confirm_unique_video). Measured on 135 real Kalshi legs (30 days) against the live chart: 43 unique matches, 19 exact (no call), 24 asked, 2 runs, 0 flips; 2 downgraded to a pick (“Zid” in the Parvati title, the lyric-video leg on the music video), 0 hand-labelled SAME downgraded. The audio regex stays code, re-verdicted CLOSED-SET: 0 wrong drops in 30 days (2,694 artist_filter events, 2 names) and 3 names on today’s 3 pages, all functional; functional_audio_check agreed 6 of 6. Wiring a model into that bulk drop needs a client inside KworbClient, which bot.py builds.
CB-22 Perplexity answer reads and market outcome prose utils/perplexity.py (+ one-line caller changes in music_desk, music_news, market_outcome), utils/market_outcome.py is_hedged at the drop sites (music_desk, music_news x2, market_outcome.is_no_result), strip_industry_projection, _MARKET_TALK open words 3 Reuse _perplexity_grounded / perplexity_found_answer (Haiku FOUND / NONE) on a list hit; Haiku CUT / KEEP per HITS sentence; Jev Noul “talks about a betting market, odds or a probability” on the open words answer body + question, _HEDGE_MARKERS as NONE examples; the winner line with the word list as examples; %, percent, kalshi, polymarket stay exact ~9 + <5 + ~2 Jev the list verdict on error; market talk: re-ask, then drop M x L-M P3-B3, P2-C Leave milestone_qualifier.is_no_qualifier’s is_hedged call to #4412. The market-talk ban is an owner steer (2026-09-06): the exact tokens stay the hard part. Wave 20: is_hedged at the 3 desk / newsroom drop sites CONVERTED (perplexity.hedged_checked: a list hit goes to perplexity_found_answer; on 35 real desk reads the list hit 5, all 5 real answers, all 5 kept, 3 goldens dropped). PROTECTED, not converted: market_outcome.is_no_result in resolve_outcome and resolve_winner (4 reads; they lead to the outcome post and its market_outcome_call ledger row). Its 2 prose reads (move_news_line, the news read) had 0 list hits in 30 days. Measured, kept as code (CLOSED-SET): strip_industry_projection cut 0 sentences in 35 real projection reads; _MARKET_TALK fired 0 times in 30 days, and the 14 shipped winner lines carry no market words.
CB-23 RIAA text and act-or-placeholder utils/riaa_boards.py, utils/riaa.py, utils/chart_standings.py (+ its caller in cogs/music_desk.py) _bare_title bracket keep list; _NOT_AN_ACT / own_credit; is_placeholder_credit as used on the census / catalog / debut boards 3 Bracket list folded into the existing riaa_recase call (0 added); one shared Haiku ACT / NOT_ACT (cached 90 days) for RIAA cells and board-leading credits titles with the keep / drop bracket lists; the cell or credit with _NOT_AN_ACT and _PLACEHOLDER_CREDITS as examples <1 (+ ~731 once for RIAA cells that reach a board) today’s rule on error M x L P4-C, P5-D artist_watch._PLACEHOLDER_CREDITS itself stays as the list (P1 kept the portrait skip exact); this batch asks the model only where a board would crown the credit. Done (CB-23): credit_act (Haiku ACT / NOT_ACT, cached 90 days, utils/credit_act.py) decides RIAA cells and Billboard census parties; riaa_bracket (Haiku KEEP / DROP per clause) decides _bare_title. Not folded into riaa_recase: that call may change only letter case (recase_valid), and the bracket choice is made when the board is built, before the recase. 2 registry lines to MODEL-DONE; _CREDIT_BRACKET_RE (a fixed credit opener, a certain drop before the model read) to CLOSED-SET; _DEBT_CEILING 84 -> 81. See “CB-23 measurement”.
CB-24 stream report loose join utils/stream_report.py _fold_loose loose-only residue in build_report 1 Existing release_version_kind; the exact _fold_track join stays the free accept row title + track title <1 error = old loose rule M x L P6a-6  
CB-23 RIAA text and act-or-placeholder utils/riaa_boards.py, utils/riaa.py, utils/chart_standings.py (+ its caller in cogs/music_desk.py) _bare_title bracket keep list; _NOT_AN_ACT / own_credit; is_placeholder_credit as used on the census / catalog / debut boards 3 Bracket list folded into the existing riaa_recase call (0 added); one shared Haiku ACT / NOT_ACT (cached 90 days) for RIAA cells and board-leading credits titles with the keep / drop bracket lists; the cell or credit with _NOT_AN_ACT and _PLACEHOLDER_CREDITS as examples <1 (+ ~731 once for RIAA cells that reach a board) today’s rule on error M x L P4-C, P5-D artist_watch._PLACEHOLDER_CREDITS itself stays as the list (P1 kept the portrait skip exact); this batch asks the model only where a board would crown the credit.  
CB-24 stream report loose join utils/stream_report.py _fold_loose loose-only residue in build_report 1 Existing release_version_kind; the exact _fold_track join stays the free accept row title + track title <1 error = old loose rule M x L P6a-6 CONVERTED (r2c) with a new read, not release_version_kind: track_version_join (row + track, SAME / DIFFERENT). The title-only read answered NEW once in 3 runs on a real row. 64 real album reports of 30 days: 3 loose rows, 3/3 SAME on 3 runs, the same as the loose rule. 1 registry line to MODEL-DONE.  
CB-25 /ask reference router utils/reference.py, utils/chart_data.py, utils/wikidata.py, utils/reference_movie.py, utils/genius.py, utils/musicbrainz.py, cogs/ask.py (tool schema, props join) _CHART_INTENT, _TABULAR_CUE, sub-page pick (_SUBPAGE_HINT, _ASPECT_SYNONYMS, _aspect_weights), _title_names_subject residue, detect_chart, render_tables filter, _FACTOID_INTENT, want_founding, movie_matches miss branch, the genius intents + _best_annotation, _MUSIC_INTENT, the /ask player-prop name join 13 Make the tool aspect an enum the /ask model fills; one cached Haiku classify per lookup for the rest (chart, table, factoid kind); route non-music questions to the Movie source, where pick_reference_film decides; a candidate-name pick for props query + aspect; candidate titles / names; the regex lists as cue examples ~0.2 (5 lookups in 30 days) regex verdict on error M x L P3-B5, P4-E, P5-E, P1-B8 (router part), P3-B7 B7 measured this router “not worth it” (#3430). Under the new rule it is MODEL, but it needs an owner yes; retiring the router is an acceptable outcome. CONVERTED (owner yes 2026-10-19): reference_route (one cached Haiku JSON read per lookup), reference_subpage, wiki_title_subject; song_credits takes a kind enum. 13 registry lines to MODEL-DONE. Measured on 72 questions (5 real, all that exist in 30 days; 67 hand-written, labelled before any model run): regex chain 49/72, Haiku 70/72 and 70/72, Jev 58/72. Sub-page: old weights 11/17, Haiku 15/17 x2. Title residue: 17/17 x2 with the subject kind (16/17 without). Not converted: the props join (0 player_props_lookup in 30 days) and the render_tables row filter (an exact match on the asker’s own words). Detail: “CB-25 measurement” below.  
CB-26 manual music tools scripts/music_drop_manual.py, scripts/riaa_credit_audit.py, scripts/artist_sweep.py video_kind; _SAME_RECORDING / _recording_key, _NOT_AN_ACT_NAME; board_stream_totals called with no client 4 Haiku classify VISUALISER / LYRIC_VIDEO / MUSIC_VIDEO / AUDIO / LIVE / OTHER; SAME / DIFFERENT and ACT / NOT_ACT (share CB-23’s wrapper); wire the existing album-track match YouTube title + channel; titles and guest names with the lists as examples manual, a few to tens per run error = regex label / reject / blank M x L P6b-5, P6b-7, P6b-8 video_kind labels “(Official Audio)” as video today, so the claim says “official video”. Done (CB-26): utils/video_kind.py (Haiku video_kind, the two regexes as the fallback) decides the report’s kind word; _NOT_AN_ACT_NAME stays as a block-only floor with the shared credit_act read on top; _SAME_RECORDING stays code (MEASURED-KEEP, the model was worse); artist_sweep passes a client, so the existing album-track match runs. _DEBT_CEILING -4. See “CB-26 measurement”.  
CB-27 X tag scan drop branches utils/x_artist_tags.py _inside_longer_name (+ _CREDIT_WORDS), _is_title_position “by” branch, _FAN_BIO 3 Existing is_music_artist_mention; new OWN / FAN / UNSURE for bios the post + a position hint; the bio + the 12 phrases 1-3 no tag (absent) on NO or error L x M P6a-5 _FAN_BIO refused 0 in 30 days and every tagged account is ID-pinned: it may wait. SHIPPED 2026-10-18 (wave 20): see the CB-27 result under the x_artist_tags inventory.
CB-28 competitor same-story judge scripts/competitor_scoreboard.py split_matchup no-separator branch, _work_kind, certs, same_story fallthrough, is_story cue admission 5 Haiku SAME / DIFFERENT per pair; STORY / OTHER per tweet matchup, our preview, their tweet, chart keys, figures; the docstring rules as the definition ~30-100 pairs + ~160 tweets error = today’s rule ladder L x H P6b-4 Blocked: the script runs in ops-monitor.yml, which has no model key. Adding one is a .github/ change, so the owner merges. Land after W19-C1 and W19-B3. CONVERTED (owner merges, .github/ change): competitor_same_story on SONNET (Haiku measured worse), competitor_story on Haiku; the rule ladder on no key / error. 30 days, 2 runs: pairs wrong 96 (rules) / 56-57 (Haiku) / 16-17 (Sonnet); posts wrong 56 (rules) / 6 (Haiku). No registry lines: the script is not in RUNTIME_SCRIPTS. Detail: “CB-28 measurement” below.  
CB-29 wire signature collisions utils/pop_stories.py, utils/sports_stories.py (+ cogs/sports_desk.py read), utils/cinema_news_stories.py (+ cogs/cinema_news.py) story_signature / dedup_key collision drop, in three files 3 On a key collision whose texts differ, ask the existing topic_duplicate; same text stays a free drop; store the recorded headline beside the key both wire texts ~1 error = drop (today) L x L P1-B8, P2-D, P4-F Add the cinema signature_dup skip event in the same PR. CONVERTED (wave 20): wire_lanes.collision_allowances, two topic_duplicate calls must agree; 226 real pairs, 23 allowed (all correct), 0 worse.  
CB-30 side surfaces utils/banger_lane.py, utils/ktt2.py, utils/versuz.py, utils/feeds.py _SPAM_RE, match_score / match_threads, answer_order, room_is_quiet 4 Small cached Haiku labels: SPAM / REAL per tweet id, a thread pick from the newest N, A / B / UNSURE per poll answer, SUBSTANTIVE / CHATTER each list as examples ~5 each keeps today’s rule on error L x L P1-B8 Disjoint files; one executor. Wave 20: _SPAM_RE CONVERTED (Haiku banger_spam, regex on error; on 304 real Top-search results the model fixed 8 and was worse on 1). Not converted, each with 0 production inputs in the 30 days to 2026-10-18: ktt2.match_threads (0 search_ktt2 tool calls), versuz.answer_order (0 versuz events; a miss already falls to the host’s own pick), feeds.room_is_quiet (9 recaps; the message text is not stored, so it cannot be measured).  
CB-31 small Jev identity picks and labels (DONE 2026-10-18: measured, every site stays code; see “CB-31 result” below) utils/x_reply_draft.py (+ draft_x_reply), utils/starboard.py, utils/market_chart.py, cogs/playercard.py _TICS / tic_hits, looks_like_wall_channel, _order_two_up / _side_tokens, _resolve_target, _resolve_member_id 5 Jev: 7 Nouls (one per tic name); Choice over channel names + NONE; Choice “which label is side X” after the exact canonical_name fold; Choice over the members whose name contains the text tic names + example phrases; channel names; the two labels; display names ~5 + <1 + <2 + <1 the code rule on error L x L P3-B6, P2-D _TICS was measured against a model once (showing drafts made tics worse, 9/35 to 19/36). A Jev label that names the tic is a different shape, but measure it against that baseline.  
CB-32 arcade memory cities (DONE, wave 20) utils/arcade/memory_cities.py, cogs/arcade.py _load_cities cities_in 1 Haiku PLACE / OTHER per nominated city (never Jev: the input is memory text) the note sentences that name the city; the gazetteer as the closed candidate set ~15-50 (1-day cache) error = keep the regex hit L x L P6b-6    
CB-33 album-track recovery, zero-overlap branch utils/chart_boards.py recovery_candidates zero-overlap branch 1 Fold into the existing album_track_match call unmatched title + top unused rows by total ~0 absent (undercount) L x L P4-G Only after the follow-up below confirms the recovery path runs (0 calls in 30 days).  

CB-02 result (wave 20). Haiku decides both reads; the regexes are input and fallback. (1) claude.market_chart_family (purpose market_chart_family, labels SLOT_TITLES / FIELD / BINARY / COUNT / OTHER, cached 30 days per event) decides which boards the gate checks. The title rules are one input line and decide on a model error. (2) Every take on a SLOT_TITLES board goes to market_take_names_leader (purpose market_take_check); the take_names_leader_only fast accept is gone and rides the new chart_take_gate event as matched. (3) The alert’s YouTube leg check ships only on a whole-phrase match. Any other take goes to market_take_names_leg (purpose market_take_leg: is the take ABOUT a leg?) with the kworb upload lines before a drop. The leader judge is the wrong question there: a take that says a leg is out of the running crowns nothing (Codex, #4566). On 12 constructed takes (7 that name a leg by its artist, 5 about another song), 2 runs: 12/12 with the uploads, 7/12 without them, and every miss without them is a drop, as on main. Measured on real data, 2 runs each:

read set old rule Haiku flips Jev
board shape 382 real Kalshi event series (196 with a chart word in the title + 186 random), hand-labelled (4 ambiguous left out) 379 right (missed the 2 YouTube weekly song boards and the vinyl sales board) 382 right on both runs (wording 3); wording 1 un-gated “Top Album on Spotify” (not shipped) 0 that change the gate 197 and 196 of 198: it gated 2 FIELD boards; p50 766 ms vs Haiku 590 ms
board shape, alert input the 198 chart-word rows as the alert sends them: event title + ONE leg (same rule) 198 right on both runs (wording 3); wording 2 read the Billboard 200 #1 board as BINARY on both runs (Claude review, #4566) 0  
gated take 34 real chart takes (30 days), leader as posted ships 34 ships 34 of 34 0 not measured
gated take the same 34 with a rival as leader drops 30 of 34 drops 30 of 34 (same 4 misses: the judge’s “unsure = true” line, #4271) 0  
gated take 5 goldens (After You, My Brilliant Career, 3 falls) ships 2 (“drops to No. 2”, “slides to No. 2”) drops 5 of 5 0  
live boards 6 live SLOT boards, real regroup (no take) correct take ships 6/6, rival take drops 6/6, wrong-slot take drops 5/6 (the fast path ships 6/6)    

Regression table, old against new on the real takes: better 2 (the goldens), WORSE 0, changed-neither 72 (per run: 34 real + 34 swapped + 4 ambiguous). Cost: shape reads about 5-15 a day (cached), judge reads about 4-10 a day (was about 0.1 a day), together under $0.02 a day. The regression query after merge: ['tootsies'] | where event == 'chart_take_gate' and ok == false | project _time, surface, matched, reason, ticker=parse_json(detail)['ticker'] joined to the market_drop_scored / market_filtered drop rows for the take text; a correct take there is a WORSE. is_music_chart_market (market_cards.py, not edited) is now only the fallback for the gate; its branding, report-lock and catalog-hint uses still decide alone.

CB-19 measurement (wave 20, 2026-10-18).

CB-31 result (2026-10-18). Every site was measured on real production input, and no model beat the code. One code bug was fixed. The registry line for _TICS is now MEASURED-KEEP (a model measured worse), and _DEBT_CEILING went down by 1.

site input measured old errors Jev Haiku verdict
_TICS / tic_hits 88 real Sonnet drafts over real wire tweets, hand labels 7 false-hit drafts, 1 miss 35-42 false hits, 14 flips 11-13 false hits, 5 flips KEEP-CLOSED
looks_like_wall_channel 34 real text channels, 2 guilds 0 not run not run KEEP-CLOSED
_order_two_up / _side_tokens 88 real Kalshi two-leg game events 0 not run not run REMOVED 2026-10-19 (the two-number cover layout it served is gone)
_resolve_target, _resolve_member_id 517 queries from the real 279-member roster 12 wrong picks that a start test fixes (1 tie goes the other way) 10 of 11 decidable ties 9-10 of 11, 5-7 refusals code fix (best_name_match)

The surface volumes are low: the X reply draft ran 1 time in 30 days (the experiment is off), /card 3 times and the engagement_lookup tool 4 times. The /card typed text is not logged, so the name queries were built from the real roster: each folded word and each 4-letter prefix of each name.

CB-01 to CB-33 hold about 140 MODEL sites. The partition total of 141 counts a few shared sites twice. CB-14 and CB-15 include sites that already run Jev beside a code rule. The same file sits in more than one batch in these places; sequence them or give them to one executor:

file batches
utils/music_news.py, cogs/music_news.py CB-01, CB-08, CB-05, CB-09 (CB-01 first)
utils/apple_music.py CB-07, CB-10 (different functions)
utils/output_checks.py CB-03, CB-12
cogs/music_desk.py CB-10, CB-11, CB-18, CB-23 (one function each)
cogs/games.py CB-10 (pool filter), CB-15 (grader)
utils/market_cards.py CB-02 reads is_music_chart_market; W19-B1 owns the cluster

CB-01 result: the newsroom story read (2026-10-18, sub-epic #4557)

One cached Haiku JSON per story, ClaudeClient.chart_story_read (purpose=chart_story_read): family, forecast, later_week, edition, title_is. One Haiku label on the take, take_marks_album. Each field that the answer does not give uses its old rule. Design change from the plan: a chart NAME in the KEPT or CUT list still decides with no read, and the model reads only the residue. On 200 claims the rule keeps, the first model run dropped 9 (6 daily stream counts, 3 bare “Hot 100” claims), so a model decision on the kept side was worse than the rule.

The data, 2026-09-18 to 2026-10-18:

Every disagreement was read by hand. 2 runs per model call.

site inputs better WORSE same flips
chart family, residue (chart_reportable unnamed) 130 10 (the rule kept 0 of the 11 real kept charts) 2: “US Pop & AC Radio” and a Spotify single-day record, both kept 2 of 2 runs; a daily stream count kept 1 of 2. The rule keeps that shape already when the post says “on Spotify”. 117 1
forecast (post_states_forecast) 201 11 rule misses (“is set to spent a 15th week”, “aiming for”, “challenging for”); none changes a shipped story in this sample, because the classifier flag was already set 0 190 0
later week (post_states_later_week, take guard) 201 11 (“third consecutive week”, “returns to its peak”, “another #1 week”; and another title’s later week in the same post) 0 187 (3 neutral: artist-level weeks) 1
edition (names_edition) 201 11 (“re-issue” with a hyphen, the #4131 Rubber Soul case; a tour’s, a festival’s or a chart’s anniversary is not an edition) 0 188 (2 neutral) 2
title is a song / album / tour 32 2 (TWICE’s “This Is For” is a tour with no “tour” word) 0 30 0
take says album (mark_album) 262 about 140 takes that already read as an album (“now 2016’s seventh most-streamed album”) stay unchanged; the rule added a second “album” 0 (the model never said unmarked where the rule said marked) 121 0 (1 error on a cut-off text: the rule decides)

Cost: about 100 to 150 reads a day (one per composed story, plus about 4 residue reads at the scope gate), cached a day per story. About $0.1 to $0.3 a day on Haiku. The album label adds about 5 calls a day.

After merge, these Axiom queries show a regression:

Not changed, and why:

Registry recheck in the same PR: FIRST_WEEK_PHRASE_RE and utils/dedup.py _STREAM_COUNT_RE were CLOSED-SET, but both approximate a meaning (“launch week” and “streamed 2B times” miss), and no model reads them, so both are MODEL-FALLBACK now. _RELEASE_WINDOW_RE stays MODEL-DONE: its one runtime caller passes the release_window read (_first_week_kicker(judged=True)).

CB-01 follow-up: the two false keeps on the residue (2026-10-19, #4583)

The CB-01 read kept two unnamed residue claims that name no kept chart, on 2 of 2 runs: “US Pop & AC Radio (Mediabase)” (read as pop radio) and “broke Spotify’s then-record for the most-streamed song in a single day” (read as the Spotify chart).

What changed:

Result on the same 131 residue claims, 4 runs on main and 4 on the branch:

claim main keeps branch keeps
US Pop & AC Radio (Mediabase) 4/4 0/4 (rule)
Spotify then-record, most-streamed in a single day 4/4 0/4
43.0M Spotify streams a day (a count) 1/4 0/4
Back to No. 1 on Billboard Global Charts 3/4 4/4
Drake, most #1 albums on “Billboard Hip-Hop/R&B A…” 4/4 3/4 (1 unreadable answer about the cut-off log text)
the other 126 same same

Run-to-run flips: 2 on main, 1 on the branch. The chart_story_read eval passed 2 of 2 runs with a fourth golden (the Spotify record).

CB-08 result: classifier and verify field folds (2026-10-18, sub-epic #4557)

Coordinator call: measure every site first. Where the rule is not worse and a change touches under about 1 post a month, the site is MEASURED-KEEP with the number. A field PR ships only where the measurement shows real misses.

site measurement (real text, 30 days to 2026-10-18) verdict
quotes_ladder_rung (_RUNG_CLAIM_RE) The rule read a year as a threshold: “83% odds of being the top US artist in 2026” was dropped as ladder_rung 7 times. 29 real market claims, 2 runs: the model and the rule agree on 26; the model is better on 3 (the year case twice, “17+ passing TDs”); 0 worse; 0 flips. MODEL-DONE, ClaudeClient.market_claim_is_rung (purpose=market_rung), market kind only
figure_is_passed_threshold OR-branch (_THRESHOLD_CUE_RE, _THRESHOLD_CUE_DURATION_RE, _DURATION_WORDS) 55 real milestone stories with Perplexity research, 2 judge runs. The rule fires on 91 of 246 texts. On the verified stories, threshold_passed agreed with the rule on 4 of 4 rule hits, and it flipped between runs on 2 of 3 rule misses. “Trust the judge alone” changes 0 in the sample and adds flips. MEASURED-KEEP
rank_list_tag (16 regexes) 92 real ranked-list posts through the real classifier. The rank_list field and the rule differ on 1 post, where field-first would label a “top 10” tier as a rank. MEASURED-KEEP
figure_is_rank_word (_RANK_WORDS) 641 distinct real figures (1,610 events): 0 fires, 0 misses found. MEASURED-KEEP
_STREAM_SCOPED_RE 0 fires on 310 real wire posts; the classifier’s stream_shape already refuses a scoped catalog count. MEASURED-KEEP
figure_unit_phrase (_UNIT_PHRASE_STOP) A stop list of function words. CLOSED-SET
claim_chart_kind The count path ran 9 times, always on one chart: chart_keys_for already narrows by chart_key, so the songs / albums word never picks between two charts. Inline, no registry line. no change
cert fields (cert_claim_format, cert_reportable prose, cert_prose_*) measured in #4590, see “CB-08 certification sites” below MEASURED-KEEP

A field on classify_music_news was tried first for the rung and NOT shipped: on 218 real posts, 2 runs each, it moved a chart forecast (“expected to surpass Boyz II Men for the sixth most weeks at #1”) to the market kind on 2 of 2 runs, where 3 runs of the old prompt read it as a chart story. A one-word label on the market claim alone has no such reach. About 1 call a day, cached.

Found on the way (#4583 shape, not fixed here): a career total scoped to “lead-credit songs” (“BTS … 55 billion streams with lead-credit songs”) reads as an unscoped career shape.

CB-08 certification sites (2026-10-18, sub-epic #4557)

Measured on the 30 days to 2026-10-18 (Axiom music_news_filtered reason=cert_out_of_scope and the 51 shipped music_news_posted phase=cert lines):

site measurement verdict
cert_reportable prose branch, cert_prose_names_other_body / cert_prose_is_riaa (_IN_SCOPE_CERT_PROSE_RE, _OUT_OF_SCOPE_CERT_PROSE_RE, _UK_CERT_PROSE_RE, _US_CERT_PROSE_RE, _FOREIGN_CERT_MODIFIER_RE) The prose branch (a cert id with no named body) cut 25 of 25 foreign certs correctly: New Zealand, Italy, Germany, Japan. 50 of the 51 shipped certs are US or UK. The one wrong ship was an Italian FIMI cert (Bad Bunny, 2026-10-06 16:50Z, artist-pull lane). It came through the OLD prose branch, 16 minutes before #3914 deployed: the classifier gave cert_id 5xplatinum| (no body), claim “5x Platinum (FIMI)” and source “FIMI”, and the old branch failed open, had no “fimi” word and did not read source. Every later copy of the story was cut. #4583 measured the remaining path, a kept body named on a foreign cert: 0 of 353 riaa / bpi ids in 30 days (the one prose hit was a real RIAA Latin cert), so no guard was added. MEASURED-KEEP
cert_claim_format (_CERT_ALBUM_RE, _CERT_SINGLE_RE) Read only by the RIAA “Multi-Platinum” level lookup (_riaa_level_for). That figure appeared 2 times in 30 days. MEASURED-KEEP

A model change here would move fewer than 1 decision a month. The 2026-10-06 ship came through a body the classifier named itself; a named body decides alone in cert_reportable. It is filed on #4583, not changed here.

Follow-ups the refresh did not do

CB-11 result: desk caption and qualifier fences (2026-10-19, sub-epic #4557)

Five sites, three outcomes.

site outcome real inputs result
catalog_stats _NAMED_COMPARISON_RE, _PLACE_ON_OTHER_METRIC_RE converted: Haiku catalog_caption (OK / NAMES_RIVAL / OTHER_METRIC), after the exact name and place checks; no answer drops the 51 catalog / daily-hits captions of 30 days (23 distinct), 2 runs; 16 goldens real: 0 disagreements, 0 flips. Goldens: Haiku 16/16 both runs; the regexes missed 7 (“right behind Drake”, “level with Drake”, “fourth in monthly listeners”) and falsely hit 1 (“after only six years”)
milestone_qualifier _AGGREGATOR_RE, _OFFICIAL_SOURCE_RE, _SCORE_RANKER_RE, _RELATIVE_SCOPE_MARKERS converted: Haiku qualifier_labels (source, scope) on each accepted qualifier; the lists decide with no label 78 accepted qualifiers (25 from a live replay with their reads, 53 production clauses), 2 runs drop decision: 0 worse; better on 2 both runs, 2 more on one run. Scope: 15 relative claims the list missed, 1 run worse (“this decade”). 7 label flips, 2 change a drop (each toward keeping a real official chart claim the lists drop)
milestone_qualifier _NO_QUALIFIER_MARKERS (phrase tail) MEASURED-KEEP 100 real lookup reads (50 subjects replayed) 0 reads opened with a phrase other than NONE; the 52 no-qualifier reads were all NONE or a hedge
music_desk _RESERVED_OTHER_CHART MEASURED-KEEP 924 real Spotify desk lines of 30 days, 2 runs regex 1 wrong (a Chartmetric “global artist rank” line, no effect); Haiku 3 wrong (“sixth highest globally” read as the Global chart twice, “Global Weekly” missed), 0 flips

The first qualifier shape was measured worse and not shipped. The batch row said to fold source and scope into qualifier_verify. A live replay of 50 real subjects (fresh Perplexity + Grok reads, old and new prompt on the same reads, 2 runs each) showed the two extra fields moved the Sonnet judge’s supported verdict on 14 of 43 judged subjects: about 7 better (Johnny Marr’s UK Top 5, Sheryl Crow’s 13th Billboard 200 entry) and 5 worse (a “13th solo entry” whose citations were about Miranda Lambert, a stale Spotify chart position, a blog’s “11th all-time”, an album rank given to a song, a lost real UK top 10). This is the CB-08 lesson again: a field added to a judge moves its other fields. The labels moved to their own Haiku read, which leaves the judge’s prompt unchanged.

The first label wording read a news site that reports a ranking (Yonhap, BroadwayWorld) as an aggregator and dropped two real superlatives on both runs; the shipped wording says a reporter is not the source.

Cost: catalog_caption about 2 Haiku calls a day (cached a week per caption); qualifier_labels about 2 a day (one per fresh verified qualifier, cached a week). The relative scope shortens the cache on about 1 in 5 qualifiers, so those re-verify after 12h.

CB-25 measurement (2026-10-19)

The set: the 5 reference_lookup rows in Axiom for 2026-09-19 to 2026-10-19 (all that exist; 132 /ask turns in the same days) and 67 hand-written /ask-shaped questions. Each was labelled with a route, a chart key (CHART) and a tables flag (ARTICLE) BEFORE any model ran. “Full” means all three are right. Two runs per model arm, with the classify cache cleared.

arm route right full right real 5 (full) flips p50 ms
regex chain (first source the chain selects) 51/72 49/72 3/5 0 0
Haiku, wording 1 71/72, 68/72 69/72, 66/72 5/5 4 667
Haiku, wording 2 (shipped) 72/72, 72/72 70/72, 70/72 5/5 2 (tables only) 655
Jev (Choice route + chart, Noul tables) 66/72, 66/72 58/72, 58/72 - 0 110

The regex chain’s misses include the two real ones: “New York Mets World Series titles” went to the movie source first (series is in _FILM_WORDS), and “Best Rap Song winner and nominees” got no tables. Others: “how many albums has she released” went to MusicBrainz (released), which answered with one karaoke album; “who are her producers” and “production credits” went to Genius; “when was it started” and “when did they form” were not seen as a founding; “highest charting song” was not seen as a chart question (charting).

Wording 2 added three meaning statements (songs produced for others are ARTICLE; anything about a person other than dates and names is ARTICLE; counts of number ones are CHART) and one cost statement for tables (“an extra table costs little”). WORSE than the regex, each listed: “Michael Jackson Thriller / how many copies sold” got no tables (both runs; the prose holds the sales figure), and “Kendrick Lamar / Pulitzer Prize” got tables in one run (one extra fetch). Jev lost on the same meaning cases the regex lost (“songs he produced” -> CREDITS, “who is he dating” -> PERSON), so Haiku ships.

Sub-page pick, 17 real articles with a candidate link list (labels written after reading the lists, before the model ran): old word weights 11/17, Haiku 15/17 on both runs, 0 flips, 0 worse. New: Michael Jordan -> career achievements, Harry Potter -> the film series, The Bear -> the TV series, Tom Hanks -> performances and credits. Both arms miss the Yankees seasons list (MAIN) and Michael Jackson (no page lists songs he wrote for others).

Title residue, 17 (subject, title) pairs the token rule rejects: the rule is right on 7 (the NO pairs). Haiku with the subject alone: 16/17, and its one miss was the dangerous kind (“REPO” -> “Repossession”, YES; the subject is a video game). With the caller’s subject kind in the input: 17/17 on both runs, 0 flips.

Movie routing: sending every ARTICLE question to TMDB as well (the audit’s first shape) put the Eras Tour concert film on “The Eras Tour / total gross” (1 wrong of 29). The FILM route alone sends no ARTICLE question there, so only FILM reaches the movie source.

End to end, real sources, 18 questions old vs new: better on 4 (Interscope founding, Dune runtime from TMDB, Olivia Rodrigo chart peaks, Taylor Swift album count no longer a karaoke album), the same fact from another source on 1 (Doechii’s birth name, now Wikidata), fewer wrong sources tried on 3, same on 10, worse on 0. Cost: about 0.2 route reads a day at about 1,000 tokens each, plus at most one sub-page read per tabular lookup and one title read per rejected lead-image title.

CB-28 measurement (2026-10-19)

The set: 30 days of real scoreboard inputs (our posts 2026-09-18 to 2026-10-18 plus the match window, read from Axiom: 3,495 posts; the four competitors’ posts from one paged twitterapi.io search: 1,360 posts). That gives 2,009 candidate pairs (inside the window, the subject words in their post) and 1,231 candidate story posts (not a reply, a repost or a look-back). Every arm ran twice on all of them.

Labels: an item where the rule ladder and every model run agree is taken as right. Every item where any run disagrees with the rules was read and labelled by hand: 181 pairs and 75 posts. 20 pairs are AMBIGUOUS and left out: a forecast against the final result, or our preview cut before its figure.

read arm wrong (run 1, run 2) worse than the rules flips
pair, 1,989 scored rule ladder 96 - 0
pair Haiku, wording 1 72, 71 41, 39 20
pair Haiku, wording 2 57, 56 30, 27 17
pair Sonnet, wording 2 (shipped) 17, 16 8, 7 8
post, 1,231 rule ladder 56 - 0
post Haiku, wording 1 22, 21 15, 14 3
post Haiku, wording 2 (shipped) 6, 6 4, 4 6

The rule ladder’s pair misses are mostly true pairs it refused: an emerging-artists #1, a prediction-market price, a ranked list that holds our figure, two posts that name the same event with no chart. Its false pairs are act-only matches across different facts (a crown race against a reaction post, an iTunes #1 against a different song’s return to #1). Its post misses are the chartmasters sales posts (“past 22 million equivalent album sales”), which carry no chart cue, and the polls and release posts it admitted.

Wording 1 told the pair read “DIFFERENT when you are not sure”, and Haiku refused true pairs that carried extra detail on one side. Wording 2 names the main fact and the details that do not change the story, with paraphrased examples, not the measured posts. Haiku still refused about 27 true pairs that the rules got right, so the owner rule’s second step applied: Sonnet, same prompt, same pairs. The posts stay on Haiku.

WORSE than the rules, Sonnet pairs (each listed): two Tinashe posts paired with her career history list (both runs); “The Art of Loving is No. 8” against the chart line (both runs); two release posts against a “will be released tonight” question post (both runs); three “Risk It All” most-streamed-of-2026 posts (one run each); a one-year-anniversary post (both runs). WORSE, Haiku posts: the year-end list prediction, the 2020s hitmakers ranking, the “most billion-stream songs” list and the VMAs most-wins list read as OTHER in one or both runs; the Grammy submission count and a recurrent-rule list read as STORY.

Golden: 10 known-DIFFERENT pairs from tests/test_competitor_scoreboard.py (a different chart, a different position, a different count, a different work, a cert level, a figure bound to another list entry) came back DIFFERENT on both runs of the shipped read.

Cost: about 70 pairs and 40 posts per scheduled run, two runs a day. The job installs anthropic and Pillow (a clean venv with only those and aiohttp imports ClaudeClient; config.py is not loaded) and gets ANTHROPIC_API_KEY on the step that runs the scoreboard.

CB-05 measurement (2026-10-19)

Every site was measured on its real 30-day traffic (Axiom, post_dedup_history, chart_history_weeks, the cached HITS documents, and live Kalshi and Polymarket reads).

site 30-day decisions result verdict
live market event pick (market_leg_candidates, _event_conflicts, _topic_tokens, _MARKET_ROLES, _MARKET_ALIASES) 17 live-leg reads (11 picked, 6 dropped) see below MODEL (Haiku market_event_pick over every candidate set) + year / role safety floor
Luminate subject tiers (_subject_tier_hits via reading_for_subject) 7 X-mention matches; the newsroom forecast path matched 0 3 of about 280 real subjects reach a non-equal tier today; 2 of the 3 are wrong MODEL (Haiku reading_subject on a non-equal tier hit)
threshold_metric (+ poly_event_matches) 13 parses, 2 distinct claims none was a metric-threshold claim, so None was right 13 of 13; poly_event_matches ran 0 times MEASURED-KEEP
restate_market_claim (4 _RESTATE_* regexes) 11 restates (10 shipped, 1 refusal) 0 wrong on the 7 real claim texts held MEASURED-KEEP
related_market_context substring match per desk units reading 30 real reading subjects x 102 live field-market outcomes: 3 exact hits, 0 substring-only MEASURED-KEEP (no registry line)
hits.projection_row substring match 2 market_drop subjects; the desk standing line 200 real subjects x both HITS documents: 42 matches, all exact, 0 substring-only MEASURED-KEEP (no registry line)

Event pick, real claims. The 16 distinct real claims of 30 days (11 shipped picks, the 5 drops) were replayed through the real _live_market_odds against live Kalshi and Polymarket and the bot’s Kalshi search index, 2 runs, 0 flips.

outcome claims
model pick = the event that shipped 10 (Coachella x2, Lana Del Rey, Bruno Mars, Rihanna, RAYE, Bruno Mars SOTY, Patient Zero, Octane nominee, Sienna Spiro)
no candidate today (event settled or gone) 4 (Housewife, Iceman, one Octane claim, Taylor Swift Oscar)
changed, neither 1: the 2026-10-13 Octane “market favorite for Best Rap Album” claim now picks the Best Rap Album WINNER event (right; the story was dropped later by the restate step that day)
WORSE without the floor 1: JAY-Z “favorite to headline the Super Bowl halftime show” took “Who will perform at the 2027 Big Game halftime show?” in both runs. The code rule refused it (role). The floor (market_role_conflict) vetoes it, so the shipped result equals today’s.

Three of the 10 were ties on today’s data (Lana Del Rey, Rihanna, Patient Zero), where main already asks the model. Hand cases built from real event titles with ONE candidate, labelled before any run: code rule wrong 5 of 10 (a “No. 2” claim took the Top artist event, a “week of Oct 24 #1” claim took the “#2 Song Week of October 31” event, an album claim took the new-song event, a “winning” claim took the nominees event, and a tie left a real nominee claim unread); model 0 of 10 in both runs.

Reading match. 12 cases (the 3 real non-equal hits and 9 hand cases in the real ticker shapes), 2 runs: the tier rule (keeps every hit) 7 of 12, Haiku 11 of 12 with the first prompt (it refused petal’s artist-named pure-sales ladder, the case the tier exists for) and 12 of 12 after one sentence that states the ticker format (the code after the dash starts with the title’s first letters; the example is GUTS, outside the set).

Calls. About 17 market_event_pick a month (cached 6 hours) and a few reading_subject (cached 12 hours). Under $0.01 a month.

After merge. ['tootsies'] | where event == 'music_news_recover' and tostring(parse_json(detail)['via']) == 'event_pick' | summarize count() by v=tostring(parse_json(detail)['verdict']), a=tostring(parse_json(detail)['agree']) and ['tootsies'] | where event == 'claude_api' and purpose in ('market_event_pick', 'reading_subject') | summarize calls=count(), failed=countif(ok == false) by purpose. A regression is an agree=false row whose picked title is a different question from the claim, or a rise in verdict=error.

CB-23 measurement (2026-10-19)

Two model reads replace three open lists. Each keeps its list as the prompt’s examples and as the fallback on a model error or a credit not yet read.

site model decides
riaa._NOT_AN_ACT (via own_credit) Haiku credit_act, ACT / NOT_ACT which unresolved RIAA artist cells a board may name
artist_watch.is_placeholder_credit in chart_standings._entries_by_act the same credit_act which Billboard parties the census, catalog and leader boards may rank
riaa_boards._IDENTITY_BRACKET_RE + _CREDIT_BRACKET_RE (via _bare_title) Haiku riaa_bracket, KEEP / DROP which bracket clauses a board row keeps

The boards are pure, so the desk runs an async pre-pass before each build (riaa.check_cells, st.check_board_credits, rib.check_titles, and each backfilled record-ledger week). A cached verdict is read first (ClaudeClient.classify_cached) and costs no model call; each pass sends at most 25 items to the model, most rows first, so the items a board can crown are asked first. A bracket that opens with a credit or a source (“feat.”, “with”, “from”) is a certain drop and is never asked: 0 of the 927 real clauses with that opener read KEEP. artist_watch._PLACEHOLDER_CREDITS stays the exact portrait skip (unchanged).

Data. The RIAA rows the bot holds in kv_cache (riaa_tallies, riaa_year_feed, riaa_recent: 1,703 distinct artist cells, 7,790 titles, 927 distinct bracket clauses) and every stored Billboard 200 and Hot 100 week (chart_history_weeks, 214 weeks, 2024-10-05 to 2026-10-17). Every input below is a real decision the old rule makes.

Act check, 2 runs, shipped prompt.

input n agree better worse flips
RIAA cells the rule names or drops 954 952 1 0 1
Billboard parties with 2+ entries in a week, and the 3 placeholder parties 184 184 0 0 0
hand-labelled hard credits (labelled before any run) 20 model 20 / 20 both runs     0

Bracket check, 2 runs, shipped prompt, 927 clauses. 919 agree with the rule.

change clauses
better (the clause names a different recording; the rule cut it) “(STRIPPED)”, “(EN VIVO)”, “(I’M GOD X DEMONS MASHUP)”, “(CHARLIE HEAT VERSION)”
worse (a kept subtitle or edition tag; the row is longer and names the same recording) “(FOOT OR 2)”, “(RELOADED)”, “(DEATH OF AUTO-TUNE)” (1 of 2 runs)
changed, neither “(FREESTYLE)” on “ALBUM OF THE YEAR (FREESTYLE)”, the song’s own title
flips 1 (“(DEATH OF AUTO-TUNE)”)

A first prompt that read the whole title was worse than the rule (about 20 wrong keeps, 13 flips): a title holding “REMIX” made it keep the feature credit after it. The shipped prompt reads the clause alone.

Effect on today’s boards. The top 20 of the Diamond singles, Diamond albums and 2026 year boards, and the census on every stored week since 2026-09-01: 0 rows change. The value is on the next placeholder or version clause the lists do not hold.

Cost. A cold cache asks at most 25 credits and 25 clauses a pass, each answer cached 90 days; the dry run (real rows, real Haiku, no recognition lists, so every cell counted as unresolved) stored 125 credit verdicts in 5 passes. After that, only a new credit or clause is asked. The measurement itself was about 4,600 Haiku calls.

After merge. ['tootsies'] | where event == 'claude_api' and purpose in ('credit_act', 'riaa_bracket') | summarize calls=count(), failed=countif(ok == false) by purpose, bin(_time, 1d) and ['tootsies'] | where event == 'music_ranking' and reason == 'credit_act' | project _time, count, detail. A regression shows as a credit_act row whose detail.not_act names a real act.

CB-17, CB-20, CB-24 measurement (2026-10-19)

CB-17, the album title pairs. The set: every pair of album titles where one title starts with the other, on the kworb album pages of 147 acts (the top streaming acts plus the acts the album lane has counted), 360 pairs. I labelled 311 of them by hand (V = the same record, D = a different album); 49 have no clear answer (a live album of a record, a remix EP, a tour edition) and are not scored. Each arm ran 2 times. The fail direction decides the metric: a wrong relation states a false album count, and a withheld one leaves the count out.

arm right wrong withheld flips
word list alone 230 13 68 0
before CB-17: the list, then the old prompt on the ambiguous pairs 275 36 0 0
old prompt on every pair (with the list’s read as a hint / without) 266-267 / 269-270 44-45 / 41-42 0 2 / 1
new prompt on every pair, model alone 289 18 4 8 of 360
new prompt + the list as a block (_combine, shipped) 281 12 18 8 of 360

CB-24, the stream report’s loose join. The set: the 96 distinct per-track milestone songs the qualifier events name in 30 days (295 events), rebuilt with the live kworb songs pages and the iTunes tracklists. 64 had an album join; 2 had loose-only rows: 3 rows, all the same song as their track (“Cameras / Good Ones Go Interlude - Medley”, two “Remastered 2009” Beatles rows). release_version_kind read the medley row NEW once in 3 runs, which would drop a true album track. track_version_join (row + track) read all 3 SAME in 3 of 3 runs, the same as the loose rule; on 9 constructed probes (a part 2, a sped-up cut, a remaster) it was right 8 of 9 and the loose rule 6 of 9.

CB-20, the manual Versuz card. The set: the two hand-made nights in #main-stage (2026-09-03, 22 polls, 44 options, 58 played songs). The title similarity matched all 44 to the right played song (read by hand). With each option’s own song taken out of the played list (the truth is then NONE), the similarity matched a wrong song 16 times of 44.

arm real options right leave-one-out NONE right flips
title similarity (kept) 44/44 28/44 0
Haiku, wording 1, played list shrinking as the night goes 43/44 not run 0
Haiku, wording 2, a 20-song cap 41/44 38/44 5
Haiku, wording 2, the full played list 42/44 24-27/44 15
Sonnet, wording 2, a 20-song cap 42/44 37-38/44 1

The model is worse on the real options, which is what the card shows, so the similarity stays (MEASURED-KEEP). Haiku with 43 labels picks the wrong song (“Fking Problems” to “Mo Money Mo Problems”) and misses a shortened title (“Diamonds remix”). A person reads the PNG before posting.

CB-26 measurement (2026-10-19)

Three manual scripts. They run by hand, a few to tens of decisions a run, so each site was measured on real inputs the scripts would see.

site inputs rule model verdict
music_drop_manual.video_kind (_VISUALISER_RE, _LYRIC_RE) 300 real YouTube titles off kworb’s music charts (2,798 held; every title with a kind word plus 60 at random), 2 runs calls 9 “Official Audio” uploads and 20 live performances “official video” 31 better, 1 changed-neither, 0 worse, 0 flips MODEL-DONE video_kind
riaa_credit_audit._SAME_RECORDING 36 real title-key mismatches: 595 RIAA single rows (Diamond singles, Drake, Future, Taylor Swift) against Deezer 36 of 36 right (karaoke, instrumentals, remixes, a parody, a Taylor’s Version) Haiku SAME / DIFFERENT 31 of 36 on titles alone (5 covers read SAME), 35 of 36 with the hit’s artist; 0 better MEASURED-KEEP
riaa_credit_audit._NOT_AN_ACT_NAME the 54 guest names on those candidates, 2 runs 2 rejects (two karaoke labels) credit_act 3 rejects: the same 2 plus “HOT 100” (a karaoke label credit) PROTECTED floor + credit_act on top
artist_sweep board_stream_totals with no client real sweeps of Zach Bryan and Olivia Rodrigo, with and without the client   identical output: no album fell to the tracklist sum wired (the existing album_track_match)

Two prompt rounds for video_kind. The first read unmarked titles as AUDIO or OTHER (Avicii “Levels”, James Arthur “Say You Won’t Let Go” are music videos). The shipped prompt says a title with no marker is MUSIC_VIDEO, and MUSIC_VIDEO and OTHER keep the rule’s word, so the model changes the kind only where the title says so. Its language examples (“Paroles”, “En Directo”) do not occur in the measured titles.

The Deezer replay used Deezer’s plain search: its advanced search (artist:"" track:"") returns no rows from the session sandbox, while production’s advanced searches hit (deezer_lookup albumtop 15 of 24 on 2026-10-19). Plain search returns the same kind of fuzzy match the audit exists to reject, more often.

Not covered by any CB-26 site. Of the 9 candidates the rules ACCEPT, about 4 are a different version under the same title (the Phoebe Bridgers “The Night We Met”, the Kendrick Lamar remix of “Bad Blood”). Equal title keys are the free accept by design; the script’s docstring already says a person checks every candidate before writing.

Part 1 (history): the 2026-09-18 audit and its worklist

This part is the first audit as it stood on 2026-09-18, with each worklist item’s outcome added as it shipped. Its verdicts (HAIKU / HYBRID / KEEP) follow the OLD rule. Part 0 gives the 2026-10-18 verdict for every site. “Part 2” in the text below means the 2026-09-18 inventory, which is in git at commit 4e1b6c0.

How to read this document

Scope. Every .py file under cogs/, utils/, the repo root, and the runtime scripts (scripts/ops_monitor.py, scripts/axiom_setup.py, the manual mod tools). Tests are excluded. Five audit passes each read one partition of the tree in full. The partitions are the five inventory sections below.

What counts as a site. A regex, a keyword list / set / dict of terms, a substring or prefix / suffix test, or a dict lookup keyed on free text, that decides one of:

A site is SEMANTIC when the input is natural language and the rule approximates a judgment. A site is STRUCTURAL when the input is a URL, a payload key, a number, a date, a filename, a log line, or our own enum. Structural sites are listed but never candidates.

Verdicts.

verdict meaning
HAIKU Replace the rule with a small-model call. The rule stays as the fallback when the model errors.
HYBRID Keep the rule as the fast path or the fence. Add a model call only for the ambiguous residue.
KEEP Leave as code. The reason column says why: structural, money path, safety fence, hot path, privacy, or already behind a model judge.

The rule that decides the verdict is already in docs/ENGINEERING.md, “The deterministic / model-judged boundary”: repeatable behavior lives in code, model judgment is for taste and language, and “don’t hand-code brittle heuristics for a genuine judgment call”. This audit applies that rule site by site.

Line numbers are anchors from the audit date. They drift. The stable reference is the symbol name in each row. Every symbol in the worklist was re-checked to exist on 2026-09-18.

The numbers

Site counts per inventory section. The counts are the audit passes’ own tallies.

section semantic sites structural sites HAIKU HYBRID KEEP
Music, newsroom, identity, feeds ~118 ~110 ~9 ~25 ~84
Markets, betting, sports, media 94 154 9 22 63
Conversation, X, gates, output checks ~80 ~190 ~6 ~12 ~62
Charts, cinema, ops, infra 76 ~110 3 12 61
The 16 remaining files 41 39 1 8 32
total ~410 ~600 ~28 ~79 ~300

About three quarters of the semantic sites stay as code. That is the expected result: settlement math, dedup keys, identity joins with a documented incident, the constitutional fences, the permission allowlist, and the per-message hot paths are all deliberately deterministic.

Cost baseline. Axiom, 7 days to 2026-09-18, claude_api events:

model family calls / week
Haiku 41,069
Sonnet + Opus 1,455
total 42,524

Haiku already carries 97% of the bot’s model calls. Tier A below adds zero calls. Tier B adds cached per-ticker or per-channel calls, in the low thousands per week at most. No item on the worklist changes the cost picture.

Three findings that shape the worklist

1. The biggest keyword surface sits on top of an existing Haiku classifier. classify_music_news (Haiku) already returns kind / subject / claim / figure / cert_id / milestone_id / outcome / source. About 20 regex parsers in utils/music_news.py and cogs/music_news.py then re-parse its claim prose to recover facts the classifier could have emitted as fields: chart key, debut vs re-entry, projection vs settled, threshold vs count, stream-claim shape, market outcome / odds / venue / deadline / award, cert territory, tour / live / non-audio. The same shape appears in markets (market_subject_image re-derives what image_subjects already classified) and in X mentions (asks_question runs beside scan_music_mention). The cheapest fix for these is “add a field to the existing call”, not “add a call”. That is Tier A.

2. The model-output FENCES are the second-biggest class, and most of them stay. utils/output_checks.py, claude_client.is_empty_response, ship_guard, the ungrounded_* number checks and the retrospective arithmetic are deterministic on purpose: each cites a measured judge miss (a career claim the judge scored 0.92, four decline narrations that shipped at 0.72 to 0.78). The number and arithmetic checks stay. The PROSE fences (decline narration, self-correction, cross-source comparison, career / all-time claims) grow one phrase per incident and are the ones a model reads better. Those are Tier B4 and Tier C8, with the regex kept as the free first pass.

3. Identity matching is deterministic by design, and the fix is a rescue rung, not a swap. apple_music, deezer, artist_watch, chart_credits, catalog_art, steam_art, market_art, espn.match_team_logo each carry a documented incident and Codex rounds. A model here re-opens the wrong-face / wrong-cover class. The one measured gap is the UNKNOWN band: 10 of 150 unknown-artist cuts in 30 days were watched acts lost to spelling artifacts, and _title_matches at ratio 0.6 to 0.85 picked the wrong link three times. The pattern that already works is claude.match_album_tracks: deterministic fold first, cheap prefilter, Haiku closed-set confirm on the residue only. That is Tier C.

The worklist

Do them in tier order. Within a tier, the order is brittleness times user impact. Each item names the site, the shape of the fix, the added call volume, and the fail direction the replacement must keep.

Every replacement PR carries the same five things. A new method on ClaudeClient following classify_abuse (label) or topic_duplicate (JSON tri-state) with skip_persona=True and the judge temperature; the old rule kept as the fallback when the model errors; the surface’s fail direction preserved (gates fail closed, reads fail open); an event with a purpose= so the ops monitor sees it; and a golden fixture that must keep failing.

A new item adds its call through ClaudeClient.classify (#4199). It takes a purpose, the task text, the input, a closed label set and an optional cache_key and ttl. It fixes the parts a caller must not get wrong: Haiku, skip_persona, the judge temperature, max_tokens of at least 16, and a prefix match on the upper-cased answer. It returns the label, or None on an error or an answer outside the set; None is never cached, and the caller maps None to its own fail direction. So a new item is a task text, a thin wrapper that maps the label, and a golden. is_music_artist_mention is the first wrapper. The methods shipped before it (same_screen_work, classify_abuse and the rest) are not migrated.

The classify cache is durable (#4231). A label lives in the kv_cache table under namespace classify for the caller’s ttl, so a deploy does not clear it. Before #4231 it was in memory only, and each deploy re-asked about 565 market_sport_check and 47 market_act_check verdicts. The cache key holds a hash of the model id, the task text and the label set, so an edit to the prompt or the labels asks the model again. A caller does not need to change its purpose or cache_key after a prompt edit.

Tier A: fold into an existing Haiku call (zero added calls)

# site fix fail direction
A1 (SHIPPED 2026-09-18, #3413) cogs/music_news.py _TOUR_CLAIM_CUES, _TOUR_CLAIM_ANTI_CUES, _NONAUDIO_RELEASE_CUES, _LIVE_CLAIM_CUES, _LIVE_CLAIM_ANTI_CUES, _LIVE_VERB_RE, _played_at_subject, _TOUR_UPDATE_CUES, _is_tour_subject Add event_shape: release / tour / tour_update / live / nonaudio to classify_music_news. Retires five phrase lists, two anti-lists and a subject-anchored regex grown over four owner reports in two weeks. Each miss ships the wrong kicker, the wrong art and a listen link on a concert. Unknown shape reads as release (today’s default).
A2 (SHIPPED 2026-09-18, #3414) utils/music_news.py charts_named, sole_weekly_chart, sole_chart_text, _forecast_near_chart, unsettled_chart_label, named_chart_label, _CHART_PATTERNS, _FORECAST_WORDING_RE Add chart_key (closed set from CHART_SOURCES) and is_projection to the classifier. The regex stays as the tiebreak. A wrong read today becomes absent and the stale gate drops a true story. No key: fall to the regex; still-no-key: web verify (today’s path).
A3 (SHIPPED 2026-09-18, #3415) utils/music_news.py stream_claim (_STREAM_*_RE), entry_count_claim Add stream_shape / rung / count fields on the milestone kind. The regex already produced a false CONFIRM (Codex #2774). No fields: fall to the regex.
A4 (SHIPPED 2026-09-18, #3416) utils/music_news.py figure_is_passed_threshold (_THRESHOLD_CUE_RE + duration / plus regexes) Add count and threshold_passed to verify_music_claim. Five Codex rounds to tell “100 entries” (a rung) from “107” (the count). Absent field: keep the wire figure (today).
A5 (SHIPPED 2026-09-18, #3417) utils/music_news.py market_claim_parts, market_award, market_caption, _MARKET_ODDS_LEAD_RE, _MARKET_VENUE_TAIL_RE, _MARKET_DEADLINE_RE, _MARKET_AWARD_RE, _PROBABILITY_FIGURE_RE Ask the classifier for outcome / odds / venue / deadline / award on the market kind. Five regexes parse the classifier’s own prose today. Missing slot: blank caption (today).
A6 (SHIPPED 2026-09-18, #3418) utils/music_news.py claims_debut, claims_reentry, _DEBUT_RE, _REENTRY_RE (has_debut_cue + its two cues retired: no production caller) Added chart_event: debut / reentry / move / hold / none on the chart kind; music_news.chart_event_of reads it ahead of the regexes at the debut dedup key, the deep-platform exemption, the repost gate and the unconfirmed-debut rewrite. Since #4131 the card’s era guard reads it too (mn.chart_story_is_same_record): hold / move / none / exit keep an old catalog row on a chart story; debut, reentry, “” and an edition word keep the guard. Bounded: a debut / re-entry needs the wording somewhere in the wire, a move / hold can only narrow. ”” (no field): the regexes decide as before.
A7 (SHIPPED 2026-09-18, #3419) utils/x_mentions.py asks_question, _QUESTION_STARTS, _REQUEST_CUES, _REQUEST_RE Added asks_question to scan_music_mention (the same Haiku read). x_mentions.mention_asks reads it ahead of the regex; a true on a wordless text (emoji, a bare link) is bounded back to the regex; a false on a text carrying a literal “?” is bounded back too (2026-09-20: a two-question-mark reply came back false and got no draft); no flag falls back to the regex. False: parent not fetched, no_facts (today).
A8 (SHIPPED 2026-09-18, #3420) utils/market_outcome.py names_match, normalize_name, _in_field winner_extract already hands the model the leg labels and takes an INDEX back, so the 6-char prefix matcher was a second matcher that could only fire on a pick the extractor cannot produce. Retired names_match; _in_field is an exact re-check of the closed-slate contract. No index: no winner line (absent, today).
A9 (KEEP, decision on #3421, 2026-09-18) utils/market_subject_image.py detect_media_brand, is_youtube_video_market, is_youtube_daily_chart_market, is_netflix_rank_market, artist_subject_name; utils/market_cards.py is_music_chart_market The premise was wrong: the card build does not call image_subjects before these gates (image_subject runs only at step 3 of the art resolver, for an un-hinted market), so the fix would add a call per card. The detectors read Kalshi’s own fixed series titles and scope a fail-CLOSED suppression gate: a structured source, kept deterministic per the ENGINEERING boundary. Unchanged.
A10 (SHIPPED 2026-09-18, #3422) cogs/discourse.py drop_self_correction at four lanes Measured first: three drafting shapes the regex misses (“no wait, scratch that:”, an Option 1 / Option 2 list, “actually, let me try that again:”) scored 0.78-0.87 with the judge and would have shipped. The discourse_score rubric now carries a SHOWS ITS WORKING clause (0.0-0.2); the regex stays as the pre-judge backstop. Three eval goldens pin it. Low score: skip (today).
A11 (SHIPPED 2026-09-18, #3423) cogs/music_news.py _songstats_verify ("stream" in claim.lower()) _songstats_verify reads mn.milestone_metric(milestone_id) (the classifier’s canonical id) ahead of the claim substring; the substring is the fallback for a story with no id. Same.
A12 (SHIPPED 2026-09-20, #3549 + #3544) utils/x_mentions.py charts_named, _CHART_CUES, _kind_nearest, _ALBUM_WORD_RE, _SONG_WORD_RE Added charts ({platform, region, kind} on closed vocabularies) to scan_music_mention, the same Haiku read. x_mentions.charts_from_scan maps it to chart families and order_units_for_question takes them ahead of the cue regex, which stays as the fallback for a reply with no field. The regex grew through six Codex rounds on #3543, each a new phrasing (“Billboard says she’s #1 on Spotify”, “Spotify Global 200”, a track named by title); measured live, the classifier reads all fourteen fixture phrasings right at the platform level (scan_music_mention_charts, four goldens). The same prompt edit makes a pushback on the bot’s own number an ask (#3544, two fixtures on scan_music_mention_asks). None (no field): the regex decides; a platform outside the vocabulary is dropped at parse.
A13 (SHIPPED 2026-10-16, #4200; N2 + N3 of the 2026-10-15 refresh) utils/music_news.py rank_list_tag (the 14 _RANKED_LIST_* patterns), claim_platform, count_label_on_platform, artist_ranking_label, _names_spotify_streams, cert_claim_format, the cert territory regexes Measured first, on 104 real wire posts (2 runs each, old and new prompt). ONE field earned its place: rank_list on milestone / chart, which VOUCHES for the list name the regex parsed: it lets a comparison tail the safe-end allow-list refuses (“moving past …”) through only when its words equal the parsed name. It never names a list on its own (two Codex rounds on #4212 showed that standalone bounds re-implement the regex’s guards badly). It agreed with the regex on 10 of 10 labels, recovered the 2 the allow-list refused (“moving past”), and the bounds refused both wrong values (0 wrong accepts). claim_platform now reads “YouTube Music” whole (it printed “YouTube”). NOT added, by measurement: platform / count_label (0 of 93 stories had a claim platform with an empty source, the only case those readers decide); cert_format (the post rarely says album or single, so a field bounded to the post adds nothing); cert_territory (every cert carried a body in cert_id, and the scope gate fails CLOSED by owner steer, so a model admission would widen it). _names_spotify_streams and artist_ranking_label stay: each is a bound on the classifier’s own output (Haiku shaped Apple Music as Spotify 3 of 3, #3854; five Codex rounds on #3816). No field, a refused field, or a model error: the regex decides, as before.
A14 (SHIPPED 2026-10-16, #4201; N4 of the 2026-10-15 refresh) utils/music_news.py states_chart_exit, post_states_forecast, wire_is_next_week, post_states_later_week / first_week_figures Measured first, on 432 real wire posts (2 runs per prompt; 4 runs of the old prompt for the noise floor). ONE value earned its place: exit in the existing chart_event set, enum text only (an added example raised drift from 35 to 53 stable field changes and added kind changes). states_chart_exit reads the field first: exit cuts inside the regex’s own title-scoped bound plus “departs”, where a “from #N” or a peak position does not block; any other event is no exit; “” is the regex. It cut 13 of 13 real exits against the regex’s 5, refused all wrong exit labels (the TTPD “Only ‘Robin’ drops off” album, “departs the top 10”), and dropped the regex’s 1 false cut (“… After Festive Exit”, a #1 story). NOT added, by measurement: week (Haiku answered week=this on 3 of 3 HITS forecasts it also called is_projection=false, so narrowing post_states_forecast brings back #3874, and the rule forbids it to widen; wire_is_next_week compares the wire to the LIVE page, which the post cannot know); chart_event on first_week (Haiku read “aiming to re-peak at #2” as debut, the #3860 failure). Output tokens: no change (144.5 vs 146.8 mean). No field, or a reply with no event: the regex decides, as before.
A17 (2026-10-16, #4214 / #4216 / #4264, wave 8; title judge SHIPPED 2026-10-18, wave 19) utils/music_news.py title_grounded_in_wire (the title_ungrounded drop); cogs/music_news.py chart card block; the ranked-list milestone dedup key Measured first, on 30 days of Axiom. #4264: NO Haiku rescue. Of the 152 title_ungrounded drops, real wire text (twitterapi.io) showed 8 real titles lost to a deterministic bug: _wire_words strips “‘s”, so the contraction “She’s” never matched (“She’s The Best”, “i think he’s got a girl”). Fixed exactly: a contraction word keeps its “‘s”. The model band had 0 confirmed correct restores and 1 wrong one: the issue’s own example, “DtMF”, is the SONG, while the classifier named the ALBUM “Debí Tirar Más Fotos”; “L.A.M.B.” reached the wire as a t.co link, so no checker can see it. The same stripped “‘s” dropped an artist such as “Destiny’s Child” in grounded_subject (class sweep, fixed). #4214: the unsettled chart card takes the rank_list_tag block, zero calls. #4216: NOT built – about 13 ranked-list milestone stories in 30 days and 0 repeats of the same act on the same list, so the key would have held nothing. Wave 19 (2026-10-18), owner rule “open sets go to a model”: the title judge shipped. A title_grounded_in_wire miss now asks Haiku (title_quote_in_post, purpose title_in_post) for the post’s OWN WORDS that name the title, and mn.quote_grounds_title keeps the story only when the quote is in the post AND shares a content word with the title. Measured on 47 real word-match misses (1,515 subject/post pairs, 2026-09-27..10-18, X wires; tweet text re-read by id), hand-labelled, 3 runs per arm: a YES/NO Haiku judge passed GUTS (lyric line) and L.A.M.B. (a t.co link); a reworded YES/NO judge was worse; Jev (Noul, 2 runs) was wrong on 5 at the 0.5 cut, GUTS at 0.77-0.82. The quote judge kept 11 real titles 3/3 (“The R&B Tour” x5, “The Writing’s On The Wall”, “Formula Of Love: O+T=<3” x2, “Bad and Boujee” glued to the next word, “House of R&B Concert” x2), “The Lucky Me Tour” 2/3, and 0 memory titles (GUTS, L.A.M.B., DtMF, the venue descriptions: 0/3 each). WORSE: 1, the “GOLF” jersey collab description, kept 2/3 (the post does state that collab). Flips: 2 of 47. About 6 calls a day, about 400 input tokens, p50 530 ms. The 135 artist-pull drops (Perplexity text, not stored) were not measurable; query: ['tootsies'] | where (event=='music_news_recover' and tostring(parse_json(detail)['via'])=='title_judge') or (event=='music_news_filtered' and reason=='title_ungrounded') | summarize count() by event, tostring(parse_json(detail)['rescue']). Title: a possessive word stays stripped (event names such as “Karol G’s Houston show” stay dropped). Chart block: no list name, or a settled live read, keeps the old block. Title judge: NONE, a quote that fails the check, or a model error drops (today).
A16 (2026-10-16, #4204 N10; wave 19 2026-10-18) utils/chart_history.py _HEDGE_RE in parse_nora_text (projection vs hedge); utils/billboard_stories.py _LEADER_TITLE_RE Wave 19: Haiku reads EVERY rank sentence, not only the hedged ones (nora_records_checked -> claude.classify, purpose=nora_rank_check, CALL / CONTEST). The hedge words are now in the prompt, and the regex decides only when Haiku gives no verdict. The #4204 fast path (“no hedge word = a firm call”) stored wrong Billboard 200 #1 rows: “the #1 is too close to call”, “currently in the lead for the #1 debut with a 2k lead”, “has reached #1 on US iTunes”, “the longest-running #1 hit on the Global Spotify chart”, “It is 190k streams away from #1”. Measured 2026-10-18 on 552 real Nora posts and posts citing them (2026-06 to 2026-10): 60 rank sentences, 57 hand-labelled. Shipped behaviour (regex, plus the old prompt on the 15 hedged ones) was wrong on 9; the new path is wrong on 3. Better 6, WORSE 0, wrong both 3 (two “above #90” bounds, one “could spend a third week at #1”). 3 runs: 2 sentences flip. About 0.4 new calls a day (45-day cache), about $0.00004 each. _LEADER_TITLE_RE is KEEP: it only orders the story candidates, and the page parse decides. CONTEST drops the rank. No verdict, an error or no client: the hedge regex (a hedge word drops the rank, as before).

Tier B: a new cached Haiku call (low volume, high user impact)

# site fix volume fail direction
B1 utils/market_cards.py hero / outcome cluster: outcome_split and the 232-entry _OUTCOME_VERBS, _field_clause, outcome_subject, named_rank, title_rank, winner_rank, award_slot, split_rank_authority, event_title_split, statement_title, how_much_title, subject_hero; plus cogs/market_drop.py _slot_figure / _RANK_WINNER_TITLE_RE / _kalshi_event_subject, cogs/market_alert.py _ending_soon_subject / _clean_authority, utils/alert_triggers.py outcome_label One call per market ticker returning {hero, caption, subject, outcome, rank, authority}, durable-cached. The regex ladder stays as the fallback. Retires about 25 regexes across four files. A verb outside the registry ships a raw question as the hero today. one per new ticker Model error: regex ladder (today). Shipped in part (#3424): the Haiku split runs on the regex RESIDUE only (prime_outcome_splits), feeds outcome_split and so every subject / caption / quantity-title / hero reader, and is grounded word for word. The regex stays the fast path. #4209 (2026-10-16): the FIELD shape now rides the same split with no second prompt (_field_split: “Who will headline X” goes in as “Will they headline X” and is kept only when the subject is the placeholder; “When will X " goes in as "Will X "), and a quantity tail with no subject ("How many N.Y.C. Groceries stores will open") titles deterministically. Live board: 18 titles better, 0 worse. MEASURED and left on the regex, because they do not miss on real titles: the rank rungs (42,708 live strings: every rank-word string `named_rank` skips is a set, "Top 10" / "Top 4", or an election "2nd place", none a slot it should read), the authority rung (0 live hits, the 30 unregistered prefixes are NBA seed boards it must refuse), and `alert_triggers.outcome_label` (0 of 28,116 live legs lack `yes_sub_title`, so its title fallback never runs; it also feeds the stored `claim_key`). `event_title_split` missed 9 "Top ...: " year-end heads; **#4306 fixed it deterministically** (a "Top" head over a bare year tail, method note dropped): 9 of 26,036 live titles change, 0 worse, no model call (the residue is empty). Dry run 2026-10-16: 80 residue questions, 65 split, 8 refused as ungrounded, 7 said unsplit. **Model first (2026-10-18, owner rule: the verb list is model input).** `outcome_split` now reads the Haiku split first and the regex second; `prime_outcome_splits` asks for every question the drop and the alert card, with `_OUTCOME_VERBS` in the prompt as examples. A model error, a refusal or an ungrounded answer falls back to the regex, and an unprimed caller (the music lanes) reads the regex as before. Grounding now folds curly quotes. A field question that writes "will" in lower case refuses a capitalised word after "they" ("What will AutoZone say" gave the verb "AutoZone"). "it" joins "there" as an empty subject (79 live "Will it ..." cards titled "it"). Live board (7,772 questions: 6,945 "Will ..." + 827 field and quantity forms; Kalshi event index + open markets): 433 card texts change, 423 better (the regex split on a noun: "win SET 1" 128, "GROSS domestic product" 130, "average", "use", "kill switch"), 8 WORSE (5 "X minority + Y support form the next government" read "support" as the verb, both runs; "Taiwan's ... repeal referendum pass" 1 of 2 runs; "Fred again.. perform" and "ACA Marketplace enrollment decline" lost their split in 1 run), 2 neither; 8 card texts flip between two runs. Verb-list ablation on the 959 disputed questions: no list vs list = 8 better / 5 worse, inside the run-to-run noise (5), so the list is kept as the owner rule asks. Jev (word-position Choice, same 460 questions): about as accurate as Haiku, p50 790 ms; Haiku kept (it already returns the spans; about 22 calls a day at $0.0004, so the saving is cents). Stay code, re-measured: `named_rank` / `title_rank` / `winner_rank` / `award_slot` / `split_rank_authority` / `_RANK_WINNER_TITLE_RE` / `statement_title` / `how_much_title` heads (closed formats, registered CLOSED-SET); `_subjectless_tail` (36 of 36 live tails right); `event_title_split` (open head list, registered MODEL-FALLBACK: on 719 refused live heads Haiku called 63 events, about 21 wrong, and Jev 39, about 11 wrong: question tails, an album title, show and song titles, a seed board; the regex only misses, which keeps the category block); `_kalshi_event_subject` (0 of 13,291 live events reach it); `_ending_soon_subject` / `_clean_authority` (our own authority enum). PROTECTED, not changed: `alert_triggers.outcome_label` (stored `claim_key`) and settlement. **#4552 (2026-10-19):** the split also returns the agreeing verb ("now"), so the decided caption and the quantity title no longer decide singular / plural on a trailing "s" alone (that rule read most names as plural), and a plural "be" reads "are". Live sample of 270 questions, 2 runs: 81 decided captions change, 71 better, 2 worse ("Caps", "Lamphun Warriors" given "wins"), 8 neither; 7 flips. `build_market_card` now primes, so the music lanes read the model split too: 150 live music titles, 3 better, 0 worse (1 worse before `_PERSON_SUBJECTS`), 14 whitespace only. `_OUTCOME_ANCHOR`, `_OUTCOME_VERBS` and `_FIELD_ANCHOR` move to MODEL-DONE.
B2 (SHIPPED 2026-10-16, #3425) utils/markets.py _is_cut_runner_up_event, _names_non_leader_slot, _looks_like_music The regex now only NOMINATES a cut; confirm_runner_up_cut asks a cached Haiku RUNNER_UP / OTHER label (claude.classify, purpose=market_runner_up_check, per event ticker) and cuts only on RUNNER_UP. The music cue list stays the fast path (kept, no call). Live 2026-10-16: the regex nominated 5 events (Netflix #2 x4, Google #2), Haiku confirmed 5 of 5; on 25 cue-less boards x3 runs the regex wrongly cut 7 of 8 music boards, Haiku 3 of 75 calls wrong. Event market_label_check. about five per week Model error: keep the event (fail open) and emit reason=judge_failed.
B3 (SHIPPED 2026-10-18, wave 19) utils/retrospective.py _FRAMES, is_retrospective, retrospective_frame, states_past_interval The anchored regex stays the fast path (a match costs no call). Every post it misses goes to Haiku once (prime_throwback -> ClaudeClient.throwback_frame, purpose=throwback_frame, event wire_throwback_check), which answers current or names the post’s own time-frame words. ground_frame keeps the words only when the post holds them, they name a time (a year, a time unit, “this day/date”, a tag) and no year is this year or later. Every reader (retrospective_frame label model, the note, retrospective_values, the grounding) then reads those words exactly as it reads a regex match, so the arithmetic is unchanged. states_past_interval stays code: it reads the CLAIM only when the desk’s retro flag (now fed by Haiku) is false. Measured on 3,526 real wire posts (music_news_seen ids, 2026-09-27 to 2026-10-18, text from twitterapi.io): the regex found 63 throwbacks; Haiku agreed on all 63 and added 57, all hand-checked real (most are @Kalshi_Music “… released X N years ago today.”, with the frame at the END). WORSE 0 (two Billboard all-time-list posts and one war-photo post are refused by the output check). Both miss about 7 (birthday posts that cite a year, “Remember when …”, year-trivia with no frame word Haiku reads). 2 runs: 2 of 3,463 flip. Haiku p50 0.52 s, p95 0.79 s. Jev (Choice) was measured on the same posts and NOT chosen: it cannot return the frame words the note and the number checks need, and it flagged unrelated posts (war photos, a tour gross, a pre-show clip) as throwbacks. End-to-end on the production compose model (gpt-cheap): 9 of 12 takes ship framed; 3 drop on a pre-existing retro_interval false positive (“since 1998” in the claim). At most about 170 calls a day (every wire post the desk sees), about $0.00007 each. per regex miss, cached 30 days No client, an error, an unreadable reply or ungrounded words: the regex verdict (no throwback), as before.
B4 utils/output_checks.py decline-narration battery (decline_narration_hits), _SELF_CORRECTION_RE / has_self_correction, claude_client._SELF_CORRECTION_MARKERS, utils/wheel.py is_narration / _NARRATION_PHRASES One “finished post, or the model’s working?” judge, purpose="decline_narration", over the composed text. Fail toward drop. Covers three phrase lists that each grow one incident at a time. one per compose Model error: regex battery (today). Done in part, 2026-10-18 (owner rule: the lists are the trigger and the model’s input): ship_guard.drop_self_correction (17 lanes) asks Haiku ClaudeClient.shows_working when a list word touches the post, and the regex decides only on a model error. Measured: 4 of 11,294 real composed posts in 30 days trigger a call; 0 real drops before or after; correction goldens 12 of 21 caught by the regex, 17 of 21 by the model; finished goldens falsely dropped 2 of 18 (the real titles “Hold On, We’re Going Home”, “Wait for Me”) against 1 (the test line “let me fixate”). The direct has_self_correction sites (x_mentions, discourse icebreaker, sports_boards, music_desk, market_alert, cinema_desk, the wheel parser) and _strip_self_correction stay on the lists (follow-up). decline_narration_hits stays the decider: on all 1,874 real chart-lane posts Haiku caught the same 7 real declines, missed the same 6, and dropped 4-5 climbs the rule passes.
B5 (SHIPPED 2026-10-16, #3428) utils/markets.py _kalshi_competition_to_sport (_KALSHI_SOCCER_CUES + substring ladder) The tag does not select a settlement feed: it only SCOPES a game’s Kalshi search (search_kalshi_events ... AND sport = $4). Every EPL row had no tag, so an “Arsenal vs Everton” search scoped to soccer found only the Champions League “Bayern Munich vs Arsenal” row. KalshiClient._apply_sport_checks now asks check_competition_sport (claude.classify, purpose=market_sport_check, SOCCER / GRIDIRON / BASKETBALL / BASEBALL / HOCKEY / TENNIS / MMA / NONE) once per competition per process, over the name and 3 event titles, before the hourly FTS rebuild. Dry run 2026-10-16 on all 564 live competitions, 2 runs: 138 tags change. The ladder was wrong on 6 competitions (119 events: EuroLeague and “Italy Serie A” basketball as soccer, eSoccer and eBasketball as real sports, the Czech soccer “National Football League” as NFL, the Rugby World Cup as soccer) and left about 130 real sport competitions untagged (EPL, Liga MX, KHL, the Grand Slams). The model was wrong on 2 to 3 per run, all non-team sports (table-tennis “TT Star Series” as tennis, darts “Modus Super Series” as MMA, and “Portugal LPB” basketball as soccer in 1 of 2 runs); 1 flip and 1 unreadable answer per run. About $0.02 a pass; the first refresh after a deploy pays it, later refreshes make no calls. A prompt that added darts and “a competition you do not recognize” made it worse (4 errors a run) and was reverted. about 560 per deploy, then 0 Model error: substring ladder.
B6 (SHIPPED 2026-10-16, #3429) utils/curator.py _IMAGE_TOPIC_TERMS, topic_is_image_room, prefers_media; cogs/curator.py mode decision; cogs/curate.py prefers_media call judged_image_mode: density first (no call at or over the ratio); below it a cached classify IMAGE / LINK (purpose curator_image_room, per channel + name + topic, one day, in memory) over name, parent, topic and six short samples. Measured 2026-10-16: the six live rooms all read IMAGE by density (0.88 to 1.00), so 0 calls and 0 mode changes in production today. On a labelled set of 16 low-density rooms the topic list got 6 right (7 picture rooms missed, 3 link rooms set to IMAGE), Haiku 15 (one no-topic room with chatter samples read LINK, which the list also missed), 0 wrong IMAGE. 3 runs: 48 of 48 and 45 of 45 answers identical run to run. per low-density room per day (0 today) Model error: the old density + topic-list rule, exactly.
B7 (NOT BUILT 2026-10-16, #3430: measured not worth it) The /ask reference router: utils/reference.py _SOURCES chain, _CHART_INTENT, _TABULAR_CUE; utils/genius.py _DETAIL_INTENT / _SAMPLE_INTENT / _MEANING_INTENT / _LYRIC_INTENT; utils/musicbrainz.py _MUSIC_INTENT; utils/reference_movie.py _FILM_WORDS / _STRONG_CUES; utils/wikidata.py _FACTOID_INTENT; utils/chart_data.py detect_chart One call per reference lookup returning {source, clean_title, year, aspect, chart}. Replaces six regex lists ordered by hand-tuned shadowing rules, and fixes the missing “is the TMDB hit the right title” check. The calling model already decided “this is a reference question”. per /ask tool turn Model error: regex chain. Outcome (#3430): the router was not built. Axiom held 5 reference_lookup calls in 30 days (2026-09-16 to 2026-10-16), and each one reached the right source, so a Haiku call per lookup had nothing to fix. The one real harm was B15’s TMDB title, now checked.
B8 (SHIPPED 2026-09-18, #3426) utils/sports_stories.py classify_sport (_SPORT_KEYWORDS, about 250 words) Inverted: sports_desk._resolve_sports asks classify_sports_beats for EVERY story (cached 48h by signature, 72 new per pass in calls of 24); the handle map and the keyword table decide only a story the model did not place. Measured on 951 live stories: the table left 175 of 309 unknown vs the model’s 116, was wrong on all 6 disagreements, and the handle map was wrong 23 of 515 (WNBA on @nba). about 40 cached per slot Model error: regex. #3431 (2026-10-15) added the shared-nickname goldens (Panthers, Rangers, Kings) and re-measured on 649 live stories from the last 2 days: the rules alone left 113 unknown, the model-first path 24; 102 tags changed; the rules were wrong on about 12 of the changed tags, the model on about 6, 4 of them combat used for a post that is not UFC, MMA or boxing. The combat guidance now says “and nothing else”, which cut those 4 to 2 in two re-runs. 28 Haiku calls, about $0.07 for the whole set uncached.
B9 (SHIPPED 2026-10-15, #3432) utils/kworb.py is_functional_audio (_FUNCTIONAL_AUDIO_RE + stream-shape gate) Keep the regex as the bulk filter over 1000 rows; run Haiku only on rows that enter the rendered top-N of a daily-ranked board. A miss ships white noise at #1 to X. Shipped as kworb.screen_functional_top + ClaudeClient.functional_audio_check (purpose functional_audio), called from music_desk._artist_board_units on the artist streams, listeners, movers and songs boards. Dry run 2026-10-15 on the live pages: 0 of 32 rendered rows changed verdict; the judge caught 6 of 6 invented white-noise names and kept 10 of 10 real acts and sleepy-titled songs. about 30 per fresh fetch, then cached 30 days Model error: regex.
B10 (MEASURED 2026-10-16, not built, #3433) utils/markets.py kalshi_channel_routing (_MUSIC_CUES, _CINEMA_CUES, _TV_CUES, _POP_CUES) No Discord channel reaches this function now. Its channel caller, MarketsManager.kalshi_category_markets, has no caller outside the tests: the drop and alert rooms use the explicit /menu category since #480. Its one live input is the topic argument of the /ask kalshi_trending / kalshi_movers / kalshi_competitive tools, and Axiom shows 0 kalshi_trending reads in the last 30 days. A Haiku call here would have no traffic to improve. 0 in 30 days Cues (unchanged).
B11 (SHIPPED 2026-10-16, #3434) utils/markets.py qualify_platform_metric (_PLATFORM_PATTERNS, nine platforms) The one-platform rule stays the fast path. When the rules name TWO or more platforms, confirm_event_platform asks Haiku for one from the closed list + NONE (purpose=market_platform_check, per event ticker), and the pick is used only if the rules name it. Rules that name no platform get no call: no text could ground the answer, so the MrBeast / PBD ‘subscribers’ titles (YouTube only in the SERIES settlement source) stayed unqualified; #4246 (2026-10-16) reads the series settlement_sources deterministically when the rules name none: one platform there is exact, two store nothing (no model call; 6 of 58,280 live snapshot titles change, all right). Live 2026-10-16: 0 events in the residue, 4 rewriting series unchanged; on 27 two-platform rules texts x2, 52 of 54 right, 2 arguable (an Apple Music primary with a Spotify fallback). per new ticker Model error or NONE: no rewrite (today).
B12 utils/banger_lane.py search_terms (_STOP, _QUOTED_RE, caps-run heuristic) Entity extraction from a headline: “which names should we search X for?”. a few per day Model error: regex. DONE (#3435): ClaudeClient.headline_search_names (Haiku, skip_persona) names the entities per headline; banger_lane.judged_terms keeps only names found word for word in that headline and uses the regex for a headline with no checked name, so a model error gives the old terms exactly. Measured on 72 live wire headlines: regex junk terms about 40 and 5 empty results, Haiku about 6 weak terms and 0 empty; 6/72 headlines changed between two Haiku runs.
B13 (SHIPPED 2026-10-16, #3436) utils/luminate.py metric_from_text (ALBUM_METRICS), parse_subject (_SUBJECT_RE) The cue rules stay the fast path. metric_cues drops the timeframe cue (“first week”) and the sales-inside-pure-sales overlap, so one label settles the metric. subject_is_ambiguous sends the rest to one cached Haiku call (claude.luminate_subject, purpose=luminate_subject): no metric cue or two different ones, no leg that parses, or two acts in one comparison. ground_event_subject keeps a value only when it is in the text (metric only from the closed set), and a two-act comparison credits no act. Wired into the music sweep (music_markets._checked_subject). Dry run on 121 live events: 71 fast path, 50 model calls, 14 changed (1 two-act misattribution and its wrong metric fixed, 12 artist-total subjects cleaned), 0 new misattributions, about $0.02. per Kalshi event (cached 7 days) Model error: cue list.
B14 (SHIPPED 2026-10-15, #3437) utils/perplexity.py is_hedged (_HEDGE_MARKERS) as used by claude_client._has_perplexity_grounding The forced web-search retry in chimein_post / discourse now asks ClaudeClient._perplexity_grounded: an empty block is not grounding (no call); otherwise a cached Haiku FOUND/NONE (claude.perplexity_found_answer, purpose=pplx_found_answer) reads the answer prose without the header or SOURCES. is_hedged stays the hedged telemetry flag. Dry run 2026-10-15, 40 real Sonar answers to chime-in hooks and discourse categories: the phrase list accepted 33, Haiku 30-31 (two runs). Every one of the 8 answers about invented subjects is NONE (the phrase list accepted 5 of them); 3 real answers the phrase list refused are FOUND. One borderline real answer (“teased a new album, no date”) flipped between runs. About $0.0001 per call. Sibling: research_disputes (#4132) is now the fallback for the verify judge’s own web_disputes flag (same call, no extra cost). On 24 real confirmed stories the regex flagged 13 and the judge 14; the judge also caught a different-figure answer and a Spanish answer the regex missed. Model error: phrase list (as before). Disputes: no flag = regex.  
B15 (TMDB half SHIPPED 2026-10-16, #3430) utils/reference.py _SUBPAGE_HINT, _ASPECT_SYNONYMS, _aspect_weights, _relevant_subpage A pick_*-style call over the subpage titles given the question. Untested today. Folds into B7. per /ask reference Model error: main page. Outcome (#3430): the movie source now checks the TMDB hit with one cached Haiku pick (ClaudeClient.pick_reference_film, purpose reference_film_pick); a live dry run on 30 film-routed questions: 28 and 29 right against 12 for the old top hit. The Wikipedia subpage half is not changed: it had no observed failure.
B16 utils/riaa.py display_title recasing (_INITIALS_RE, _SMALL_WORDS, _ROMAN_RE, …) One recase call over a card’s rows. Rules print SZA as “Sza” and DNA. as “Dna.” on public cards. per card Model error: rules. DONE (this PR): riaa.recase_texts + riaa_boards.recase_board, Haiku purpose riaa_recase, one cached call per card, each row checked by recase_valid (only the case may change).
B17 (SHIPPED 2026-10-14, #3996) cogs/music_news.py _news_x_facts: artist_from_subject split, is_daily_stream_count, _MONTHLY_LISTENERS_RE, _X_STREAM_SOURCES, the chart-unit read (chart_unit over claim_chart_keys) New call classify_x_post (topic / milestone / artist / title / unit / itunes), made by utils/x_gate.py only for a story whose X copy would ship (about 30-60 a day). A song or album unit is kept only when the unit_from words it quotes are in the story. The regexes were removed, not kept as a fallback (owner steer: replace). Fail-open: a failed read is OTHER, so the post goes to X ungated, as any gate error does.  

B4 shipped (#3427, 2026-10-16). The phrase lists stay as the free first pass. The “finished post, or the model’s working?” judge rides the self-gate scorers as one more hard fail, so it adds no call: claude_client._SHOWS_ITS_WORKING_RULE, placed by _with_working_rule into every branch of music_desk_score, drop_score and cinema_desk_score (discourse_score already had its #3422 form). A 0.0 score is under every ship floor, so the fail direction is drop; the reason starts WORKING:. The newsroom’s N7 hedge and unnamed-source regexes got a matching CONFIDENCE DISCLAIMER line in the music-desk numbers rubric (reason HEDGE:). #4289: that line, together with the Haiku 5.5 move (#3939), dropped forecast posts (“forecast to open with ~32K”) at 0.0. The numbers rubric’s UNGROUNDED line now says a forecast the ground truth gives, stated as a forecast, is grounded; the HEDGE line also names “says it may change”. On 316 real tracking posts, 2 runs, drops under 0.6 went from 118-122 to 5-7; 100 of 100 hedge goldens still fail. N6 (misstated_chart_frame) needed no change: the numbers rubric already fails “another chart period … or a hold it does not give (‘briefly’)”. The wheel has no scorer, so it asks classify(purpose="decline_narration") once per wheel and once per slice only when that first answer is WORKING; a model error keeps the regex verdict. NOT moved: decline_narration_hits and _MARKET_DECLINE_RE. They judge the DIRECTION of a rank or a figure (a rank pair compared as numbers, a decline verb beside a figure), which is a deterministic fact, and they apply only on some lanes (a chart EXIT narrates its fall on purpose), which the shared scorers cannot see.

Desk numbers rubric: the reason before the score (wave 15, 2026-10-17). With the score first, Haiku 5.5 sometimes gave a correct post 0.0-0.12 and then wrote a reason that ended “clean, grounded, score high” (4 of 119 hard-fail reasons in 14 days, and more on the Ashe forecast). The numbers rubric of music_desk_score (the desk’s numbers lanes and the newsroom) now asks for {"reason": ..., "score": ...}, with _DESK_SCORE_MAX_TOKENS = 400, and the UNGROUNDED line says “Start the reason with ‘UNGROUNDED:’”. Zero added calls. On 332 real and real-derived posts, 4 runs a side: 0 verdicts worse, 9 posts better, and 0 flips between runs (old: 1-6). A re-score on a Haiku classify() read of the reason was measured too and not shipped: the check said PASS on a WORKING: fail and a HEDGE: fail. The release rubric keeps the score first (measured worse there). has_self_correction also matches “let me correct” and “let me fix” now (#4331 shape 2).

Desk IDENTITY fence shipped (#4261, 2026-10-16); RECORD measured, not shipped (#2784). has_identity_subset_claim misses slang (“carrying the ladies on this board”, “five guys and SZA”). It stays as the free first pass. music_desk_score now carries claude_client._IDENTITY_RULE in all three branches (reason IDENTITY:), plus one line that keeps the reply to the JSON object. Zero added calls; the fail direction is drop. IDENTITY fired on 0 of 260 verdicts over 130 real shipped posts, and the old rubric shipped the slang goldens at 0.78-0.82.

The RECORD twin for ungrounded_ranking_claims (“the only artist alive with …”) was NOT shipped. On 59 real chart-lane takes scored against the source production passes (source=blob), it put 24-28 under the floor against 15-20 today: the judge read an act’s own entry count (“her eleventh entry”) as an ungrounded record, because the career fact the compose used is not in that source. The same PR passes that fact to the scorer on the chart and called-shot lanes (music_desk._score_source), which alone lowered the old rubric’s drops to 13-16. With that fix RECORD was only level with today, and other callers (x_mentions, the newsroom) still pass a reduced source, so #2784 stays open.

#2784 closed deterministically instead (2026-10-17). ungrounded_ranking_claims now reads the universal-scope words as a ranking context: “in the world”, “on the planet”, “ alive" and "living ". Those are exact. A scope word inside a CLOSED quoted title is skipped ("Best Rapper Alive" is a song), and a digit standing rank before "in the world" ("her 71st most-streamed artist in the world") is not flagged. Every other superlative near "alive" / "living" is a CANDIDATE (`universal_scope_candidates`), and a new Haiku label decides it at the desk (`claude.universal_scope_claim`, CLAIM or MOOD; owner steer: a model decides the fuzzy cases, not a longer noun list). CLAIM or no verdict joins the ranking guard's drop path; MOOD ships. It asks whether a claim EXISTS, so it may run on an empty source; `claim_grounded` still never runs on one. Old vs new over 10,725 distinct real texts (30 days of desk, newsroom and alert composes from Axiom, plus 800 full @tootsiesbar posts): 0 verdicts changed and 0 candidates, so 0 added calls a day. On 17 HELD-OUT candidates that share no wording with the prompt's examples, 2 runs each: 32 of 34 verdicts right, 0 flips, 0 claims read as MOOD. The miss ("the warmest crowd alive", CLAIM both runs) fails toward drop. A candidate's lead is "only", "most" or any "-est" word, so "finest" and "loudest" are read too. The digit standing-rank skip covers "in the world" and "on the planet" only. "on earth" was NOT added: U2's live title "The Greatest Show On Earth" appears in 30+ real composes. NOT added: `drop_score` (#4276), `pop_desk_score` and `chimein_score` (0 calls in 7 days).

Drop IDENTITY fence shipped (#4276, 2026-10-16). drop_score (254 calls in 7 days) carries _IDENTITY_RULE, one pass line for sports event names (_EVENT_NAME_PASS) and _FENCE_SILENT, through _with_drop_fences, in all five branches. Production gives this scorer the market’s numbers without its title, so the pass line says an event name passes even when the ground does not repeat it. The music desk’s rule text is unchanged. Measured on 142 real-lane inputs: live Kalshi music and sports boards rendered by market_drop._render_market_blob, and Kalshi game markets rendered by betting_board._line_blob / betting_alert._move_blob. Each was composed on the live guild’s model and scored against that same blob. SGO returned no games, so the game lines fold Kalshi prices, as the live board does now. Two runs per arm, both arms fresh:

measure old new
noise floor, run vs run (mean abs) 0.098 0.037
under 0.6 50, 47 33, 30
ship to drop on both runs   0
drop to ship on both runs   10

The 10 drop-to-ship posts are correct takes the old judge docked for a market framing it could not check (no title in its ground). IDENTITY fired on 4 of 284 verdicts, all on two posts with no gender word that the old rubric also scored 0.0. Drift-run goldens (10 fixtures): 5 gender-sorting takes 0.00 on every new run (the old rubric shipped 2 at 0.82); 5 event-name takes (“the women’s final”, WNBA, “men’s singles”, “Women’s World Cup”) ship on every new run. The live eval drop_score_fences keeps 7 of them (3 gender-sorting, 4 event-name). A first wording without the “ground does not repeat it” clause failed two real event takes as IDENTITY (“Chess.com Woman Player of the Year”, “WTA Singles Finals”). A third wording with “a post with no gender word passes” failed a women’s-singles take, so it was cut.

Working rule on the result scorer (#4228, 2026-10-16). winner_line_score now carries _SHOWS_ITS_WORKING_RULE. 3 calls in 7 days, so the drift run was the trivial-clean check: 46 inputs (7 real shipped lines plus 39 real-lane composes, one per market_outcome_calls row), scored against the production fact. Noise floor 0.015, mean drift +0.002, 0 crossings either way. Goldens: 4 of 5 working lines score 0.0 on the new rubric (the old one shipped 3 of those 4 on at least one run); a trailing EMPTY still scores 0.92-0.95, a known limit.

Skipped from #4228, with 7-day call counts (Axiom claude_api by purpose):

scorer calls 7d why skipped
pop_desk_score, sports_desk_score, cinema_news_score 0 (0 in 30d) No real post exists: their *_events tables are empty, so there is no drift run.
chimein_score 0 It scores the chat buffer, not a composed post, so the rule does not apply.
board_score 0 No production caller (only scripts/dryrun_betting_board.py).
commentate_score 0 (33 in 30d) The production source is the live game feed at post time, which is not stored. A drift run would need a substitute source.

Tier C: hybrid rescue on the ambiguous band only

# site fix fail direction  
C1 (wave 19 re-measured 2026-10-18: every site STAYS CODE) utils/artist_watch.py lenient_tier, s_variants, _ALIASES / canonical_credit; utils/music_news.py wire_spelling, grounded_subject, artist_grounded_in_wire, corrected_name Haiku “is X one of [these candidates]” ONLY on an UNKNOWN verdict that is about to CUT a story (#3439, shipped). Wave 19 measured each remaining site on real data and converts none: (1) s_variants: 0 tier raises on 617 distinct credits in 21 days, so a model there has no traffic to judge. (2) The rescue label set: 37 of 45 rescue cuts (2026-10-15..18) had no near name, so the judge was never asked. Arm “near names + the whole watched list” on the 226 real cut credits, 5 runs: it mapped band members and relatives (Chris Martin -> Coldplay 1-4/5, RM -> BTS 4/5, YG Marley -> Bob Marley & The Wailers 2/5) and lost NBA YoungBoy (0/5 against 5/5). A reworded prompt fixed the members and lost KPop Demon Hunters. WORSE, not shipped. (3) _ALIASES: an exact table read before the gate; fed to the judge, it reached 0 of the 226 cuts. (4) wire_spelling: 1 change in 1,515 real pairs (Beyonce); 76 of its 77 near candidates were connector forms (&amp;, “and”, “ft.”) it correctly refuses; no miss found. (5) grounded_subject: market stories only, 0 drops in 30 days. Pre-existing, not changed: the near-name judge maps “Ghost Town (Ghost)” to Ghost 5/5 in every arm (My Boo is Ghost Town DJ’s). Model error: cut (today).  
C2 (SHIPPED 2026-10-15, #3440) utils/apple_music.py _title_matches; utils/deezer.py album_title_matches Both stay the prefilter. A pass that is not equal and scores under 0.85 (title_match_band; for an album, a new core word or overlap under 0.85, album_match_band) is the band. An equal row in the same result set drops the band rows with no call. Otherwise one cached closed-set Haiku pick (claude.match_catalog_title, purpose=catalog_title_match) keeps the picked title or none. The installment rule and the non-Latin rule run first, so Slime Language and Cinderella never reach the model. _artist_matches / _credit_similar are NOT changed: no band incident, and the 0.8 floor plus the partial-name guard already refuse the known shapes. Dry run (90 real chart rows): 1 changed link (Bad Bunny “Bokete” no longer links “Vete”), 3 Haiku calls. Model error: no link (absent). No judge attached: the old rule.  
C3 (SHIPPED 2026-10-16, #3441) utils/release_type.py is_side_version (_SIDE_MARKERS, _BARE_TRAIL_MARKERS, _FEAT_RE, …); N11, the edition / variant tag readers album_milestones._relation, release_type._EDITION_MARKERS, _REMASTER_RE The regex stays on the bulk rows. The two announce paths ask the cached Haiku claude.release_version_kind (new / recut / edition, purpose=release_version): the release board over its first 20 in-window rows, and the new_entry lane on a regex-flagged title and on a regex-passed title it is about to keep. release_type.judged_side_version maps the label. N11: album_milestones._relation keeps its word list, and only an AMBIGUOUS pair goes to claude.album_variant_relation (purpose=album_variant). _EDITION_MARKERS and _REMASTER_RE are KEEP: they build persisted song keys and group a song’s uploads, so they must stay deterministic. No new title regex (utils/title_forms.py already owns the feature and radio-edit forms). Dry run: 80 calls on 14 days of real candidates, 2 decisions changed, both right, 0 wrong. #4230: every side_version drop now names its judge; the watch lane’s presence feed keys a side version on its own edition key, in its own <chart>:side record (_presence_key), so a model-cleared one is established after its window. The music_alert debut lanes stay regex-only: on 13 real regex-flagged titles Haiku changed 0 outcomes for the better and cleared 2 instrumental / a cappella album editions the lanes would post as debuts. #4268: a RENDITION marker in the version suffix (instrumental, a cappella / a capella / acapella, karaoke, sped up, slowed, reverb; release_type.rendition_marker) is RECUT with no model call, and judged_side_version treats it as a side version whatever the verdict. Haiku labelled the 4 real titles of that shape (“Popstar (Instrumental Edition)”, “Bass Persuades (A Cappella Edition)” and their pairs) EDITION on 2 runs of 2. In 399 real watch_new_entry / apple_debut titles (30 days) the marker matched those 4 and no other title. The bulk regex now also spells “A Capella” with one p. Model error: regex (the release check); withheld count (the album check).  
C4 (SHIPPED 2026-10-16, #3442) utils/market_subject_image.py take_references_leg and cogs/market_drop.py fabrication-gate routing The gate now runs on every chart family: the YouTube daily-chart series plus every single-slot chart board with a clear leader (is_chart_slot_market: Netflix, Billboard, Spotify). The cheap pre-check is take_names_leader_only (the take names a leader by its whole phrase and no rival leg -> ship, no call). Everything else asks the existing Haiku claude.market_take_names_leader (purpose=market_take_check). No prompt change. take_references_leg stays for utils/youtube_grounding.take_names_a_leg. market_alert follow-up SHIPPED (#4239): the alert’s LEADER takes (lead change, confirmed ending-soon leader, swing on the leading leg) on every chart family now run the same two steps, fail closed; other takes on a YouTube daily-chart board keep take_names_a_leg. Dry run (2 runs, Haiku): 18 real alert takes from Axiom plus 15 inverted-overtake and 3 invented-song goldens. Old gate: 0 real drops, 14 of 15 inverted shipped. New gate: 1 real drop (a take that contradicted its own license), 0 to 1 of 15 inverted shipped, 0 invented shipped, 4 fast ships, 34 judged, 1 verdict flip across runs. No prompt change. Dry run (91 real takes, 2 runs per judged take): 55 fast ships, 21 judged, 2 decisions changed, 0 wrong ships, verdicts identical across runs, 42 calls for $0.0013. #4271 (2026-10-16): prompt left as is, measured. The prompt still says “If you are UNSURE, answer true”. Test set: 232 real chart takes from Axiom (market_drop_scored + market_alert, 30 days): 190 as posted and 42 with the leader swapped to a rival the take names. Two runs per arm. Old prompt: 0 verdicts moved between its two runs, 187/190 real passed, 2/42 swapped passed. Cut the UNSURE line: 3 to 7 moved vs old, 184-186/190, 1-2/42. Reword it to “answer false”: 3 to 7 moved, 185-186/190, 2-3/42. Cut only the stale last line: 2 to 3 moved, 187-188/190, 1-3/42. Every arm dropped 1 to 2 correct runner-up takes on both runs, and none stopped more swapped takes on both runs. The #4239 overtake goldens: 5/5 dropped on every arm. Also #4271: the chart_take_gate panel now counts the alert’s market_filtered drops, split by event. Model error: drop (fail closed).  
C5 (SHIPPED 2026-10-16, #3443) utils/music_policy.py _IN_HOUSE_TAGS / in_house_genre, at cogs/music.py:_house_gate and cogs/music_news.py:_artist_new_release The exact allow list stays, and an on-list tag makes no call. An off-list tag goes to music_policy.judged_in_house_genre, which asks one cached Haiku label (claude.house_genre_check, purpose=house_genre, RAP / RNB / POP / NONE) with artist + title + tag. No call for a blank tag, a blank artist or title, or an owner-rejected tag (_NEVER_RESCUE: Indie Pop, K-Pop, Pop Latino, Pop/Rock). Dry run on 103 real off-list records (production drop history through iTunes, the 3 live off_genre refusals, 12 known hits): 3 Soundtrack-tagged classics rescued, 0 wrong accepts of 95 hand-labelled outs, 103 calls, 30.9k input + 0.5k output tokens. NONE or model error: refuse (today).  
C6 (SHIPPED 2026-10-16, #3444) utils/chart_ages.py _matching_date (now _matching_pick), in resolve_week_ages (the oldest-songs board) and first_release_date (the RIAA plaques card) The containment match and the earliest-date pick stay. A pick whose folded title AND folded credit EQUAL the row makes no call. Any other pick goes to one cached Haiku SAME / DIFFERENT label (claude.classify, purpose=chart_age_check) that sees the group title, credit, primary type and first-release date. Measured on the live Hot 100 + Billboard 200 (top 40 each, 2026-10-16): 63 old picks, 61 exact (no call), 2 checked; the check refused Drake “Classic” dated 2015 off Bob Drake’s album. The debut gate (release_age, exact=True) passes no client and is unchanged. DIFFERENT: undated, cached as a miss. Model error: undated, nothing cached (retries next build).  
C7 (SHIPPED 2026-10-16, #3445) cogs/music_releases.py _REISSUE_RE _REISSUE_RE stays the cheap yes. A title it passes that carries Deluxe / Edition / Version / Live / Expanded (_REISSUE_ASK_RE) goes to the cached Haiku claude.release_is_reissue (REISSUE / NEW, purpose=release_reissue), through MusicReleases._reissue_keys, at all three stat sites (the board’s first-night stat, the first-week card, the Songstats fill). Not release_version_kind: that check labels a re-recording and a new deluxe “edition” too, and both carry new plays. With no model client, the regex alone decides. Also #4230 item 3: the first-week roster now reads the release-version verdicts. Dry run, 58 real titles (46 asked), 2 runs: wrong-accepts 25 -> 2, wrong-omits 0 -> 4 to 6 (2026 records Haiku does not know). Model error: omit the stat.  
C8 utils/output_checks.py has_career_claim, has_alltime_claim, ungrounded_career_ordinals, ungrounded_ranking_claims, has_cross_source_comparison, metric_foreign_word (+ N8, K5 from the 2026-10-15 refresh) Done for the two sites that drop posts (#3446). The regex stays the trigger. A flagged caption goes to ungrounded_flagged_ordinals (ordinal values, code) and then to Haiku claude.claim_grounded (a classify label, not qualifier_verify: that judge PICKS a qualifier from research; this one checks a written line against one source). Wired at the qualifier guard (_ranking_drop, 44 distinct drops in 30 days; since #2784 it also sends a fuzzy “alive” / “living” candidate from universal_scope_candidates to the Haiku label claude.universal_scope_claim, CLAIM or MOOD, claim_arbiter kind=scope; CLAIM or no verdict keeps the drop) and the bare-row career check when a career block exists (_drop_shaped, 3,484 drops in 30 days, all before #4083). The ranking regex also skips a duration (“its first month at 47.8K”). Not wired, measured 2026-10-16 over 30 days: has_alltime_claim (desk boards, cinema) 0 drops, ungrounded_career_ordinals (newsroom) 0, metric_foreign_word 0, has_cross_source_comparison 1, has_row_arithmetic (K5) 0, has_stream_unit_comparison 0. A judge there would only add false-ship risk. has_cross_source_comparison and metric_foreign_word also ask a different question (an owner ban, a metric name), not grounding. Revisit a site when its drops show on the panel. Re-measured 2026-10-18 for #4522 (30 days, Axiom), kept as regexes: has_alltime_claim 0 drops (music_desk board lanes, of 21,212 music_desk_scored rows; cinema_desk alltime_claim, of 46 rows), ungrounded_career_ordinals 0 (music_news career_ordinal, of 1,586 rows), has_cross_source_comparison 0 (music_desk and market_drop, of 21,212 and 1,377 rows), metric_foreign_word 0 (music_desk and music_alert, of 21,212 and 1,366 rows). The music_desk reason career_claim is shared: all 3,424 such rows have matchup chart or called_shot (_BARE_ROW_LANES), so they are has_ungrounded_career_claim (Haiku since #3446), not has_alltime_claim. Recall check: 1 of the 11,149 shipped music_desk / cinema_desk takes holds “ever”, “all-time”, “history” or “career” (a chart-lane career first, a different check). A model read here would add a call on every take to decide drops that do not happen. Model error: drop (kept).  
C9 (SHIPPED 2026-10-16, #3447) utils/music_markets.py title_names_act The trigger is now title_act_hits: every watched act whose folded name is a whole-token run in the title, with no capitalisation rule. Each hit, in watchlist order (at most 3 a title), goes to one cached Haiku YES/NO (title_is_about_act through claude.classify, purpose=market_act_check, 7 days per act and title, in memory). The first YES is the act. judged_title_act replaces the old rule at the three artist-market call sites (listing, settle, and _artist_market_candidates for the move and leader lanes). A NO emits market_filtered reason=not_the_act; no verdict emits reason=act_check_failed and stops all checks for 10 minutes. The candidate list runs its checks 8 at a time. With no client, title_names_act (the old rule) still decides. Dry run 2026-10-16, 179 real titles (89 Kalshi + Polymarket act markets, 90 sport-filed Kalshi and Polymarket titles that name an act): wrong accepts 6 -> 0 (Derik / Patrick / Brenden Queen, three VP.Future esports titles); missed acts 9 -> 0 (two “Katseye” titles, seven “ Streams in 2026"); 1 other change (a bare "Future" sport leg, now refused); 178 calls, 33,258 input and 712 output tokens. A first prompt that named the field "ACT" read the word literally and answered NO on 88 of 89 act markets. Model error: skip (the market is not the act’s).  
C10 (SHIPPED 2026-10-16, #3448) utils/music_news.py chart_cue_strength slate ordering, chart_reportable unnamed fallthrough, cert_reportable territory prose, _RUNG_CLAIM_RE / quotes_ladder_rung Slate: the cue order stays the shortlist and still drops a post with no cue. When it holds more than the 12 culture places, ONE Haiku call per slot (ClaudeClient.rank_music_news_slate, purpose=music_news_slate_rank) ranks its first 40 posts and returns indexes. mn.apply_slate_rank drops a bad index, caps one account at 3 places and fills the rest in cue order, so the model never adds a post and the classify budget stays 12. Replayed on 12 real slots (3 days of the 41 wires, 2 runs): real stories in the 12 places, cue order 118 of 144, Haiku 131 and 129 with the per-account cap (133 and 134 without it); 2 small-pool slots ranked below the cue order; about 3,450 input and 47 output tokens a call. unnamed: reads the classifier’s EXISTING chart_key (no prompt edit, so no drift): kept only when the key’s label maps to the kept Spotify daily family and “spotify” is in the text. 4 of 369 real chart stories changed, all “Spotify Global” / Spotify US wordings; Billboard keys on unnamed stories stay cut. Cert and rung: measured, not changed. 0 of 53 cert_out_of_scope cuts in 30 days were wrong. 0 of 41 real percentage claims were a missed rung, and every market story is re-read from the exchange on either branch. A new classifier field would move other outputs (#4212) for no measured gain. Model error: cues (the order on main).  
C11 (MEASURED 2026-10-16, no change, #3449) utils/kworb.py find_video_match non-unique picks; utils/billboard.py find() with more than one match; utils/riaa.py _NOT_AN_ACT unresolved cells; utils/riaa_boards.py, utils/cinema_numbers.py _clean_news; cogs/discourse.py looks_like_sports; utils/retrospective.py frames_as_past No site changed. Each site was measured first, and none has an ambiguous case that is both frequent and wrong. Axiom covers the 30 days to 2026-10-16; RIAA was read from the prod riaa_tallies / riaa_year_feed caches. (1) kworb: 30 market_subject events with source=youtube_chart (unique), 0 with youtube_chart_pick. (2) BillboardClient.find() has no production caller (only tests/test_billboard.py). (3) RIAA: 2,931 rows (Diamond singles + albums + the 2026 feed), 731 cells own_credit names. One is filler: CAST OF HIGH SCHOOL MUSICAL, 1 Platinum single, which ranks on no board. A Haiku call per distinct cell (731 cold calls, cache in memory only) is not worth one cell. (4) _clean_news: the three news lanes produced 0 events; the cinema desk shipped 3 posts, all on 2026-09-19 and none from those lanes. (5) looks_like_sports: 1 discourse_icebreaker in 30 days, on a channel name that does not change. (6) frames_as_past: 0 retro_unframed drops on all four desks; the throwback takes that shipped state their frame (“35 years ago this week”, “in 2004”). Re-open a site only when its count above moves. Model error: today’s path (unchanged).  
C12 (SHIPPED 2026-10-16, #3450) utils/wikidata.py _MUSIC_OCCUPATIONS / is_music_act unlisted classes; utils/artist_watch.py first_credit_party art-miss path, _PLACEHOLDER_CREDITS Measured first on 707 real chart credits (8 weeks of Billboard, 14 days of Spotify): 79 Deezer portrait misses. is_music_act rejected 2 Wikidata hits among them, both albums (right); _PLACEHOLDER_CREDITS matched 3 credits, all real placeholders. Neither gets a rescue: nothing to rescue, and a rescued placeholder is the worse error. The real miss side: 77 of the 79 were co-billings joined by “&”, “,” or “With”, which first_credit_party never splits. After the whole credit and the “+” retry miss, first_party_candidate cuts ONE name from the credit, and one cached Haiku YES/NO (artist_watch.haiku_first_party through claude.classify, purpose=credit_first_party) decides whether it is the full name of a music act. Code refuses a cut before an article (“Tyler, The Creator”: the real model said YES to “Tyler” on both golden runs) and a placeholder. The rescue keeps only an EXACT Deezer row (the whole name and _EXACT_MIN_FANS), because a YES on a fragment of one act’s name (“Sam” of “Sam & Dave”) reached a namesake whose name IS the fragment (“SAM”, 48.7K fans). It is still reported NON-exact, so the media gate also judges it. Dry run 2026-10-16 (real Haiku, real Deezer, 2 runs): portraits on the 79 misses 0 -> 46 on both runs, wrong rescues 0 (each YES read by hand), 1 Haiku flip with no portrait change, 72 calls a run (about 18.6k input, 0.3k output tokens). 22 YES answers get no portrait under the exact rule, mostly real acts under 50K fans (Carly Pearce, Aesop Rock, HARDY): absent, as before. Without the exact rule the count was 68. A first prompt with “or you are not sure” gave 55 to 57 YES and 16 flips. Goldens on the real model, both runs NO: Earth, Wind & Fire, Simon & Garfunkel, Hall & Oates, Pasek & Paul, Crosby, Stills & Nash, Brooks & Dunn, Big & Rich, Sam & Dave, Mumford & Sons, Helluva Boss, Original Broadway Cast Recording. Model error: absent.  
K2 (SHIPPED 2026-10-16, #4170) utils/kworb.py _find_by_artist / find_chart_entry on an UNRANKED multi-act credit (the KworbClient.artist_standing, youtube_artist_standing, standings and resolve_chart_row callers) The strict matchers stay the hot path. A row that matched only a PART of an unranked multi-act credit (fragment_match) goes to one cached Haiku ONE / COLLAB label (claude.classify, purpose=kworb_act_check). COLLAB keeps the best-ranked part (the old rule); ONE gives no row. A first draft that asked Haiku to pick a candidate number answered NONE on real collabs and kept the namesake; the ONE / COLLAB question read 21 of 21 probe credits right. standings and resolve_chart_row now also pass the known ranked set (#2615). Dry run 2026-10-16 on live kworb (1,296 ranked acts, the 4 Spotify charts, the Apple albums chart; 577 requests): 31 multi-act credits, 12 Haiku calls, 1 pick changed (“Zezé Di Camargo & Luciano”: the rapper Luciano’s row to none), 0 wrong picks, about $0.002. Still on the old split rule: chart_history._credited, artist_priors, radio.standing (follow-up). Fast path added 2026-10-16 (#4277): a credit whose every part is a ranked act is COLLAB with no call. On 303 live credits that reach the check, it takes 168 (55%) off the model and fixes the one ONE flip (“George Michael & Wham!”, 1 of 2 runs); 0 verdicts get worse. No prompt edit: the other issue examples never reach the check. about 10 to 30 per day, cached 14 days per credit Model error: no row (absent).
C13 (SHIPPED 2026-10-15, #4174) utils/catalog_art.py screen_title_check (was screen_title_verified, #4118): the TMDB poster title check Folded-title equality stays the free accept. A bracketed request year (“Superman (2025)”) goes to TMDB’s year filter and must match TMDB’s year within one, so a same-title remake is refused with no call. On a title mismatch, a cached Haiku yes/no (claude.same_screen_work, purpose=screen_same_work) with both titles and both years. Dry run 2026-10-15: 45 real names, 37 accepted before and 41 after, 0 wrong accepts; 16 judge pairs, 13 right, the 3 misses all refusals. Model error: refuse (fail closed, as before).  
N1 (SHIPPED 2026-10-16, #4199) utils/x_artist_tags.py _SCAN_SKIP, _inside_longer_name, _is_title_position (the scan that picks the public X @tag); K6, the utils/x_mentions.py scan_watched hit (“Queen”, #3567) The deterministic scan stays. x_artist_tags.scan_names now also scans the _SCAN_SKIP names, and sends every one-word or skip-list hit to a cached Haiku YES/NO (claude.is_music_artist_mention, purpose=x_artist_mention, through classify). A multi-word name, or a name the lane passed as its own artist (the newsroom story subject, which classify_x_post also reads), needs no call. In the mentions lane, x_mentions.confirm_scanned asks the same check for a one-word hit the mention classifier did not name. Dry run 2026-10-16 on 800 real @tootsiesbar posts (6 days): 579 scanned names before, 594 after; 0 removed; 15 added, all right (Ashe, Future as the artist); 168 Haiku calls, about $0.004 in total. Goldens on the real model: “Queen of the night”, “Queen behavior from Doechii”, “Future plans”, “Zendaya … Spider-Man premiere” all NO; “Drake dropped a surprise EP” and “Future’s Mask Off” YES. Model error: no tag (absent). Mentions: the check runs only when the mention classifier itself answered, so a Haiku outage keeps the lexical rule.  
N5 + K3 (SHIPPED 2026-10-16, #4202) utils/music_news.py market_topic_overlap / pick_market_leg / restate_market_claim (N5); _fold / _artist_agrees / resolve_artist_count (K3, #3664) N5: code still scores the candidate events (market_leg_candidates); only a TIE (tied_market_legs) asks Haiku (pick_market_event, purpose market_event_pick, labels EVENT1..n + NONE over the tied events only). The restate keeps its own clause (the classifier fields carry no verb clause) but reads the odds or favourite phrase anywhere and drops the whole exchange tail: 8 of 59 real wire claims had read “according to Kalshi traders on Kalshi”. K3: the count still folds in code; artist_count_collisions finds same-title rows with different lead credits, and only those ask Haiku (same_record_listings, purpose count_same_record, YES/NO on the main artist). Dry run: 0 ties in 59 live market claims, 0 collisions over 321 acts on 5 live charts, so the steady-state volume is about 0 calls a day. Labelled sets: tie pick 0 wrong-accepts in 24, collision check 0 wrong in 28. No verdict: today’s rule (a tie drops the story; a collision folds to one record, an undercount).  
#4222 (SHIPPED 2026-10-16, no model) utils/catalog_art.py cover_rung: the gate-exempt catalog_cover rung on a row whose COLLECTION belongs to another act Deterministic, no Haiku. cover_rung now also requires _collection_verified(row) (whole billed acts in either direction, no spelling ratio and no piece of a name): iTunes fills collectionArtistName only when the collection carries a different credit, so a filled credit that does not name the act (“Various Artists”, “Tom Howe”) reports catalog_cover_fuzzy and the vision judge decides. Replay 2026-10-16: of the 20 overruled flags, the 6 GTA VI covers move to the judge and the rest keep the rung (they are the song’s own album, an empty collection credit); of 93 judge-passed exempt rows, 2 move (the GTA VI row again, and a TV-soundtrack cut). No ambiguous band remains, so a Haiku call would add cost and no decisions. Absent collection credit: exempt as before (iTunes leaves it empty on the act’s own release).  
#4263 (SHIPPED 2026-10-16, no model, same PR) utils/catalog_art.py cover_rung: a tagged REQUEST (Live, Acoustic, Remix, Taylor’s Version) verified the plain release Deterministic. The request’s recording tag (apple_music._edition_tagged) must be on the candidate too, else catalog_cover_fuzzy and the judge decides. Replay: 1 of 93 exempt requests carried a tag, and it resolves to its own tagged single, so it stays exempt. No tag on the request: unchanged.  
#4115 (2026-10-17, HYBRID) utils/catalog_art.py _pick_artist_row / _row_title_matches: a bracketed release NAME on the request (“WEEZER (GOLD ALBUM)”) was dropped by _normalize_title, so another colour album or a remaster matched release_name_check keeps the exact cases in code: no bracket name on the request, or the candidate carries it, is “match”; a candidate with a DIFFERENT bracket name is “mismatch”, no call. Only a candidate with no bracket name of its own (“unsure”) asks Haiku (catalog_release_name, classify(), SAME / DIFFERENT, cached 30 days), at most 3 names per search page. No word list was added. Replay on the 54 bracket-named requests of 30 days (100 art + debut verdicts, live iTunes rows, 2 runs): 9 change, 7 better, 2 neither, 0 worse, 0 flips; about 1.3 calls a day. Model error or no judge: the old rule, so the name matches. The model can only refuse a name the fuzzy match took. The no-artist “several acts” guard reads the fuzzy matches first, so a refusal cannot leave one act’s row as a guess.  
#2016 (2026-10-16, new check, not an audit site) utils/market_sibling_claims.py at market_alert._judge_and_ship and market_drop._compose: a market post against posts on OTHER Kalshi events about the same title Code picks the residue: same_subject_posts keeps the last 6h of market posts on another event (post_dedup_history.event_key) that share a quoted title or a “ by <Artist>" opening; no match costs no call. Haiku (`market_sibling_claim`, `classify()`, cached 12h) answers SAME / CONTRADICTS / DIFFERENT. Measured on 30 days: 12 pairs (about 0.4 calls a day), 0 wrong drops over 6 runs, goldens in `eval_classifiers.run_market_sibling_claim`. The model never drops without an earlier post on the same title.</Artist> Fail-OPEN (the claim gate’s direction): no event key, no sibling, a read error or no verdict ships the post.  
#3257 (2026-10-17, new check, not an audit site) cogs/music_desk.py:_cut_flourish (every desk lane, through _detag_credit): a trailing clause that only judges the figure HAIKU from the start, with no word list (owner steer 2026-10-17: a filler-or-fact call is about meaning). output_checks.trailing_clause is structural only: it finds the last clause and skips one with a digit, because a cut must never remove a figure (that also covers a comma inside a name, “Tyler, The Creator”). Every such clause goes to the cached Haiku FACT / FLOURISH (claude.is_flourish_clause, purpose=flourish_check). FLOURISH cuts the clause; the post still ships. Measured 2026-10-17 on 30 days of shipped desk lines: 499 of 5,939 reach Haiku. Wording 1 cut 20 with 1 flip over 2 runs, and 2 of the cuts were trend facts (“a strong launch despite streaming cooling”, “a big opening even with her dailies cooling”). The shipped wording names a trend as a FACT with that example: 18 cut, all judgments, 0 flips, both trend clauses kept. A first version with a fact-word list in front (25 reach Haiku) was replaced on the steer; with no list, the split misread “Tyler, The Creator” and “Earth, Wind & Fire” as clauses until the digit rule went in. Coordinator review: a comma inside a closed quote span is not a break, and the task says a name or title cut at a comma is a FACT; 8 name-ending posts (“for Tyler, The Creator”, “Crosby, Stills & Nash”), 2 runs, FACT 16/16; on the 499, 18 cut as before, 1 flip. Model error: keep the clause (the rule before the check).  
#4350 (2026-10-17, new check, not an audit site) cogs/music_releases.py _topic_verdict: the release board’s TEXT DEDUP drop (utils/dedup.duplicate_match, the shared spine gate) The text dedup stays the first pass and still decides every take it passes. A take it DROPS goes to the cached Haiku claude.release_take_adds_new (purpose=release_take_new, labels ADDS_NEW / SAME, through classify) with the recent posts, matched post first. ADDS_NEW ships it (the spine’s existing overturn path, dedup_overturned surface=music_releases); SAME keeps the drop. The board sets TOPIC_DEDUP = True and the new spine switch TOPIC_CATCH = False, so a take the text dedup passed is never asked about. Measured on every board and top-card drop 2026-10-02..17 (39 drops, real lines and real history): Haiku agreed with a hand label of “names an act no earlier post names” on 39 of 39, 0 run-to-run flips over 2 runs. Sequential replay of 2026-10-03..17: 4 dropped takes ship (DDG, Babyfxce E, Gucci Mane, Lucky Daye, each the first post to name that act), 0 real posts lost, 23 Haiku calls in 14 days (about $0.002). Model error or no verdict: the drop stands (today’s behavior).  
#3319 (2026-10-17, HYBRID) utils/wikidata.py artist_portrait: the Wikidata portrait rung took the FIRST same-name music act with a photo (“Steve Lacy” -> the jazz musician Q504641 for the R&B singer’s card) wikidata.artist_portraits returns every same-name music act with a P18 photo (after the first act it reads only music-looking descriptions, inside _ARTIST_CLAIM_READS). One act: no call. Two or more: music_markets._pick_namesake asks Haiku (classify(), purpose wiki_namesake, ONE / TWO / THREE / UNCLEAR, cached 30 days) with the card credit, title and each Wikidata description. No word list. Measured 2026-10-17 on the 114 artist names the portrait rung saw in 30 days (Wikidata SPARQL with en and mul labels, then the live search order): 97 have a music act with a photo, 3 reach the call (Steve Lacy, Michael Jackson, Chris Brown). Haiku, 6 runs per case, 0 flips: Steve Lacy 3 of 3 contexts better (the R&B singer); Michael Jackson 3 of 3 unchanged; Chris Brown with a title unchanged, Chris Brown with NO title (an artist-only card) WORSE, UNCLEAR, so no photo where the old rule was right. A “current chart card” year line was measured and not shipped: it also turned Michael Jackson with no title to UNCLEAR. 2026-10-18: entity_image._credit_portrait_bytes (the newsroom and market art resolver) now calls the same rung under its Deezer candidates, so both ladders share this check (rung wiki_portrait, gated). about 0 per day: the Wikidata rung answered 0 cards in the 14 days to 2026-10-17 (it runs only when Deezer misses or, since #4134, is blurred) Model error or no judge: the old first match. UNCLEAR: no photo (the card drops a rung).
#3259 (2026-10-18, new check, not an audit site) utils/x_crosspost.py _media_gate on a found photo: the identity judge passed a right subject on the WRONG occasion (the Odell Beckham Jr. post of 2026-09-14, note “… during an ‘F2 vs Odell’ soccer trick shot video”) HAIKU from the start: a fuzzy occasion call, no rule to keep. _OCCASION_CHECKED_ART (found only, the owner’s 2026-10-15 decision) is the exact part. A passing identity verdict with a note goes to the cached Haiku DIFFERENT / SAME (claude.photo_occasion_mismatch, purpose=photo_occasion), which reads the vision judge’s own note, so no second vision call. Step 1 of the task makes a release / chart / tour-announcement / milestone post SAME whatever the photo shows (lenient, owner: “don’t be too strict”). A drop needs two agreeing votes (separate cache rows). Measured 2026-10-18: wording 1 put a chart post over a Super Bowl photo at DIFFERENT 2/5, so the post-type step went first; then 10/10 SAME, the Odell golden and two other wrong-occasion controls 10/10 DIFFERENT. Real data, 2 runs each: 150 shipped (take, note) pairs from 30 days, 1 single DIFFERENT per run on different items (2 flips), 0 two-vote drops; 82 real notes that name an event, 0 DIFFERENT, 0 flips. Exposure: 12 source=found verdicts in 30 days. Model error or a split vote: ship the photo (the rule before the check).  
#4522 pilot (2026-10-18, HAIKU, converted) utils/qualifier_trim.py time_span, joins_as_only, states_most: the three shape gates that add the #4512 rules in claude.check_claim_qualifier Converted under the owner rule of 2026-10-18 (a regex keeps only an exact format). Code keeps the exact part: span_phrases lists the claim’s time phrases (the _SPAN_RE forms, titles blanked) and phrase_span computes their years. One cached Haiku read per claim (claude.qualifier_shape, purpose qualifier_shape, a JSON line on the classify cache and key) picks the ONE phrase that limits a qualifier and answers joins-as-only and maximum-“most”; the old patterns are in its task as examples. Two phrases give no span. Measured 2026-10-18 on 172 hand-labelled claims (the 33 harness claims, 63 detector probes, the 14 distinct qualifier_check matchups since 2026-10-17, 62 real newsroom claims of 30 days), 3 runs: all three gates right regex 138/172 (real claims 77/109), Jev 431/516 (300/327, 7 flips; it read every “biggest” as a “most”), Haiku 504/516 (324/327, 0 flips), p50 589 ms, p99 1174 ms, $0.15 per 1k. Haiku reads the 33 harness claims the same as the regex, so their judge prompts are unchanged (end to end on fresh research, real judge, n = 16 per claim: every one of the 528 judge prompts was byte-identical to main’s). About 30 calls a day (the judge’s own volume), cached a day per claim. Details: docs/PROMPT_OPTIMIZATION.md. Model error or an unreadable reply: the regex gates (the rule before).  
#4522 site 2 (2026-10-18, HAIKU, converted) utils/qualifier_trim.py has_qualifier (the _QUALIFIER_RE word list and the _RANK_RE rankings): whether a claim pays for the qualifier research and the judge (qualifier_check.check_source_qualifier, the newsroom) Converted under the owner rule of 2026-10-18. The word list stays the fast path: qualifier_trim.qualifier_candidates lists the candidate words outside quoted titles, and a claim with none is False with no call. A claim with candidates gets one cached Haiku read (claude.claim_has_qualifier, a classify YES / NO, purpose qualifier_present) with the candidates in its input and the word list in its task. Measured 2026-10-18 on 1,000 distinct newsroom claims of 30 days (Axiom detail.claim): the word list fired on 168. Hand labels: 13 of the 168 hold no qualifier (“58-63k first week”, “Best Rap Album”, “from lead songs only”, “our most recent chart”), 25 are left out as unclear, and 3 of the 832 misses hold one. Plus 41 goldens (#4512, #4502, #4468, #4485) and 12 test cases. Per arm (3 runs): word list 130/143 on labelled hits, 0 false hits dropped; Jev (TypeSafe.ask, one Noul) 412/429, 22 of 39 false-hit reads dropped, 0 true qualifiers lost, 1 flip, p50 793 ms; Haiku (shipped wording, 6 runs) 850/858, 70 of 78 false-hit reads dropped, 0 of 1,092 true-qualifier reads lost, goldens 246/246, 2 flips, p50 about 560 ms, p99 about 2 s. Cost: at most about $0.55 per 1k reads (no prompt cache); about 36 reads a day (the judge’s own volume), so under $0.02 a day. Rejected, measured: a model on EVERY claim. With “none listed” as the candidates it found the 3 misses but read 504 of 894 misses wrong (plain chart figures sent to the judge), 61 flips. Details: docs/PROMPT_OPTIMIZATION.md. No verdict (an error, an answer outside YES / NO): True, the word list’s answer, so the judge runs (the rule before).  
#4522 site 3 (2026-10-18, MEASURED, regex kept) utils/qualifier_trim.py _RANK_RE / _RANK_ORDINAL and the release noun / verb lists (#4471): _rank_matches, “does the claim rank its subject among other acts by the order they reached a feat?” It gates the ranking candidate, the #4485 ordinal rule in check_claim_qualifier, and the is_cut ranking spans Kept as code, measured. 6,534 tweets of 30 days from the 14 wire accounts #4471 measured; 1,262 hold an ordinal; the regex fires on 33. Set: the 293 tweets with an ordinal before “to” / “with” (hand labels: 35 yes, 258 no), plus 19 goldens (the #4485 ordinals and the _RANK_RE test shapes). Per arm: regex 289/293 (3 rankings missed: “the second Spanish-language song from the 1980s to reach”, “the second youngest player in WNBA history to win”, “the 4th ever among all artists”; 1 false fire: “the 16th Take Care track to pass”, an album’s own tracks), goldens 19/19. Haiku (classify-shaped YES / NO, the regex’s lead words, time units, release nouns and verbs in the task as examples) 814/879 over 3 runs, 61 counts read as rankings, 12 flips, p50 458 ms; a second wording that named the possessive count explicitly did not help (812/879 before the label fix). Jev (one Noul) 820/879, 56 counts read as rankings, 5 flips, p50 794 ms. Both models read a claim with a catalog count (“her 7th song to reach this mark”) or a tally standing (“the 3rd-highest tally among female rappers”) as a ranking. A false YES adds the #4485 rule to a claim that is not a ranking, which #4485 measured to change how the judge reads “first” claims. Re-measure when a better model ships. The list-position shape (“the 5th most-streamed album of 2016”, over 100 tweets) is not this rule’s shape and stays unmatched on purpose. Data: scripts/data/qualifier_rank_cases.json; harness: scripts/dryrun_qualifier_research.py --rank. Not converted, so no fallback.  
#4522 site 4 (2026-10-18, MEASURED, code kept) utils/qualifier_trim.py leaves_dangling (with _DETERMINERS, _COPULAS) and the _INTENSIFIERS rule in is_cut (#4484, #4461, #4477): does a judge cut leave a broken phrase (“is his song to hit No. 1”), and does it remove only “ever” / “in history” / “of all time” while keeping the superlative they intensify? Kept as code, measured. Real traffic first: in 30 days, 2 cuts reached is_cut across every lane (pop_desk “for the first time”, music_desk “highest ever”); neither was an intensifier-only cut, and leaves_dangling (the music drop lane only) got no cut at all (0 broken_cut drops). So the set is the 28 cut cases in tests/test_qualifier_trim.py, which are real judge cuts from the #4452 / #4465 / #4477 dry runs and review cases (20 dangle: 8 broken, 12 fine; 8 intensifier: 3 blocked, 5 fine). Per arm: code 28/28 (these are its own tests). Haiku (YES / NO, the shapes as examples) 75/84 over 3 runs, 0 flips; it passed the real broken judge cut “‘Housewife’ is the track by Tate McRae to reach No. 1” in all 3 runs. Jev 74/84, 4 flips; it passed “Tate Is The Artist To Top The Chart”. Both flagged “the song to the top” as broken. A model miss ships a broken or still-false cut with no compose after it, the costly direction. With no production input and both models worse on the cases there are, a call would add latency to the cut path and buy nothing. Re-measure when cuts show up on the qualifier_check panel. Data: scripts/data/qualifier_cut_cases.json; harness: scripts/dryrun_qualifier_research.py --cuts. Not converted, so no fallback.  
#4522 site 5 (2026-10-18, MEASURED, regex kept) utils/cinema_numbers.py tracking_items: the result-verb and projection lists (_RESULT_VERB, _RESULT_PAST_OPEN, _PROJECTION, the verb half of _TRK_PROSE) and the exact title match (_same_film) that decide whether a tracking pull is for an opened weekend (#4513) Kept as the rule: neither model beat it on real traffic. Traffic: about 22 tracking pulls a day since 2026-10-18 (pplx_cinema_desk 67 on 2026-10-18, 3 per sweep); every pull is a decision. Set: 36 real pulls of the production tracking_query made 2026-10-18 for 6 Fridays, 22 after dedup (tests/fixtures/cinema_tracking_pulls.json, hand labels per pull and per headline), run with the lane’s opened titles (production) and without them (a film open but not yet on the chart or TMDB); the 70 real SOURCES headlines as (film, headline) pairs; 54 probes (the 41 test_cinema_desk_tracking.py cases, the coordinator’s 10 #4513 cases among them, and 13 from real headline wording). Both model arms get the same films (_line_films, the pipe-form title) and answer the same evidence questions (a line, a headline or an opened title shows this film’s result). 3 runs (scripts/dryrun_cinema_desk_score.py --read 3): regex lane 22/22, bare 20/22, pairs 527/543, probes 46/54, 0 flips; Haiku (classify spine, one JSON call per pull, wording 4) lane 66/66 (130/132 over 6 runs), bare 62/66, pairs 1583/1629, probes 153/162, 1 flip, p50 607 ms, p99 2893 ms, $0.51 per 1k; Jev (TypeSafe.ask, one Noul per question) lane 63/66, bare 63/66, pairs 1614/1629, probes 157/162, 3 flips, 27 of 294 calls failed, p50 759 ms, p99 981 ms, $0.28 per 1k. The regex misses real result wording (“Rings Up No. 1 $51M+ WW Opening”, “Tanks With $5.5 Million Opening”, “Opens Small”, “Pulls Off a Box Office Shocker”), but in production the opened-title check catches every such pull (22/22). Each model adds false skips on real pulls, and a false skip loses a real post: Haiku read “‘Clayface’ Box Office Tracking Points to a Profitable Debut” and “Clayface Box Office Outlook Gets a Major Boost” as results (a Clayface pull skipped in 2 of 6 runs, another every bare run) and “Scream 7 Opens to $40M” as Scream’s result; Jev read “The Cat in the Hat | $40 million+ opening” (the only Nov 6 tracking film) as a result in every run. Re-measure with --read when the pulls change shape or a missed result ships (a cinema_desk_scored phase=tracking post for a film already open). Details: docs/PROMPT_OPTIMIZATION.md. Unchanged: the regex reading decides.  
#4522 close-out (2026-10-18, final verdicts) The last 22 OWNED-PENDING #4412 lines in tests/classifier_registry.txt: 20 in utils/output_checks.py (old C8), qualifier_trim._FILLER, cinema_numbers._NOT_A_TITLE Every line has a final verdict, and #4412 left _PENDING_OWNERS. CLOSED-SET (13): the number and ordinal parsers _NUMBER_RE, _WORD_RE, _WORD_VALUES, _MAGNITUDES, _ORDINAL_RE, _ORDINAL_WORDS, _FLAGGED_ORDINAL_RE, _DURATION_AFTER_RE, _TALLY_WORDS, _ROW_TALLY_RE, plus _STANDING_RANK_BEFORE_RE, _STANDING_SCOPE_RE (two exact phrases) and _RANKING_NOUN_SYNONYMS (five song nouns). MODEL-DONE purpose=claim_grounded (3): _RANKING_CLAIM_RE, _RANKING_STOP, _POSSESSIVE_CLAIM_RE; they are the triggers whose flagged captions Haiku reads since #4237 (merged 2026-10-16). Axiom, 30 days: 52 qualifier ungrounded_ranking drops and 3,422 career_claim drops, all before #4237; 0 drops and 0 claim_arbiter events since. MEASURED-KEEP (6): _ALLTIME_CLAIM_RE (#4574: has_alltime_claim 0 drops; recount: 0 of the 3,422 career_claim drop previews match it, 3,419 are possessive entry ordinals), _CAREER_ORDINAL_RE (#4574: 0 of 1,586), _AGE_CLAIM_RE / _AGE_GROUNDED_RE (measured here: ungrounded_age 0 drops of 21,257 music_desk_scored and 1,376 music_alert_scored rows; 0 of 12,435 shipped post_preview texts hold an age phrase), _FILLER (#4585: 2 cuts reached is_cut in 30 days; its model read did not cover this list), _NOT_A_TITLE (#4573: 0 of 22 real pulls are prose; 0 fires on their 55 answer lines). Unchanged.  

Tier D: deterministic fixes found on the way (not model work)

These are bugs the audit found in KEEP sites. Each is a small code fix.

site fix
utils/billboard.py _lead_credit + _history_slug “Earth, Wind & Fire” folds to “Earth”, so a solo act “Earth” can take EWF’s slug. Require a full party via artist_watch.credit_parties.
cogs/cinema_desk.py budget lookup + utils/tmdb.py search_movie The first TMDB hit is used unverified. Pass the year and check title_matches, the guard omdb.by_title already has (the Moana incident).
utils/api_sports.py _team_logo_from_rows “First row with a logo” ships a guessed crest. Return None on no exact match.
utils/radio.py standing A second substring matcher beside the house identity (song_key / fold_tokens). Wire the home in.
utils/riaa_boards.py _BRACKET_RE Drops “(Taylor’s Version)”. Allowlist identity-bearing parentheticals. DONE (#3455): _IDENTITY_BRACKET_RE + _bare_title keep “‘s Version”, remix/mix, live, acoustic, part, interlude, reprise, intro, outro; a feature, soundtrack or edition bracket is still dropped.
utils/kworb.py find_chart_entry loose mode ‘Future’ matches ‘Future Islands’. Callers stay on strict. Fixed (#3456): strict=True is now the default for find_chart_entry and standings(), so the four callers that took the loose default (radio-entry lane, /ask, the release lane, resolve_chart_row) run strict with no call-site edit. With a title, strict also accepts the request’s LEAD credit, because the radio chart lists more acts than Spotify (“KAROL G, Judeline & rusowsky” vs “KAROL G”).
utils/markets.py detect_league direct use in sgo_snapshots Confirm every direct call sits behind the Haiku rung; else a “cowboys” query defaults to NBA (FIXED #3457).
utils/kalshi_ladder.py companion pairing A Kalshi title-template change breaks the pairing silently. Add an event. DONE (#3458): companion_pair on every pairing (paired | tie | no_events | unparsed | no_partner, kalshi_ladder.companion_pair_reason); panel companion_pair (Markets board); ops-monitor finding kalshi_companion_template on unparsed only (60% over 5 or more pairings; no_events and no_partner are normal states). Live 30 days before the change: about 24 lone-leg attempts a day, 77 of 728 fell to the rank rung or dropped with no reason recorded.
utils/chart_ages.py resolve_week_ages on an album chart (#4235) The album row matched its own title-track SINGLE and took the single’s earlier date. DONE (#4235): _matching_pick(album=True) keeps only Album groups (compilation and live albums stay, remix / DJ-mix / demo drop); album rows cache under album:<song_key>. Live Billboard 200, 2026-10-17: 28 of 200 rows change date, 8 change year (“One Thing At A Time” 2022 -> 2023, “Diamonds” 1974 -> 2017), 0 lose a date. No model call added.
cogs/music_desk.py _plaque_facts on an ALBUM award (#4281) The RIAA plaques card read first_release_date with no album rule, so an album award took its title-track single’s date. DONE (#4281): first_release_date(album=True) reuses #4235’s _matching_pick(album=True); the in-process memo keys it album:. 189 real RIAA album awards, 2026-10-16, Haiku check on: 19 rows change, 7 change year (“Sweet Boy” 2023 -> 2024, “Thank U, Next” 2018 -> 2019), 3 undated rows gain the album date, 0 lose a date. No model call added.
utils/chart_records.py _lead_act (#4265) Its own credit split keyed “Earth, Wind & Fire” as “earth”. DONE (#4265): the key is artist_watch.first_party over the ledger’s solo credits (_credit_corroboration_set). Live 2026 ledgers (Hot 100, Billboard 200, Global 200): every row and every board signature is unchanged; the two real merges (“Dracula”, “I Can’t Love You Anymore”) stay. Residual: a solo act named exactly like a band’s first fragment, with the same title in the same year, still merges.
utils/music_markets.py resolve_art_url + cogs/music_desk.py _fetch_art (the Deezer portrait) DONE (#4134, 2026-10-17): both call catalog_art.portrait_blurred, the ONE sharpness floor entity_image already used (#2716). A blurred face falls to the next rung. Exact signal, no model. On the 23 ungated portrait picks of 14 days (19 distinct acts, live Deezer photos scored), 1 changes: DRAKE (0.0033) on 2026-10-12, now Drake’s Commons photo; 22 unchanged; 0 worse.
utils/music_markets.py pick_album_art_row (the market row title match) DONE (#4349, 2026-10-17): a row whose title names a DIFFERENT bracket release (catalog_art.release_name_check == “mismatch”) is refused, so “Weezer (Green Album)” no longer covers “Weezer (Gold Album)”. Exact, no call; an “unsure” row keeps the old rule here. Replay 2026-10-17 on live iTunes rows: of the 55 real queries of 30 days whose bracket names a release, 34 could be replayed (21 had no rows, or no act name to split off). 68 album and song picks, 0 changed, 0 worse. It closes the Weezer case and changes nothing today.

Deliberately not candidates

Checked and kept deterministic, with the reason:

Part 2: the inventory (2026-10-18)

Seven read-only passes, one per partition. Each pass read every file in its partition as it stands at origin/main 4e1b6c0 (the qualifier files at fba1952). Each report has its counts, its per-file inventory (the old verdict and the new verdict for every semantic site, with structural sites in one line per file), the Axiom measurements behind its verdicts, and notes. Each report’s own batch proposals are merged into the Part 0 batch plan, whose “from” column names them (P1-B3, P2-A, …). The stable reference is the symbol name, not a line number.

Inventory P1: music, newsroom, identity, feeds

Audit wave 19, 2026-10-18, against origin/main 4e1b6c0. Read-only. Old section: docs/CLASSIFIER_AUDIT.md lines 372-688 (audit date 2026-09-18).

Abbreviations in the tables: KC = KEEP-CLOSED, MH = MODEL-HAIKU, MJ = MODEL-JEV, KP = KEEP-PROTECTED. “fold” = add a field or a rule to a model call that already runs (zero added calls). “NEW” = the site is not named anywhere in the old audit file (added after 2026-09-18, or missed by the old pass).

Counts

Semantic sites only. A row in the inventory is one site (a symbol, or a group of symbols that make ONE decision together).

file semantic structural KEEP-CLOSED MODEL-HAIKU MODEL-JEV KEEP-PROTECTED DONE OWNED
utils/music_news.py 62 ~30 22 22 2 0 11 5
cogs/music_news.py 11 ~12 4 2 0 0 3 2
utils/artist_watch.py 9 ~6 5 0 0 0 2 2
utils/apple_music.py 13 ~5 6 6 0 0 1 0
utils/deezer.py 5 ~4 2 1 0 0 2 0
utils/chart_credits.py 2 0 2 0 0 0 0 0
utils/catalog_art.py 5 ~8 2 0 0 0 3 0
utils/names.py + utils/aliases.py 3 ~4 1 0 0 2 0 0
utils/song_pool.py 1 ~3 1 0 0 0 0 0
utils/hits.py 2 ~8 1 1 0 0 0 0
utils/music_markets.py 8 ~7 3 2 0 0 3 0
utils/release_type.py 4 ~4 2 1 0 0 1 0
utils/release_board.py 1 ~2 1 0 0 0 0 0
cogs/music_desk.py 5 ~25 4 1 0 0 0 0
cogs/music_alert.py 1 ~8 1 0 0 0 0 0
cogs/music.py, cogs/pop_desk.py 0 ~4 0 0 0 0 0 0
utils/genius.py 2 ~2 0 2 0 0 0 0
utils/musicbrainz.py 2 1 1 1 0 0 0 0
utils/banger_lane.py 2 1 0 1 0 0 1 0
utils/ktt2.py + utils/ktt2_chatter.py 2 ~4 1 1 0 0 0 0
utils/pop_stories.py 1 ~4 0 1 0 0 0 0
utils/versuz.py 4 ~10 3 1 0 0 0 0
utils/versuz_catalog.py, utils/versuz_card.py 1 2 1 0 0 0 0 0
cogs/versuz.py 2 ~5 2 0 0 0 0 0
utils/milestone_qualifier.py 5 1 2 3 0 0 0 0
utils/catalog_stats.py 2 ~3 1 1 0 0 0 0
utils/milestones.py, utils/stream_totals.py 1 ~4 1 0 0 0 0 0
utils/feeds.py 1 ~6 0 1 0 0 0 0
utils/artist_caption.py 1 ~4 1 0 0 0 0 0
section H structural files (see below) 0 ~25 0 0 0 0 0 0
total 158 ~225 70 48 2 2 27 9

Verdict changes against the old section: 48 MODEL-HAIKU and 2 MODEL-JEV. Of these 50, 16 were KEEP under the old rule (“hot path”, “behind a judge”, “identity”, “tiny list”), 16 are new or were missed by the old pass, and 18 were HYBRID / HAIKU items that a wave shipped only in part or measured and left as a regex. 27 sites are DONE: a shipped wave (A2-A17, B12, C2-C13, K3, N5, #4115, #3319) already puts a model on the decision.

Inventory

utils/music_news.py (7,752 lines; was 4,680)

Model nearby for every row: classify_music_news (Haiku, 361 calls/day), verify_music_claim (Haiku, 117/day), music_desk_score (Haiku, 381/day), rank_music_news_slate (Haiku, about 2-3/day). Volumes below are 7-day Axiom counts divided by 7.

symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_TRUSTED, may_relay_number, is_trusted handle registry wire handle may an unverified number ship KEEP KC our own registry 0 -  
_NUMBER_CUES, looks_like_numbers_story (+ authority_watch_candidates) 120 substrings every polled wire post admission to the paid classifier; a miss means no model ever reads the post KEEP (it is the prefilter) MJ (B1) Jev Noul “this post reports a music number about an act” on each post the cues DROP; cue list + 4 real non-stories (Gaza funeral, Gen Z stat, “A$AP” $, film gross) in the state. Cue hit admits free, as today 400-1,500 (code: 46 handles x 20 posts x ~22 slots, cached per post id) Jev error: old rule (drop)  
_CHART_CUES, chart_cue_strength, strength_buckets 70 substrings, count culture-tier post ORDER is DONE (C10). A post with strength 0 is DROPPED before the slate rank sees it HYBRID (C10 shipped for order) MJ (B1) for the drop branch same Jev question; a zero-strength post with P>=0.5 joins the bottom bucket, so the Haiku slate rank decides its place same batch Jev error: drop (today)  
apply_slate_rank + cog _rank_culture index validation Haiku slate order which 12 culture posts classify HYBRID DONE C10 2-3 cue order  
KEPT_PATTERNS, CUT_PATTERNS, chart_family, chart_reportable 20 kept + 19 cut chart-name regexes, deny first wire text + claim may the newsroom report this chart story (fail closed, unnamed cut) HYBRID (C10 rescued Spotify only) MH (B3) one Haiku JSON read per chart story: family from the closed label set KEPT + CUT family names + OTHER + NONE (the two pattern tables fed as the label list with the owner notes); regex stays the veto when it names a CUT family ~51 (music_news_chart_lookup 357/7d) model error: regex (fail closed)  
chart_key_family, _CHART_KEY_EVIDENCE key -> family classifier chart_key rescue of unnamed Spotify stories - DONE C10 0 - NEW
career facts: entry_ordinal_fact, peak_history_fact, chart_run_fact, chart_streak_fact, is_latest_entry, history_debut_confirmed, projected_entry_fact _fold exact Billboard page rows grounded career clause KEEP KC exact folded join 0 absent  
corrected_name (_SPELLING_MIN_RATIO) SequenceMatcher 0.85 wire vs model name accept a spelling fix KEEP OWNED:W19-C1        
wire_spelling, _POSSESSIVES, _wire_words n-gram + ratio artist vs wire re-spell the artist HYBRID OWNED:W19-C1        
named_by_mention, artist_grounded_in_wire, grounded_subject, corrected_subject_from_wire whole-word grounding artist vs wire drop artist to bare title HYBRID OWNED:W19-C1        
title_grounded_in_wire, _TITLE_FILLER, _TITLE_TAIL_RE, _CONTRACTION_SUBJECTS token subset title vs wire title_ungrounded drop (177/30d) A17 OWNED:W19-C1        
ungrounded_quoted_titles, _QUOTED_TITLE_RE quoted titles vs source composed take drop a take naming an unsourced title - OWNED:W19-C1 (same matcher)       NEW
_RUNG_CLAIM_RE, quotes_ladder_rung % + 20 threshold verbs + number classifier claim ladder re-read vs leg re-read HAIKU, then C10 measured 0/41 missed MH (B2 fold) add market_is_rung: bool to the market kind of classify_music_news; verb list goes in the prompt as examples 0 no field or error: regex  
novelty_is_distribution, _RANGE_FIGURE_RE range shape, % count >= 3 figure + claim drop a distribution novelty KEEP KC number shape 0 drop  
_RANK_WORDS, figure_is_rank_word 30 superlative words figure string the card hero is dropped KEEP MH (B2 fold) add figure_kind: count / rank / state to the classifier; word list in the prompt 0 error: list  
collapse_rank_run, _RANK_LIST_RE, _RANK_RANGE_RE, top_block_rung, position_grounded, lead_figure, split_figure_block, _DURATION_BLOCK_FIGURE_RE position / number formats figure hero formatting KEEP KC number format 0 as given  
split_figure_unit, _FIGURE_UNITS, _MAGNITUDE_UNIT_FIGURE_RE 16 unit nouns after a magnitude figure hero vs pill split - KC our card unit vocabulary, display only, number anchored 0 as given NEW
figure_unit_phrase, _UNIT_PHRASE_STOP stopword cut after a number claim pill words (“YouTube subscribers”) - MH (B2 fold) classifier returns figure_unit words, bounded to words in the claim 0 error: stopword cut NEW
figure_is_passed_threshold, _THRESHOLD_CUE_RE, _PLUS_FIGURE_RE, _DURATION_WORDS passing verbs before a number wire text + figure swap the wire rung for the research count HYBRID (A4 shipped field) MH (B2) code reads vthreshold OR regex (cog _verify), so the regex alone still swaps when the judge said false. Trust threshold_passed; regex only on a judge error 0 error: regex  
count_past_threshold, _FIGURE_SEGMENT_SEP_RE number > rung, unit words judge figure which researched count KEEP KC number check on model output 0 ””  
market_claim_parts, _MARKET_ODDS_LEAD_RE, _MARKET_VENUE_TAIL_RE, _PROBABILITY_FIGURE_RE, is_probability_figure, probability_pct, market_venue percent format, two venue names claim / figure odds + venue slots HYBRID (A5) KC fixed format: a percent and the closed venue set {Kalshi, Polymarket} 0 field fills a gap  
market_deadline_phrase (_MARKET_DEADLINE_RE), market_award (_MARKET_AWARD_RE, _MARKET_AWARD_PARTICLES) preposition / win-verb tails claim caption window, award headline HYBRID DONE A5 field first, bounded to the clause; regex only when the field is empty 0 blank caption  
market_caption (_DANGLING_TAIL_WORDS), title_case_headline, sentence_case, _TITLE_SMALL_WORDS display casing / trim caption text display only KEEP KC formatting 0 -  
dedup keys: _CERT_CONJ_RE, _release_identity, _ARTIST_FEATURE_RE, _lead_party, _TITLE_FEATURE_RE, _cert_release_identity, cert_story_keys, milestone_story_key, chart_debut_story_key, chart_position_key credit syntax fold subject + model ids permanent dedup keys KEEP KC exact folded keys 0 visible dup  
_LATIN_PROGRAM_RE latin / oro / platino / diamante claim suppress units clause KEEP KC RIAA Latin award names (fixed vocabulary) 0 -  
cert_count_unstated (_UNCOUNTED_MULTI_RE), cert_batch_count (_BATCH_FIGURE_RE) exact figure shapes figure RIAA lookup / batch board - KC fixed format 0 - NEW
cert_claim_format, _CERT_ALBUM_RE, _CERT_SINGLE_RE album/lp/ep/mixtape vs single/song/track claim RIAA format cell A13: NOT added MH (B2 fold) cert_format: ALBUM / SINGLE / NONE; Haiku can use world knowledge of the title, which the regex cannot 0 error: regex  
cert_reportable empty-body branch (_OUT_OF_SCOPE_CERT_PROSE_RE, _IN_SCOPE_CERT_PROSE_RE) 17 bodies + 33 countries claim + post + source drop a non-US/UK cert (fail closed) HYBRID; A13/C10: not changed MH (B2 fold), lowest priority widen cert_id body to any authority string + territory field; country list in the prompt. Measured 31 of 52 cuts in 30d took this branch, 31 of 31 right 0 error: regex (fail closed)  
cert_prose_names_other_body, cert_prose_is_riaa (_US_CERT_PROSE_RE, _UK_CERT_PROSE_RE, _FOREIGN_CERT_MODIFIER_RE) 27 demonyms + body names cert prose veto the RIAA level lookup - MH (B2 fold, same field) same territory field 0 veto NEW
claim_platform, _CLAIM_PLATFORM_RE, milestone_platform, milestone_metric 18 platform proper names; model id parse claim / model id block label A13 kept KC closed set of platform names; model-id parse 0 ””  
threshold_metric, _THRESHOLD_METRIC_RE verb + number + metric words claim Polymarket metric + deadline - MH (B4) returned by the B4 Polymarket pick call (metric, deadline), bounded to claim words in B4 error: regex NEW
poly_event_matches artist substring + head noun in title Polymarket titles which event re-reads a wire rung - MH (B4) Haiku pick EVENT1..n / NONE over the searched events (the existing pick_market_event shape) ~0.5 error: substring rule NEW
market_leg_candidates, _topic_tokens, _MARKET_TOPIC_STOP, _event_conflicts, _MARKET_ROLES, _NARROW_ROLES, _MARKET_ALIASES, _PROPER_WORD_RE, pick_market_leg token overlap >= 2, role stems, synonym table Kalshi events vs claim which live market answers a wire claim HYBRID; N5 shipped TIES only MH (B4) send every non-empty candidate set (not only a tie) to pick_market_event; overlap stays the shortlist order ~1 (ladder_rung 13 + live_leg 10 + market_unread 6 in 30d) no pick: drop (absent)  
restate_market_claim, _RESTATE_LEAD_RE, _RESTATE_NOT_ODDS_RE, _RESTATE_*_TAIL_RE, 18-verb lead list phrase surgery claim the restated market line N5 kept MH (B4) build the clause from the classifier’s existing market_outcome field (A5), bounded to claim words 0 error: regex  
claims_debut, claims_reentry, _DEBUT_RE, _REENTRY_RE, _DEBUT_BOUND_RE, _REENTRY_BOUND_RE, chart_event_of debut / re-entry verbs claim + wire debut key, repost gate SHIPPED A6 DONE classifier chart_event first, regex bounds it 0 regex  
names_edition, _EDITION_WORD_RE, chart_story_is_same_record 9 edition words claim + post keep the era guard on a chart card (#4131) - MH (B3) edition: bool in the B3 chart-story read; word list as examples in B3 error: regex NEW
named_chart_label, _APPLE_SONGS_RE, _APPLE_ALBUMS_RE songs vs albums nouns claim Apple chart label HYBRID DONE chart_key read first in unsettled_chart_label 0 regex  
names_projection, _PROJECTION_CUES, _forecast_near_chart, _FORECAST_WORDING_RE forecast phrases claim / wire projection, no chart label HYBRID DONE A2 is_projection first; cues only when the flag is absent 0 regex  
post_states_forecast: _STRICT_FORECAST_RE, _SETTLED_CHART_VERB_RE, _HEADLINE_INFINITIVE_RE, _FACT_INFINITIVE_RE, _INFINITIVE_LEAD_RE, _CLAUSE_SPLIT_RE ~60 verbs in 5 lists post text OVERRIDES a classifier is_projection=false to true (#3874) - (A14: week field measured worse) MH (B3) tense: settled / forecast / later_week in the B3 read, asked of the post scoped to the subject; the 5 verb lists as examples. Measured: the override fired 0 times in 30d (music_news_recover via=forecast_wording) in B3 error: regex NEW
states_chart_exit, _CHART_EXIT_RE, _EXIT_*_RE, _reads_exit exit verbs, blockers claim + post chart-exit lane SHIPPED A14 DONE classifier exit first 0 regex  
artist_ranking_label (_ARTIST_RANKING_RE, _RANKING_PLATFORMS, _TOP_ARTISTS_RE, _RANKING_POS_RE) “#N on top artists" parse claim Top Artists label A13 kept KC chart-name + position parse on a closed platform set 0 ””  
_FUTURE_RE (inside artist_ranking_label) will/would + 13 verbs sentence refuse a label for a future rank - MH (B3) the B3 tense field in B3 error: regex NEW
charts_named, sole_chart_text, chart_keys_for, names_unreadable_chart, _CHART_PATTERNS, _US_QUALIFIER_RE, _FLAGSHIP_PAIR_RE, _ON_BILLBOARD_RE, _AIRPLAY_RE chart-name table claim which live chart settles HYBRID (A2) DONE classifier chart_key narrows / fills; regex vetoes 0 regex  
deep_platform_cut chart keys + claims_debut position depth floor KEEP DONE reads chart_event_of 0 -  
wire_is_next_week, wire_fall_position, wire_last_week, _WIRE_MOVE_RE, _BILLBOARD_MOVE_RE, _WIRE_STATES_NO1_RE, refute_is_last_week “#N(+3)”, “climbs 12-5” wire text + live position the wire is about last week - KC position-number formats; the verb only anchors Billboard’s fixed headline form 0 False NEW
post_states_later_week, _LATER_WEEK_RE, later_week_first_week_claim re-peak / second week phrases post drop a “first-week” take on a later week A14: not added MH (B3) B3 tense = later_week; measured 1 drop in 30d in B3 error: regex NEW
FIRST_WEEK_PHRASE_RE, first_week_figures, release_windows, _RELEASE_WINDOW_RE “first week”, “day one” take / claim number anchor; block window word - KC; the block window is MODEL since #4538 closed phrase set anchoring a number check; the first-week block window is read by Haiku release_window, and these phrases are its examples and the error fallback 0 no window NEW
wire_move_sizes, wrong_move_sizes, strip_wrong_moves, _MOVE_SIZE_RE, _FROM_TO_MOVE_RE, _WORD_NUMBER move-size numbers take vs wire drop / strip a wrong move size - KC number check 0 strip NEW
scope_daily_streams, _DAILY_SENTENCE_RE, _BIGGEST_RISE_RE, is_daily_stream_count, _DAILY_CLAIM_RE one account’s fixed template wire text rewrite the date, daily-count shape B17 removed the X use KC fixed bot template 0 as given NEW
misstated_chart_frame period half (_CHART_PERIOD_RE, _CHART_POSSESSIVE_RE) daily/weekly/… + chart noun take vs ground drop a wrong chart period - KC closed period vocabulary compared to the ground 0 drop NEW
misstated_chart_frame hold half (_TRANSIENT_RE) briefly / momentarily / for a day take vs ground drop a take that adds “briefly” - (B4 note: rubric covers it) MH (B3) retire to music_desk_score, whose numbers rubric already fails “a hold it does not give (‘briefly’)”; zero calls. Measured: chart_frame drops 0 in 30d 0 judge NEW
stream_claim, _STREAM_*_RE, _names_spotify_streams, _OTHER_STREAM_PLATFORM_RE, stream_shape_allowed stream shape claim settle off kworb SHIPPED A3 / A13 bound DONE classifier stream_shape/rung/count first 0 regex  
_STREAM_SCOPED_RE solo/different/separate/distinct/lead/credited + songs claim + wire refuse a subset count (veto before the fields) - MH (B2 fold) add subset to stream_shape 0 error: regex  
album_stream_story, _calls_title_a_song 4 phrase patterns around the title claim + wire OVERRIDES the classifier album shape to a song - MH (B3) Haiku ALBUM / SONG with title + artist + post (world knowledge decides a title track); only when the shape says album ~0.2 error: regex NEW
mark_album, _album_next_to_title, _ALBUM_TOTAL_RE, _SELF_TITLED_RE does the take already call it an album composed take insert “album” or drop (album_unmarked 5 in 30d) - MH (B3) Haiku MARKED / UNMARKED on the take; the insertion stays code ~0.2 error: regex NEW
catalog_count_tag stream_claim catalog shape + figure equality claim + figure catalog block KEEP KC number match 0 ””  
rank_list_tag + 14 _RANKED_LIST_*_RE, _SPELLED_ORDINAL_RE, _RANK_HERO_RE list-name extraction + allow/deny lists claim + figure the block over a ranked-list hero A13: field only vouches MH (B2 fold) let the existing rank_list field NAME the list, bounded to claim words; the authority / scope / comparison rules become prompt text; regex is the fallback. Five Codex rounds on #3778/#4212 0 (~13 stories/30d) error: regex  
claim_count_label, _COUNT_LABEL_RE, _COUNT_COORDINATED_RE, count_label_on_platform words before a unit noun after the hero number claim block label A13 kept KC anchored to the figure’s exact value; unit nouns are the card unit set 0 ”” NEW
claim_top_n (_TOP_N_RE), entry_count_claim (_ENTRY_COUNT_RE) “top N”, “N … entries” claim count board HYBRID KC number formats 0 refusal  
claim_chart_kind songs vs albums nouns claim count chart disambiguation HYBRID MH (B2 fold) derive from the classifier chart_key through _CHART_KIND; nouns only when no key 0 ambiguous  
exact chart joins: resolve_chart_position, resolve_entry_history, recover_chart_artist, strip_artist_prefix, lead_act, _credit_acts _fold equality chart rows live settle KEEP KC exact folded join 0 absent  
_artist_agrees, artist_count_collisions, resolve_artist_count subset rule rows vs subject count KEEP DONE K3 (count_same_record on collisions) ~0 fold to one  

Structural: _PULL_DATE_RE, _ISO_DATE_RE / humanize_iso_dates, _CLAIM_POS_RE, _NOTE_*_RE, _valid_cert_id, _valid_milestone_id, _CERT_LADDERS, CHART_SOURCES, _CHART_KIND, _CHART_PLATFORM, parse_chart_mark, format_threshold, leader_threshold, is_longshot_leg, _TIER_WEIGHTS, _NBSP, chart_text, market_forecast_context, poly_leader_context.

cogs/music_news.py (8,272 lines; was 6,333)

The A1 cue lists (_TOUR_CLAIM_CUES, _LIVE_CLAIM_CUES, _NONAUDIO_RELEASE_CUES) are gone (0 hits). The cog calls ~150 mn.* readers; they are counted under utils/music_news.py.

symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_is_tour_subject the word “tour” in the title classifier subject refuse a loose Deezer cover match HAIKU (A1 family) MH (B3) nominate-only: the word sends the title to a cached Haiku TOUR / RECORD; the classifier’s event_shape stays primary ~0.1 error: refuse (today)  
_first_week_kicker (\b(sold\|copies)\b) 2 words claim block metric “sales” - MH (B2 fold) classifier first-week metric field 0 error: regex NEW
_songstats_verify (“stream” in claim) substring claim spend a Songstats slot SHIPPED A11 DONE milestone_metric first 0 -  
pull answer NONE + is_hedged sentinel Perplexity answer drop the pull KEEP KC model-output protocol 0 drop  
_artistpull_desk_owns kind + metric id classifier fields drop desk-owned story KEEP KC fixed keys 0 -  
_names_the_collection, _listen_link normalize + equality catalog row song vs album link KEEP KC exact 0 -  
_row_is_a_different_record -> retrospective.states_past_interval throwback frame claim refuse old cover KEEP OWNED:W19-B3        
_rank_culture slate rank cue order culture places HYBRID DONE C10 2-3 cue order  
_artist_in_tier -> aw.lenient_tier, _recover_bare_artist tier rescue credit unknown_artist cut (315/30d) HYBRID OWNED:W19-C1        
_house_gate / _artist_new_release -> music_policy.judged_in_house_genre genre tag Apple tag in-house genre HYBRID DONE C5 ~1 refuse  
_recent_release, copyright_year lag dates Apple rows fresh drop KEEP KC numeric 0 -  

Structural: _split_subject, _COLLECTION_TYPE_SUFFIX_RE, _MONEY_MARK_RE, _album_link URL strip, _KIND_ACCENT_KEYS, _KIND_NOUNS, _FIRST_WEEK_METRICS, _FEED_PLATFORMS, _RECORD_LANES, _EXISTING_RECORD_KINDS, _first_party_rank, _rotate_window, alnum edition keys (lines ~2053, ~2128), src != "riaa".

utils/artist_watch.py (2,205 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
fold_tokens, _CONNECTORS, _POSSESSIVE_RE identity fold credit key KEEP KC exact fold 0 -  
_names_a_credit, Watchlist.contains, recognition_tier, recognized_for_store_chart whole-segment match credit WATCHED / KNOWN / UNKNOWN KEEP KC exact segments 0 fail open on empty list  
s_variants, lenient_tier trailing-s, raw vs corrected credit spellings rescue HYBRID OWNED:W19-C1        
_ALIASES, _CREDIT_ONLY, canonical_credit, credit_names, performing_names alias table credit alias -> canonical HYBRID OWNED:W19-C1        
rescue_candidates (ratio 0.75), rescue_unknown difflib nominate UNKNOWN cut closed-set rescue DONE #3439 DONE Haiku watched_act_match (7/7d) ~1 cut  
credit_parties, _PARTY_SPLIT_RE, _PARTY_SPLIT_AND_RE, _LIST_SHAPE_RE, is_solo_credit, lead_party, first_party, listed_parties, lead_run, _LIST_CUT_RE, _PARTY_JOIN_RE credit syntax + corroboration credit split joint credits KEEP KC credit syntax, corroborated against the known list 0 whole credit  
first_credit_party, _JOINT_CREDIT_RE, _ARTICLE_TAIL_RE, first_party_candidate, judged_first_party one cut name credit art-miss portrait party DONE C12 DONE Haiku credit_first_party ~2 absent  
_FEATURE_CLAUSE_RE, featured_credit, _FEATURE_MARKER_RE, _TITLE_WITH_RE, _TRAILING_GROUP_RE, title_feature, row_credit, row_is_theirs, credit_matches feat syntax credit / title feature parse KEEP KC credit syntax 0 -  
_PLACEHOLDER_CREDITS, is_placeholder_credit 15 exact folded stand-ins credit skip portrait lookup KEEP (C12 measured 3/3 right) KC exact folded equality on catalog stand-in strings 0 absent  

Structural: daily_share_ok, daily_is_plausible, check_daily_drop(s), _TOP_RUNG_RE, TOP_MOVE_KINDS, _STORY_WHAT, _STORY_EYEBROW, _POSITION_ONLY_EYEBROW, rotate.

utils/apple_music.py (2,846 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_EDITION_NOISE edition parentheticals search term strip noise from the query KEEP KC query normalization only 0 -  
sequence_mismatch, _sequence_marker, _TITLE_TAIL_NOISE, _NUMBER_WORDS, _roman_installment installment number title pair refuse a different installment KEEP KC number parse 0 refuse  
_title_matches (substring >= 3 or ratio >= 0.6) difflib title vs trackName which link / cover HYBRID; C2 band (< 0.85) shipped MH (B5) a NON-EQUAL pass at ratio >= 0.85 (or a substring pass) still accepts alone. Send every non-equal pass to match_catalog_title (C2, 12/7d); equality stays the free accept +5-20 (code estimate, cached 30d) no pick: refuse  
_credit_similar (ratio >= 0.8, word-sorted), _artist_matches, _primary_artist_matches, _partial_name difflib on credits requested vs artistName homonym guard for links and art HYBRID MH (B5) _credit_exact stays the free accept; a ratio-only pass goes to a cached Haiku SAME / DIFFERENT (both credits + track) +5-20 (cached) error / DIFFERENT: refuse  
credit_verified, _credit_exact, _acts_within, _same_act_words, title_verified, core_title whole-word, Counter equality credit / title exempt a cover from the vision judge - KC exact; a False sends the cover to the vision judge 0 judge NEW
release_tag_mismatch, _release_name_tag, _NOT_A_NAME_ZONE_RE (55 words), _ZONE_STOPWORDS which bracket zone NAMES the release raw titles refuse a row (Weezer colours) - MH (B5) the zone word list decides “is this bracket a name”; reuse catalog_release_name (#4115) for a zone the list does not classify as packaging ~1 error: list NEW
_edition_tagged, _EDITION_TAG_RE, _PACKAGING_ZONE_RE recording tags title a tagged request needs a tagged row (#4263) - DONE a mismatch only drops the gate exemption; the vision judge decides 0 judge NEW
pick_apple_music_url fallback order logic results link choice KEEP KC logic over the matchers 0 -  
_is_single_or_short ”- single” suffix, <= 2 tracks collection album vs single KEEP KC upstream suffix + count 0 -  
_exact_artist_row, _pick_album exact name, fold containment rows discography row KEEP KC exact 0 no row  
_BAD_VERSION_RE, _is_guessable_title, _version_tag_text 27 version words in tag zones track title drop from the /guess and X-game pools, Deezer pick KEEP (bulk) MH (B6) nominate: a flagged title goes to the cached release_version_kind (exists, 28/day); RECUT keeps the drop, NEW clears it +10-40 (cached 30d per title) error: drop (today)  
_ALT_VERSION_RE, _is_clean_original, targets_alt_version 11 alt-version words title prefer the clean original row (clip, art rank) KEEP MH (B6) same label (recut / edition vs new) in B6 error: regex order  
_COMPILATION_RE, _is_compilation 30 compilation phrases + “various” collectionName prefer the act’s own album cover KEEP MH (B5) cached Haiku COMPILATION / OWN_RELEASE per collectionName (90 days); only when two collections compete ~2-10 (cached) error: regex  

Structural: copyright_year (_PHONOGRAM_MARK_RE, _COPYRIGHT_YEAR_RE), apple_music_id, _FORMAT_SUFFIX_RE, _CREDIT_TAG_RE, _FEAT_CREDIT_RE, _PAREN_ZONE_RE, _BILLING_SEPARATOR_RE, breaker / retry constants.

utils/deezer.py (1,986 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
album_title_matches, album_match_band, _TITLE_STOPWORDS, _core_title, _fold_stylized 60% token overlap album row vs request release date / genres HYBRID DONE C2: every non-equal core goes to the Haiku pick ~1 refuse  
_credit_matches_contributors, _initialism, _split_billing, _names_whole_acts subset / initialism contributors FOUND cover, gate exemption only for a one-act exact credit (#4203) KEEP KC exact; non-exempt covers go to the vision judge 0 judge  
album_cover, _cover_from_rows, _cover_from_artist_albums, _one_act core-title equality rows which cover KEEP KC exact 0 -  
artist_picture_match (any token overlap, top fans) token overlap artist rows portrait + exact flag KEEP DONE a non-exact pick is flagged and the media gate (vision judge) decides it; verify each consumer reads exact 0 judge  
_artist_overlaps, _artist_is_billed (60% token overlap) token overlap credit vs Deezer artist which track row gives date / genres / top track KEEP MH (B5) exact fold free accept; overlap-only pass to the B5 SAME / DIFFERENT credit check ~2 refuse  

Structural: _is_real_deezer_image (md5 sentinel), _SEARCH_CONNECTOR, _GLUED_CONNECTOR_RE, connector_glued, credit_key, _is_varied.

Identity helpers and small files
file: symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
chart_credits: _artist_matches _names_a_credit both ways chart credit vs Genius attach Genius credits KEEP KC exact whole segments (Miguel Newberry fix) 0 absent  
chart_credits: credit_matches filter segments credit drop self-credit rows KEEP KC exact 0 -  
catalog_art: screen_title_check equality + Haiku TMDB title poster DONE C13 DONE screen_same_work ~0.5 refuse  
catalog_art: release_name_check, _judge_release_names bracket name rows match / mismatch / unsure DONE #4115 DONE catalog_release_name on unsure ~1 refuse  
catalog_art: cover_rung, _collection_verified, _edition_tags exact credit + tags row gate-exempt rung #4222 / #4263 KC exact; any miss goes to the vision judge 0 judge  
catalog_art: _SELF_TITLE_TAG_RE, _only_self_title_tags, _names_the_act packaging-tag allow-list title zones self-titled ranking boost - KC allow-list on catalog packaging tags; a miss only removes a ranking boost 0 no boost NEW
catalog_art: _pick_artist_row, _row_title_matches, _rank_rows apple matchers rows cover row KEEP DONE C2 via apple_music (and B5 widens it) - -  
names: name_pattern, name_in_text, match_forms whole-word name match Discord prose which member is named (/forget, attribution) KEEP KP privacy matching 0 -  
aliases: AliasIndex.users_in, _text_fold_keys alias fold Discord prose member resolution KEEP KP privacy matching 0 -  
names: strip_ep_marker (_EP_MARKER_RE) “(EP)” title identity join KEEP KC catalog suffix 0 -  
song_pool: song_key, recognizable apple normalizers, fans rows song identity, pool floor KEEP KC exact / numeric 0 -  
hits: projection_row (_norm_name substring both fields) substring HITS rows vs title + artist which projection row a debut card / market answer reads KEEP MH (B4) exact fold free accept; a substring-only row goes to a Haiku pick ROW1..n / NONE ~0.5 NONE: no projection  
hits: find_upcoming exact rows upcoming release KEEP KC exact 0 -  

Structural (song_pool, hits): song_pool genre dicts (DEEZER_GENRE_CHART, RSS_GENRE_IDS, GENRE_BUNDLES); _to_int, _COLUMN_FIELDS, _spins_agree, _slash_date, TSV parsers, decap_title, benchmark_key, _COUNT_CELL.

utils/music_markets.py (3,783 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
pick_album_art_row, _credits_artist, _rank_art_rows apple fuzzy matchers + release_name_check mismatch (#4349) iTunes rows market cover KEEP DONE delegates; fixed at the source in B5; media gate downstream - -  
_is_full_album ”- single” / “- ep” + track count row album vs single KEEP KC upstream suffix 0 -  
_is_soundtrack, _SOUNDTRACK_NAME genre field + 3 words row art preference - KC upstream genre field; release-name words are catalog naming 0 - NEW
_subject_tier_hits token-set equal / superset / subset tiers Kalshi subjects vs text which ladder answers a question KEEP MH (B4) equal tier is the free accept; superset / subset tiers go to a Haiku pick over the tier’s readings ~0.5 NONE: no ladder  
_FIELD_LABELS 7 title regexes Kalshi market title label in the related-markets line KEEP KC Kalshi’s fixed title templates; the fallback prints the title itself 0 title  
related_market_context (_artist_key substring >= 5) substring subject vs outcome names which act’s odds join the compose context KEEP MH (B4) exact fold free accept; substring-only hit to a cached Haiku SAME / DIFFERENT act ~0.5 DIFFERENT: no line  
title_names_act, title_act_hits, judged_title_act token run event title artist market DONE C9 DONE market_act_check (67/day) 67 skip  
_pick_namesake, _wiki_portrait Wikidata namesakes descriptions portrait DONE #3319 DONE wiki_namesake ~0 UNCLEAR: none  

Structural: _TICKER_DATE_RE, _DEBUT_TICKER_DATE_RE, _RELIST_SUFFIX_RE, "::" subtitle split, "PUREALBUM" in ticker, "stream" in metric, ARTIST_MARKET_SKIP_SERIES, _SALES_PAIR_METRICS.

utils/release_type.py (467 lines), utils/release_board.py (750 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
is_side_version, side_version_marker, is_side_version_beyond_radio_edit on BULK paths: music_alert debut lanes (2259, 2279, 2674, 2986, 3095, 3121), music_desk presence / sort / new_entry rows (1834, 3940, 6568, 6575, 10070, 10079), music_news row filter (6172) _SIDE_MARKERS, _BARE_TRAIL_MARKERS, _FEAT_RE, _TRAILING_DASH_RE, _FORMAT_TAG_RE, _RADIO_EDIT_RE track title suppress a debut, hold out of presence memory, down-rank HYBRID; C3 shipped on 2 announce paths only MH (B6) nominate: a regex-FLAGGED title goes to the cached release_version_kind; rendition_marker stays a hard RECUT. C3 measured the music_alert debut lanes: 0 better, 2 wrong (instrumental editions) before #4268 fixed that shape; re-measure first +20-60 (cached 30d) error: regex  
judged_side_version (release board, new_entry) label map title + verdict announce paths DONE C3 DONE release_version 28/day 28 regex  
rendition_marker, _RENDITION_RE instrumental / a cappella / karaoke / sped up / slowed / reverb version suffix RECUT with no call - KC closed catalog rendition-tag set; the model measurably lost on it (#4268: EDITION 2 of 2 runs) 0 side version NEW
_EDITION_MARKERS, edition_marker, without_edition, _REMASTER_RE, version_base edition words title persisted song keys KEEP (C3) KC builds persisted keys 0 -  
release_board: dedup (artist.lower(), title.lower()) exact rows dedup KEEP KC exact 0 -  
cogs/music_desk.py (14,017 lines), cogs/music_alert.py (4,763 lines), cogs/music.py, cogs/pop_desk.py
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
music_desk: _RESERVED_OTHER_CHART “US”/”U.S.” vs “global” our own recent posts exclude a line from the Spotify board’s dedup comparison (#3894) - MH (B7 fold) pass the board’s chart identity into the existing topic_dedup judge (Haiku, 247/day); the regex only pre-sorts 0 error: regex NEW
music_desk: wl.contains / recognition_tier / _is_recognized / aw.lead_party / credited_names delegations exact segments credits floors, caps, grouping KEEP KC exact 0 fail open  
music_desk: "streams a day" in qual_source substring of our own template card_qualifier text drop the live line - KC our own template string 0 - NEW
music_desk: _kworb_career_row (kworb_fold) exact board rows career rung KEEP KC exact 0 -  
music_desk: _genre_debut_groups Apple genre + Deezer genres upstream tags genre board KEEP KC upstream fixed vocabulary, two-source confirm 0 -  
music_alert: _ARTIST_MARKET_NOT_ACTS 3 exact folded names watchlist name exclude from the act candidates KEEP KC exact folded keys; market_act_check decides every other title 0 -  

Delegations counted elsewhere: is_side_version* (B6), output_checks / ship_guard / qualifier_* (other partitions, #4422 / #4412), mq.on_chart (milestone_qualifier), match_album_tracks (Haiku). Structural: music_desk _ORDINAL_RE, _CHART_ENTRY_KINDS, lane sets, slug keys, " -- " splits; music_alert _RANK_FIGURE_RE, _CROWN_LANES, lane-name startswith, "stream" in metric, "album" in lane; music _MUSIC_LINK_HOSTS, drop_song_key; pop_desk _VISION_HOSTS.

Search, intent, side surfaces
file: symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
genius: _DETAIL_INTENT, _SAMPLE_INTENT, _MEANING_INTENT, _LYRIC_INTENT, matches, lookup render branch 4 intent regexes query + aspect (the /ask model’s own words) which source, which render HYBRID; B7 not built (5 lookups/30d) MH (B8 fold) make the song_credits tool’s aspect an enum (credits / samples / meaning / identify) in the /ask tool schema, so the /ask model picks it; regex only for a free-text aspect 0 (5 reference_lookup in 30d) regex  
genius: _best_annotation word overlap question vs annotations which ONE annotation reaches the model KEEP MH (B8 fold) hand the top 3 annotations to the /ask model, which already answers 0 top 1  
musicbrainz: _MUSIC_INTENT, matches release / drop / label words /ask intent route to MusicBrainz HYBRID MH (B8 fold) same tool-argument enum 0 regex  
musicbrainz: "feat" in joinphrase upstream field artist-credit main vs featured KEEP KC upstream fixed field 0 -  
banger_lane: search_terms, judged_terms stopwords / caps runs headline X search names DONE B12 DONE headline_search_names ~2 regex  
banger_lane: _SPAM_RE 15 spam phrases candidate tweet drop a reply target HYBRID MH (B8) cached Haiku SPAM / REAL per tweet id, phrase list as examples (Jev not used: low volume, owner direction 2026-10-17) ~2 (banger_lane 14 events/7d) error: regex  
ktt2: match_score, _STOPWORDS, match_threads title-word overlap > 0 forum threads vs topic which threads reach /ask context (a 0 score is never seen) KEEP MH (B8) Haiku pick of the relevant threads from the newest N (indexes); overlap stays the order ~1 (ktt2_fetch 20/day) error: overlap  
ktt2 _is_watched, ktt2_chatter is_watched watchlist fold thread watched first KEEP KC exact 0 -  
pop_stories: story_signature, dedup_key, _STOPWORDS first 5 content tokens wire text durable moment dedup; a key collision drops alone KEEP MH (B8) on a key collision whose texts differ, ask the existing topic_duplicate (Haiku); same text stays a free drop ~1 error: drop (today)  
versuz: is_play_command, PLAY_COMMAND_RE, STRICT_PLAY_COMMAND_RE command syntax chat message play attribution KEEP KC bot command syntax (closed) 0 -  
versuz: parse_shorthand, SHORTHAND_RE, _LETTER_DASH_RE, slot_for_name “name: song” host text slot KEEP KC our input syntax 0 -  
versuz: _title_key, base_title (_EDITION_WORDS, _FEAT_RE) normalize Jockie title queue identity, display KEEP KC exact after fold; display 0 -  
versuz: answer_order name / handle / title substring hand-made poll answers which slot each answer scores for KEEP MH (B8) Haiku A / B / UNSURE per answer with both names, handles and titles; UNSURE keeps today’s fallback (play order, said aloud) <1 UNSURE: fallback  
versuz_catalog: _YT_NOISE_RE official / lyric / audio YouTube title cleanup KEEP KC upload-title tags, display 0 -  
cogs/versuz: _handles, _member_id_named, _pop_pre_queued lowercase equality “Requested by” attribution KEEP KC exact 0 unattributed  
cogs/versuz: by_name and open_rnd and not (url or artist) shape message chat vs correction KEEP KC input shape 0 -  
Milestones, streams, feeds, captions
file: symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
milestone_qualifier: is_no_qualifier NONE lead sentinel Perplexity read skip the judge KEEP KC protocol sentinel 0 absent  
milestone_qualifier: _NO_QUALIFIER_MARKERS tail (12 phrases) “no notable”, “not a first” read lead skip the judge (decides alone) KEEP MH (B7) send a phrase hit to qualifier_verify (Haiku, 3/day) instead of skipping +~1 absent  
milestone_qualifier: is_unofficial_rank, _AGGREGATOR_RE, _OFFICIAL_SOURCE_RE, rank_from_score_ranker, _SCORE_RANKER_RE source-name lists qualifier + reads drop an aggregator rank (TV Girl / Chartmetric to X) - MH (B7 fold) qualifier_verify returns source_class: official / aggregator / none; the lists go in its prompt. Coordinate with #4412 (it owns qualifier_check) 0 error: list (drop) NEW
milestone_qualifier: _RELATIVE_SCOPE_MARKERS this year / ytd / today qualifier cache TTL KEEP MH (B7 fold) same call returns scope: relative / standing 0 short TTL  
milestone_qualifier: on_chart, nth_release_qualifier label containment, fold qualifier scope gate, nth release KEEP KC exact chart label / fold 0 drop  
catalog_stats: rank_claim_mismatch names_act phrase half (_NAMED_COMPARISON_RE) + _PLACE_ON_OTHER_METRIC_RE / _OTHER_METRIC “trailing only”, “second only to”; streamed/played/… composed catalog caption drop the caption - MH (B7) cached Haiku OK / NAMES_RIVAL / OTHER_METRIC with the proved rank fact; the exact-name and place-number checks stay code ~1 error / no verdict: drop NEW
catalog_stats: stated_places, _COUNT_PLACE_RE, exact rank.ahead names ordinal numbers caption wrong place - KC number check + exact names 0 drop NEW
milestones: _REMASTER_TAIL_RE; stream_totals: _CREDIT_SPLIT_RE key fold, credit split title / credit keys, act list KEEP KC persisted key / credit syntax 0 -  
feeds: room_is_quiet >= 4 words or media room buffer skip the forced web search in recap (#880) KEEP MH (B8) Haiku SUBSTANTIVE / CHATTER on the human lines (Jev not used: about 1 recap a day) <1 error: word count  
artist_caption: _HAS_WORD_RE any letter caption decoration-only caption KEEP KC structural 0 -  

Section H structural files, one line each:

Measurements

All Axiom, dataset tootsies.

# query (short) window result what it decides
1 claude_api count by purpose 7d music_news_classify 2,529; music_desk_score 2,666; music_news_verify 817; topic_dedup 1,726; market_act_check 470; release_version 195; music_news_slate_rank 18; catalog_title_match 12; watched_act_match 7; chart_age_check 6; screen_same_work 4 the fold batches (B2, B7) add 0 calls to calls that already run
2 music_news_filtered count by reason 30d not_a_story 2,184; shortlist_full 477; chart_cut 337; unknown_artist 315; title_ungrounded 177; cert_out_of_scope 52; ladder_rung 13; market_unread 6 ranks B3 (chart_cut) above B2 cert work
3 chart_cut by detail.family 30d unnamed 130; genre_country 57; format_hot_ac 29; uk_official 28; genre_rock 27; billboard_japan 17; aria 12; others 37  
4 the 130 unnamed claims, read by hand 30d 10 name a KEPT chart and were cut: “Billboard Global Charts” x4 (Global 200), “most No. 1 albums on Billboard’s R&B/Hip-Hop chart” x3 (genre_albums), “Spotify Global chart/top 10” x2, “Spotify US Weekly Top 10” x1. 4 more imply the Billboard 200 (“No. 1 on the US albums chart, 88K”). The uk_official and aria cuts read correct B3 impact H: about 8% of the unnamed cuts lose a true story
5 cert_out_of_scope by empty body (cert_id ends in |) 30d 31 empty body, 21 named body; the 31 claims are all foreign (New Zealand x16, Italy/FIMI x11, Germany x3) the prose branch is live, and 31 of 31 cuts are right, so B2 cert work is lowest priority. It contradicts the A13 note “every cert carried a body”
6 music_news_slate by detail.rank, culture pool sums 7d rank model 18 slots, skip 35, blank 102 (pre-#3448 rows); culture pool 13,033 rows in 172 slots (about 1,860 rows a day after the number cue) B1 volume: the Jev check runs on the posts the cues drop, cached per post id
7 music_news_recover by detail.via 30d live_chart 423; mention_name 83; stale_refute 24; live_leg 10; tracker_lag 8; wire_spelling 3; forecast_wording 0 post_states_forecast override fired 0 times; its miss side is not measured
8 music_news_scored shipped=false by reason 30d unsupported_rank 14; prior_position 5; album_unmarked 5; later_week 1; chart_frame 0 _TRANSIENT_RE (0) can move to the scorer; mark_album and _LATER_WEEK_RE are low volume
9 reference_fetch / reference_lookup by source, surface 30d reference_lookup 5 (ask); genius fetches 3,560 and musicbrainz 6,805 come from the boards, not the intent router the genius / musicbrainz intent regexes route about 5 questions a month: fold, do not add a call
10 music_desk_trim by reason 7d streams_tier 3,376; light_milestone 254; tenure_floor 1 numeric trims, no word list (not a candidate)

Proposed batches

Merged into the Part 0 batch plan.

Notes

Inventory P2: markets, betting, sports, media

Audit date 2026-10-18. Repo at 4e1b6c0 (branch claude/classifier-audit-wave19). Read-only pass. Scope: the 51 files of the old section (docs/CLASSIFIER_AUDIT.md lines 692-1172), plus utils/sportsdata/*.py (11 files) and the new utils/market_sibling_claims.py. Every file was grepped for re.compile, word-list literals, substring tests, fuzzy ratios and free-text dict lookups, and the code around each hit was read.

The git history in this checkout is shallow (50 commits), so “NEW” means “not in the old section’s inventory”, checked symbol by symbol.

Counts

Semantic sites only. KEEP-PROTECTED sites are listed as semantic rows so that the protected set is visible.

file semantic structural KEEP-CLOSED MODEL-HAIKU MODEL-JEV KEEP-PROTECTED DONE OWNED
utils/markets.py 22 16 15 3 0 0 4 0
utils/market_cards.py 18 7 8 1 0 0 0 9
utils/market_subject_image.py 13 3 8 5 0 0 0 0
utils/market_outcome.py 10 4 7 1 1 0 0 1
utils/market_chart.py 3 3 2 0 1 0 0 0
utils/alert_triggers.py 1 1 0 0 0 0 0 1
utils/market_triggers.py 0 1 0 0 0 0 0 0
utils/market_siblings.py 0 0 0 0 0 0 0 0
utils/kalshi_price.py 1 1 0 0 0 1 0 0
utils/market_sibling_claims.py (NEW file) 1 1 0 0 0 0 1 0
utils/kalshi_ladder.py 6 2 5 0 0 1 0 0
cogs/market_drop.py 12 6 8 1 0 0 1 2
cogs/market_alert.py 12 7 7 1 0 0 0 4
cogs/bookie.py 2 6 0 0 0 2 0 0
cogs/betting_board.py, betting_alert.py, betting_value.py 0 11 0 0 0 0 0 0
utils/bet_corrections, odds_adapt, odds_compare, called_shot, leaderboard, table_card, ranking_strip 0 8 0 0 0 0 0 0
utils/sports_stories.py 4 2 1 1 0 0 2 0
cogs/sports_desk.py 1 2 0 0 0 0 0 1
utils/api_sports.py 2 6 1 0 0 1 0 0
utils/espn.py 1 7 1 0 0 0 0 0
utils/highlightly.py 0 2 0 0 0 0 0 0
cogs/sports_boards.py, utils/sports_boards.py 0 14 0 0 0 0 0 0
utils/sportsdata/*.py (11 files) 6 22 3 0 0 3 0 0
utils/playercard.py 2 5 2 0 0 0 0 0
cogs/playercard.py 4 1 2 0 2 0 0 0
utils/retrospective.py 4 0 0 0 0 0 0 4
utils/scrapecreators.py 0 5 0 0 0 0 0 0
utils/steam_art.py 1 1 1 0 0 0 0 0
utils/entity_image.py 0 7 0 0 0 0 0 0
image utils (vision_image, xai_image, image_gen, image_inspect, grid_vision, image_hash, image_codec) 1 14 1 0 0 0 0 0
media utils (media_edit, video_trim, clip_pick, video_fetch, video_ingest, audio, audio_tags) 1 22 1 0 0 0 0 0
utils/radio.py 2 4 2 0 0 0 0 0
utils/artist_socials.py 1 0 1 0 0 0 0 0
total 131 ~191 76 13 4 8 8 22

OWNED split: W19-B1 = 12 (market_cards 7, market_drop 2, market_alert 2, alert_triggers 1), #4428 = 2, #4412 = 2, #4422 = 1, W19-B3 = 5 (retrospective 4, sports_desk 1).

Inventory

utils/markets.py (9030 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
qualify_platform_metric + _PLATFORM_PATTERNS 9 platform names, one-platform rule Kalshi settlement rules weave the platform into the title HYBRID (B11 shipped) DONE Haiku market_platform_check already decides the 2+ platform case; the 1-platform case is an exact name match 0 (0 events in 30d) no rewrite  
_UNQUALIFIED_METRIC_RE views / streams / subscribers nouns Kalshi title whether a title needs a platform (part of above) KEEP-CLOSED Kalshi’s own metric nouns; the rewrite also needs a grounded platform 0 no rewrite  
series_source_platform platform name in settlement_sources[].name Kalshi series payload series platform – KEEP-CLOSED upstream source name, exact 0 no rewrite NEW (#4246)
_AFTER_ISSUANCE_RE / strip_issuance_clause “after issuance” Kalshi title cut a rules phrase – KEEP-CLOSED fixed upstream phrase 0 title unchanged NEW
_DOUBLED_ARTICLE_RE / tidy_kalshi_title “a a” Kalshi title collapse a typo – KEEP-CLOSED exact lowercase article pair 0 title unchanged NEW
_names_non_leader_slot (_RUNNER_UP_RE, _RANK_HASH_RE, _SEED_RANK_RE, _AUCTION_RANK_RE) runner-up / #N>=2 Kalshi event title NOMINATE the runner-up cut HAIKU (B2 shipped) DONE nominate-only: confirm_runner_up_cut (Haiku RUNNER_UP/OTHER) decides every cut; a regex miss keeps the event ~0.5 new tickets/day (15 calls/7d) keep event (open)  
_MUSIC_MARKET_CUES / _looks_like_music – – REMOVED 2026-10-27 – REMOVED owner steer: music #2 boards are cut too, so no music exemption exists and the cue list is gone. The Haiku task names music as RUNNER_UP 0 –  
_is_cut_runner_up_event category == entertainment + _names_non_leader_slot Kalshi category + title drop at discovery HAIKU DONE (via the two rows above) – – –  
_is_cut_netflix_views_event ticker cue NETFLIXTOPVIEWS + title backup Kalshi ticker + title drop the view-count board KEEP KEEP-CLOSED ticker prefix 0 drop (276 in 30d)  
classify_intent (_SPORTS_PATTERNS, _PM_PATTERNS, _WILL_X_BY_RE) as the _classify fallback substring word lists user query intent when the Haiku market_intent call errors KEEP DONE fallback only; 0 markets_classifier errors in 30d, so it never ran 0 None  
classify_intent as the discourse routing prefilter (cogs/discourse.py sportsy_channel) same lists, RAW SUBSTRING channel name + topic whether a quiet slot asks the market classifier at all KEEP MODEL-HAIKU branch: the admission miss (no convo, no category, no cue = no call). One cached classify SPORTS / OTHER per channel id + name + topic hash, 7d TTL; feed _SPORTS_PATTERNS as examples <5 after warm-up (one per channel per topic edit) model error: today’s regex  
looks_like_sports + _SPORTS_WORD_RE (icebreaker _market_line in cogs/discourse.py) classify_intent(...) == "sports" or \bsport channel name + topic whether the icebreaker fetches market odds at all KEEP MODEL-HAIKU same call as the row above (shared cache). Substring bug reproduced: “general-chats”, “stats-and-facts”, “conflict zone” (“nfl”), “nuclear takes” (“ucl”), “comma club” (“mma”), “whats good” (“ats”), “beats and bars” all read SPORTS shared with above model error: today’s regex (fix it to word boundaries as the fallback)  
detect_league + _LEAGUE_KEYWORDS in sgo_snapshots league words, fast accept before Haiku /ask SGO tool query league for the SGO fetch HYBRID (fixed #3457) KEEP-CLOSED (dead) fast accept (“football” -> NFL) decides alone, but the /ask SGO tool had 0 calls in 30d; revisit if it gets traffic 0 Haiku on miss  
_LEAGUE_SENTINELS “none” / “unknown” model output sentinel KEEP KEEP-CLOSED protocol sentinel – NBA default  
_KALSHI_SPORT_TAGS / kalshi_sport_label tag map Kalshi series tags sport label KEEP KEEP-CLOSED upstream enum – unclassified = kept  
_TAG_KIND_CUES / derive_kind_tags cue substrings Kalshi taxonomy tag names kind tags KEEP KEEP-CLOSED upstream taxonomy; reached only from the dead routing path below 0 whole category  
kalshi_channel_routing (_MUSIC_CUES, _CINEMA_CUES, _TV_CUES, _POP_CUES) cue substrings /ask topic arg which Kalshi category a topic pulls HYBRID (B10 measured dead) KEEP-CLOSED (dead) the drop passes topic=None; the /ask kalshi tools had 0 calls in 30d (tool_call). Dead-code candidate 0 global pool  
_kalshi_competition_to_sport + _KALSHI_SOCCER_CUES substring ladder competition name sport tag HAIKU (B5 shipped) DONE fallback under check_competition_sport (Haiku, 13,165 “changed” of 55,172 checks in 30d) ~575 calls/day in 7d (re-asked per process, cache 7d) ladder on error  
"kalshi" in lower (source order) substring /ask query source order KEEP KEEP-CLOSED (dead) venue name; same 0-call path 0 default order  
_norm_game_tokens + token guard in kalshi_player_props any >=3-char team token inside the folded title /ask game query vs Kalshi titles which events count as the matchup KEEP KEEP-CLOSED (dead) a fuzzy join by the new rule, but the tool had 0 calls in 30d. If revived: Haiku pick among the <=6 FTS candidates 0 prop dropped  
_kalshi_player_from_label split “:” Kalshi label player name KEEP KEEP-CLOSED template – whole label  
_clean_kalshi_label rsplit “: “ Kalshi label draw detection KEEP KEEP-CLOSED template – 2-way fold  
_POLY_QUERY_STRIP quote strip query search text KEEP KEEP-CLOSED cleanup – –  

Structural: _PARSER_SPORTS, _SGO_SPORT_LABEL, league constants, _classify_fetch_error, bookmaker preference, _PROP_STAT_* labels, bet in (ou, yn), _BLOCKED_TICKER_PREFIXES, SHOW_CATALOG, KXMVE prefix, yes/no formatting, _SGO_LEAGUE_ALIASES, _UNOPENED_STATUSES, _KALSHI_QUERY_TOKEN / _or_expand_query, kalshi_ticker_start / _KALSHI_TICKER_START_RE (NEW, #3560 date parse), DROP_CATEGORIES / parse_drop_category, MUSIC_SUBTAGS.

utils/market_cards.py (3716 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
figure_is_number digit + <=8 letters figure string number vs text card KEEP KEEP-CLOSED format – text card  
_MUSIC_CHART_RE / is_music_chart_market billboard / hot 100 / first week … / spotify Kalshi title music branding, report-lock branch, catalog-hint rung, AND admission to the take fabrication gate (via is_chart_slot_market) HYBRID, then A9 KEEP MODEL-HAIKU a regex that approximates “is this a music chart market”. Cheaper closed option first: read the series tags (MUSIC_SUBTAGS, an upstream enum) if the snapshot carries them. Else fold into the batch P2-A family call (per event ticker). Keep the regex as the fallback shared with P2-A model error: regex (today)  
is_music_lane / _MUSIC_SURFACES own surface names surface branding KEEP KEEP-CLOSED own enum – –  
_snapshot_is_sports / _is_sports_card category / tag / bet prefix meta sports layout KEEP KEEP-CLOSED upstream category + own surface – not sports  
_fold_cardtext / redundant label yes/no or title prefix labels hide a label row KEEP KEEP-CLOSED closed vocab – shown  
proposition_clause label in question labels + title clause text KEEP KEEP-CLOSED substring of an upstream leg label in its own question – title  
named_rank, title_rank, winner_rank (_RANK_RE, _RUNNER_UP_RE, _TOP_SLOT_RE, _THRESHOLD_*, _TOP_BOARD_RE) rank words title hero rank HAIKU (cluster) OWNED:W19-B1 old-B1 row (measured: regex does not miss on 42,708 live strings, #3424 note) – –  
award_slot / _AWARD_HEAD_RE winners / awards title award hero HAIKU (cluster) OWNED:W19-B1 old-B1 row – –  
split_rank_authority / _RANK_AUTHORITIES authority prefix title authority split HAIKU (cluster) OWNED:W19-B1 old-B1 row – –  
market_chart_tag (_YT_CHART_TAG_RE, _BILLBOARD_TAG_RE, _SPOTIFY_TAG_RE, _NETFLIX_TAG_RE) per-family template event title eyebrow tag HYBRID KEEP-CLOSED one exact pattern per Kalshi series template; a miss gives the category block (absent) – category block  
split_metric_title (_METRIC_WORDS, _METRIC_TITLE_RE, _METRIC_TAGS) Luminate / view-count metric event title metric / subject split HYBRID KEEP-CLOSED anchored Kalshi metric template; a miss keeps the whole title – whole title  
event_title_split (_EVENT_HEAD_RE, _EVENT_TAIL_RE, _EVENT_HEAD_BAD_RE, _TOP_LIST_HEAD_RE) event-word list event title event block HAIKU (cluster) OWNED:W19-B1 old-B1 row (_EVENT_HEAD_RE is an open word list) – –  
_OUTCOME_VERBS cluster: outcome_split, _regex_split, _field_clause, _field_candidate, _field_split, outcome_subject, existential_question, _outcome_plural, outcome_caption, prime_outcome_splits 232 verbs + anchors title hero + caption HAIKU (B1 part-shipped #3424, #4209) OWNED:W19-B1 – – – _field_split NEW (#4209)
statement_title, how_much_title (_INTERROGATIVE_WORD, _HOW_QUESTION) question words title statement rewrite HAIKU (cluster) OWNED:W19-B1 – – –  
subject_hero ladder rung order title hero rung HAIKU (cluster) OWNED:W19-B1 – – –  
take_rank_conflict (_ORDINALS, _TAKE_RANK_RE, _FORMER_RANK_RE, _TAKE_RANK_WINDOW_WORDS) position near the subject composed take + card hero drop the “#N” hero – MODEL (#4538) Haiku take_rank_place reads the place; the “#N” hero stays an exact regex; this rule is the error fallback – hero dropped NEW (#4383)
swap_to_stated_alt figure stated in take take + figure swap the hero figure – OWNED:#4428 number check – figure kept NEW (#4383)
_RANK_HERO_RE / _region_word “#N”, region words title hero region word KEEP KEEP-CLOSED small closed set – none  

Structural: card_block / number_card_tag_rung ladder, emit_card_hero rung names, _CONTAIN_SOURCES, _valid_http_url, _BARS_ONLY_SURFACES / _SURFACE_LABELS, _SINGLE_LEG_HERO_MIN_PCT, SETTLED_LEADER_PCT / market_settled.

utils/market_subject_image.py (1371 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_is_generic_outcome (_GENERIC_OUTCOME_WORDS, _GENERIC_OUTCOME_RE) yes/no/month/number leg label skip as image subject KEEP KEEP-CLOSED closed vocab – skip  
_top_charted_leg, _featured_label label in title labels image leg KEEP KEEP-CLOSED structural – first leg  
_subject_query, _extraction_text prefix before “:” title text sent to image_subject KEEP KEEP-CLOSED feeds the model – whole title  
detect_media_brand (_MEDIA_BRANDS, _BRAND_PATTERNS) 16 platform aliases title org-logo rung 3b HYBRID, then A9 KEEP KEEP-CLOSED runs only after image_subject named NOTHING (no_subject); the output set is closed to brands with a verified P154 logo; exact alias match – branded floor  
subtitle_catalog_hint ”::” prefix Kalshi subtitle catalog rung KEEP KEEP-CLOSED own encoding – none  
is_youtube_video_market “music video” and “youtube” title + event title YouTube chart art rung (fail OPEN) HYBRID KEEP-CLOSED Kalshi series wording; only selects an art lookup that verifies its own match (find_video_entry) – next rung  
youtube_chart_day / _CHART_DAY_RE date title chart-day window KEEP KEEP-CLOSED date parse – not current (gated)  
is_youtube_daily_chart_market (_YT_DAILY_CHART_RE) “daily … music video” title ADMITS a take to the fail-closed fabrication gate HYBRID MODEL-HAIKU P2-A family call: one cached Haiku classify per event ticker, labels SLOT_TITLES / FIELD / BINARY / COUNT / OTHER; input = event title, leg title, 3 leg labels, series tags, and the current regex verdicts as a hint P2-A model error: regex (today)  
is_netflix_rank_market (_NETFLIX_RANK_LEAD_RE, “view” / “how many” excludes) netflix + show/movie + rank lead title admits to the gate HYBRID MODEL-HAIKU P2-A family call P2-A regex  
is_chart_slot_market (_MUSIC_SLOT_LEAD_RE, uses is_music_chart_market) rank lead + music chart title admits to the gate (every chart family, #3442) – MODEL-HAIKU P2-A family call. A miss means the judge never reads the take, and the take ships unchecked P2-A regex NEW (#3442, #4238)
take_names_leader_only (_leg_phrase_in_take, _NON_AFFIRMATIVE_RE) leader phrase present, no rival phrase, no negation word composed take + leg labels FAST-PATH SHIP with no model call – MODEL-HAIKU branch: the fast accept. Send every gated take to the existing market_take_names_leader (purpose market_take_check). Keep the phrase test as a logged agreement signal. _NON_AFFIRMATIVE_RE is an open negation list (“drops to”, “couldn’t hold on” are not in it) <=10 (bounded by gated takes; 303 drops shipped in 30d across all families) judge error: drop (fail closed, today) NEW (#3442, #4238)
take_references_leg (_LEG_NOISE_WORDS, _LEG_QUALIFIER_RE, _PHRASE_MATCH_MAX_TOKENS, _POSSESSIVE_RE, fold helpers) via utils/youtube_grounding.take_names_a_leg phrase / token-bag identity composed take + leg labels DROPS a YouTube take that names no leg (market_alert non-leader YouTube lanes) HYBRID (C4 partial) MODEL-HAIKU branch: the no-match drop. Keep the phrase match as the fast accept; ask the same market_take_check judge (“does the take name one of these legs”) before a drop; feed the leg list <1 judge error: drop (today)  
resolve_market_image rung ladder routing classifier output image source KEEP KEEP-CLOSED routing on image_subject – branded floor  

Structural: _market_key, _subject_key / _cache_key, _download_raster checks. Removed since the old audit: artist_subject_name, _ARTIST_COUNT_METRIC_RE, _ARTIST_QUESTION_RE (no longer in the file).

utils/market_outcome.py (1334 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
is_no_result lead word “none” NONE token Sonar / Grok read no result KEEP KEEP-CLOSED protocol sentinel the query asks for – no result  
is_no_result -> perplexity.is_hedged hedge phrase list Sonar / Grok prose treats a read as NO result, alone KEEP MODEL-HAIKU branch: the hedge drop. Reuse claude.perplexity_found_answer (FOUND / NONE, purpose pplx_found_answer, B14) on the lead before the SOURCES block; feed _HEDGE_MARKERS as examples of NONE ~2 (outcome_extract 3 + winner_extract 9 in 7d; move_news reads also pass here) model error: hedge list (today)  
cited_hosts / cited_host (_SOURCE_URL_RE, _X_HOSTS, _MARKET_HOSTS) URL hosts read text independent source KEEP KEEP-CLOSED URL – not counted  
_GENERIC_LABELS, DECIDED_LOCK_PRICE yes/no, price labels / price decided entity KEEP KEEP-CLOSED closed / numeric – undecided  
normalize_name / _in_field exact fold winner index vs labels winner leg SHIPPED (A8) KEEP-CLOSED exact – no winner line  
_MARKET_TALK / has_market_talk / market_talk_phrase %, kalshi, polymarket, market, odds, favourite, underdog, tracking, priced, betting composed winner line one re-ask, then drop KEEP MODEL-JEV branch: the open words only (“market”, “odds”, “favourite”, “tracking”, “priced”, “bettors”). Keep %, percent, kalshi, polymarket exact. Jev Noul: “Does this line talk about a betting market, odds or a probability?” with the word list in the state as examples ~2 model error: today’s regex (re-ask, then drop)  
_NONE_VERDICT NONE / “answer is none” Sonar lead move_news_line = None – KEEP-CLOSED protocol sentinel plus its two prose forms – no news line NEW (#3783)
lead_publisher (_SOURCE_LINE) [n] -> SOURCES line Sonar read publisher stamp – KEEP-CLOSED structural – no line NEW
move_news_date, news_dates (_EVENT_DATE, _ISO_DATE, _WORD_DATE) dates Sonar lead date window – KEEP-CLOSED date parse – None NEW (#3760)
check_news_qualifier qualifier check news lead cut a qualifier – OWNED:#4412 – – – NEW

Structural: _SETTLED_STATUSES, encode_retry / hold_state state strings, _CITATION_MARK, _MARKDOWN_EMPHASIS.

utils/market_chart.py (2222 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
stamp_is_redundant source word in eyebrow own labels hide a stamp KEEP KEEP-CLOSED own strings – shown  
_order_two_up / _side_tokens shared >=3-char token overlap outcome labels vs matchup sides which number sits under which crest KEEP REMOVED 2026-10-19 with the two-number cover layout (owner: “go back to lines”) Measured 2026-10-18: 88 of 88 real Kalshi two-leg game events seat each number under its own crest; 0 errors. A same-city pair (“Los Angeles C” / “Los Angeles L”) never reaches it, because kalshi_game_teams returns no sides, and a no-match keeps the ranked order (safe). Old plan: a fuzzy identity join. First try the exact sportsdata.names.canonical_name fold (KEEP-CLOSED fast accept); on the residue, Jev Choice “which label is side X” over the two labels <2 input order (today)  
_lower_medium / _chart_medium plural rules own tag caption noun KEEP KEEP-CLOSED cosmetic – tag  

Structural: _mark_is_dark, size / aspect math, font fit.

utils/alert_triggers.py, market_triggers.py, market_siblings.py, kalshi_price.py, market_sibling_claims.py
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
alert_triggers.outcome_label (_TITLE_OUTCOME) “will X win” Kalshi title claim dedup key HYBRID (cluster) OWNED:W19-B1 old-B1 row (0 of 28,116 legs lack yes_sub_title, so the title path never runs) – yes_label  
kalshi_price trusted-price rules (WIDE_SPREAD, LAST_TRADE_TOL, bid_supported, book_backs_move) price thresholds order book trusted price KEEP KEEP-PROTECTED price threshold – dropped rung  
market_sibling_claims.same_subject_posts (_QUOTED, _TITLE_BY) quoted title, “ by <Artist>"</Artist> our own market posts NOMINATE siblings for market_sibling_claim (Haiku SAME / CONTRADICTS / DIFFERENT) – DONE nominate-only; the extractors parse our own post shapes ~0.4 no match = ships NEW (#2016)

Structural: market_triggers.classify_market_event bands, charted_move; market_siblings group-by; alert_triggers.claim_key.

utils/kalshi_ladder.py (1379 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
is_ladder_rung (_NUMERIC_STRIKE_TYPES, _THRESHOLD_LABEL_RE) strike_type + above/below words Kalshi meta + label rung vs leg KEEP KEEP-CLOSED upstream enum + fixed strike phrase – outcome  
_rung_subject / rungs_share_subject (_STRIKE_PHRASE_RE, _STRIKE_NUMBER_RE) strike words + numbers rung labels same ladder KEEP KEEP-CLOSED number strip – separate cards  
ladder_unit, is_bucket_leg %, $ label unit KEEP KEEP-CLOSED format – none  
date_rung_deadline, is_date_ladder, is_threshold_ladder, is_threshold_family dates labels ladder shape KEEP KEEP-CLOSED date parse – threshold  
_COMPANION_LADDERS, companion_event_matches, companion_outcome_matches, companion_outcome_period_end ticker registry; subject+period substring; equality Kalshi subtitles companion pairing KEEP KEEP-CLOSED one Kalshi template; companion_pair event watches template drift (#3458) – no pair  
ladder_is_decided, bucket_is_decided price thresholds prices decided KEEP KEEP-PROTECTED price threshold – none  
cogs/market_drop.py (3759 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_POLY_TAG, _GAME_RISK_CATEGORIES, _REPORT_CATEGORIES / _is_report_lock, _POLY_GAME_TAG category / tag upstream category drop family KEEP KEEP-CLOSED upstream enum (_is_music_chart part is in market_cards) – default  
_take_catalog_hint image_subject kind model output catalog rung KEEP DONE model already decides – none  
_slot_figure / _RANK_WINNER_TITLE_RE rank words title rank-winner figure HAIKU (cluster) OWNED:W19-B1 – – –  
titled_leg yes/no closed set labels named leg KEEP KEEP-CLOSED – – –  
_source_artist split “::” Kalshi subtitle crediting artist KEEP KEEP-CLOSED own encoding – ””  
_companion_names_artist artist substring in leg title Kalshi leg titles companion pairing (number on card) KEEP KEEP-CLOSED (fix) substring today (“Future” in “Future Islands”). Closed fix, no model: parse the “BY have" slot of Kalshi's leg template and compare the exact fold – no pair  
_is_single_game / _DRAW_LABEL_RE draw/tie labels single game KEEP KEEP-CLOSED closed set – field  
_clean_side, _matchup_sides, _slate_pairs, _matchup_covered (_MATCHUP_RE, _SIDE_*) canonical team names game titles one drop per game KEEP KEEP-CLOSED exact canonical_name keys – both post  
_norm_topic strip “the “ topic dedup key KEEP KEEP-CLOSED fold – raw  
_kalshi_event_subject / _KALSHI_TITLE_SUBJECT “win (the) X” title topic subject HYBRID OWNED:W19-B1 – – –  
_chart_slot_gateable + the gate call (yt_chart or _chart_slot_gateable) family detectors + fast accept snapshot + take whether the take is judged / ships HYBRID (DONE #3442) MODEL-HAIKU P2-A (caller side of the family call and the fast-accept removal) P2-A fail closed  
_FIGURE_RE / _withhold_figures number formats own prior posts mask figures in the history block – KEEP-CLOSED number format – – NEW (2026-09-18 steer)

Structural: price-shape helpers (_is_competing_field, _is_threshold_leg, _is_priced_threshold_ladder, _is_flat_wall, _is_unpriced_board, _frontrunner…), _alert_already_said (exact claim compare), CHILL / YAPS times. Calls into output_checks (drop_self_correction = OWNED:#4422, has_cross_source_comparison, ungrounded_numbers) belong to the output-checks partition.

cogs/market_alert.py (5945 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_GENERIC_LABELS, _PULSE_SUBTAGS / _is_pulse_market closed sets labels / tags pulse lane KEEP KEEP-CLOSED – – –  
_clean_authority authority prefix title prose subject HYBRID OWNED:W19-B1 – – –  
_headtohead_favorite (_VS_SPLIT) label in “A vs B” side event title + label favorite side KEEP KEEP-CLOSED Kalshi “A vs B” template; the label is that side’s own name – no favorite  
_forecast_figure (_LABEL_UNIT, _UNIT_SUFFIX) million/billion rung label unit KEEP KEEP-CLOSED format – –  
_quotes_range numbers in line take drop on wrong range KEEP KEEP-CLOSED number check – drop  
_race_label, _named_leg_event labels labels race framing KEEP KEEP-CLOSED – – –  
_non_leader_reason, ending-soon leader compare exact label labels leader identity KEEP KEEP-CLOSED exact – –  
_ending_soon_subject title title subject HYBRID OWNED:W19-B1 – – –  
has_self_correction output_checks take drop KEEP OWNED:#4422 – – –  
_CAUSE_BANS / _news_context exact prompt strings our own blob lift a ban – KEEP-CLOSED own prompt text – – NEW (#3363 follow-up)
_leader_check (is_youtube_daily_chart_market or is_chart_slot_market, take_names_leader_only) family + fast accept snapshot + take ship / drop – MODEL-HAIKU P2-A (alert caller) P2-A fail closed NEW (#4239, #4272)
qualifier_trim.restates_cut + check_news_qualifier qualifier words take drop – OWNED:#4412 – – – NEW

Structural: _PUBLIC_DELIVERIES, _settled_out, _soft_priced, _headline_license thresholds, context prompt constants, retry:hunting: state prefix.

cogs/bookie.py and the betting files
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
bookie._winning_team / _split_matchup (+ is_single_leg_knockout, knockout_advancer) scores + “ @ “ feed scores who won -> payout KEEP KEEP-PROTECTED settlement – void / push  
bookie._WORLD_CUP_FOOTBALL_LEAGUES, _KNOCKOUT_NATIONAL_LEAGUES, _THREE_WAY_SPORTS, _ODDS_API_* league ids own enums knockout + 3-way settlement, odds feed KEEP KEEP-PROTECTED settlement / pricing – –  

Structural: bookie stake presets, _line_is_known_stale, _is_degenerate_side, _needs_pricing, legacy key split; betting_board _SPORT_SUBJECT_WORD, _BOOK_LINE_NON_SPORTSBOOK, _PREDICTION_VENUE_LABELS, is_effectively_decided; betting_alert _favorite, _classify_move, _has_started; betting_value coherence / edge math; called_shot verdict_for, pct_error, SCORECARD_COLUMNS (no name matching); bet_corrections.STRANDED_BETS data; odds_adapt source routing; odds_compare, leaderboard, table_card, ranking_strip math / rendering.

utils/sports_stories.py (503 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
story_signature / dedup_key (_STOPWORDS, _TOKEN_RE, _SIGNATURE_TOKENS=5) first 5 non-stopword tokens wire post SKIP a story whose key matches a recent post (recent_sports_desk_events), and within a slot KEEP MODEL-HAIKU branch: the key-collision drop only. On a key hit against a recorded moment, ask the existing topic_duplicate (Haiku) with both texts; the stopword list stays the key builder. Needs the recorded headline stored beside the key <1 (18 desk posts in 30d) model error: skip (today)  
is_postable_moment not reply, >=24 chars wire post postable KEEP KEEP-CLOSED length – skip  
SPORT_BY_HANDLE handle map author handle sport tag KEEP DONE B8: the model tags every story; the map decides only a story the model did not place – –  
classify_sport (_SPORT_KEYWORDS) ~250 words headline sport tag SHIPPED (B8) DONE fallback under classify_sports_beats (sports_beat 83 calls / 7d) ~12 regex  

Structural: sport_penalty, apply_sport_diversity.

cogs/sports_desk.py, utils/api_sports.py, utils/espn.py, utils/highlightly.py, sports_boards
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
sports_desk retrospective_note / retrospective_frame / frames_as_past calls retrospective.py wire text / take throwback framing + drop see retrospective OWNED:W19-B3 – – –  
api_sports._team_logo_from_rows exact casefold name /teams rows crest KEEP, flag KEEP-CLOSED fixed #3453: no exact match -> None – absent  
api_sports status-code sets (_FOOTBALL_*, _BASKETBALL_*, … _TEAM_STATUS_SETS) status.short enum API-Sports live / done -> settlement KEEP KEEP-PROTECTED settlement – –  
espn.match_team_logo canonical_team or exact short; 2+ hits -> “” side name vs ESPN teams crest KEEP KEEP-CLOSED exact folded keys; ambiguity refuses – ””  

Structural: sports_desk._VISION_HOSTS; api_sports _count_goal_events, _event_kind, WNBA “ W” suffix, stat labels / leader thresholds, _response_error, _TEAM_IMAGE_HOSTS; espn _ENDPOINTS, _SINGLES_SLUGS, _competitor_name, TEAM_LEAGUES, stat names, _leader_display, odds math; highlightly _HOSTS, type == VERIFIED; cogs/sports_boards _ODDS_KINDS, _CHANGE_GATED_KINDS, dedup key format, _ODDS_PROP_MARKETS, _SGO_BOARD_LEAGUE, date filters, side == “Over” (its has_self_correction call is OWNED:#4422, counted in output_checks); utils/sports_boards floors, short_stat, join keys, _game_line_figure, league_is_live, streak / record parsing, _with_implied_pct, upcoming_board hero rules.

utils/sportsdata/*.py (11 files, ~7400 lines; NEW to this audit)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
names.canonical_team / canonical_name / canonical_fighter / canonical_soccer_club (+ _SOCCER_CLUB_TOKENSET token-set index, espn_team_name) alias tables, exact fold, token-set equality with an ambiguity guard, initial+surname provider team / fighter names match_key -> bet settlement, hub dedup – KEEP-PROTECTED settlement identity; a miss voids (never mis-pays) – void NEW to audit (#4327 token-set)
names.kalshi_game_teams / _league_team / _MATCHUP_SPLIT_RE ticker league + unique roster resolve; a soccer series (_KALSHI_SOCCER_SERIES) passes the two club names through Kalshi ticker + title crest pair on a market card – KEEP-CLOSED exact roster resolve; both sides must resolve uniquely (soccer: both must match inside one ESPN league) – None (branded floor) NEW to audit
format.is_knockout_round, _is_final_round, _KNOCKOUT_ROUND_KEYWORDS, _TWO_LEGGED_NAME_HINTS, _is_two_legged_competition round-label and league-name substrings API-Sports league.round / league name draw suppression -> 2-way vs 3-way settlement and pricing – KEEP-PROTECTED settlement – keep the draw NEW to audit
format.fold_prediction_as_line, fold_prediction_match_winner, prediction_line_odds canonical_team mapping prediction outcomes vs game sides the bettable line – KEEP-PROTECTED money path (pricing) – no write NEW to audit
format “own goal” in e.detail upstream detail text API-Sports event own-goal label – KEEP-CLOSED upstream field – – NEW to audit
providers._espn_event_kind goal / card / substitution in type.type ESPN keyEvent type event kind – KEEP-CLOSED upstream enum – drop incident NEW to audit

Structural: board stat / period labels, _ODDS_BOARD_MARKETS; cadence _BASKETBALL_PERIODS; competitions registry; format _SPORT_LABEL, _DAY_WORDS, _STAT_ORDER, _EXCHANGE_LABELS, _LINE_KEYS, _MONEYLINE_KEYS, _DRAW_SPORTS, draw / tie label checks, “player “ prefix; hub _SGO_SIDED_SWAP, _SGO_UNSIDED_KEYS; models status code sets, match_key_parts; names rosters / aliases / _QUIRK_OVERLAY / _CODE_SYNONYMS; odds_health key checks; providers _EXTRA_SPORT_FETCH, _ODDS_SPORT_TAG, _ESPN_PERIOD_PREFIX, _ESPN_TEAM_STAT_LABELS, _ESPN_LEADER_ABBR, clock regex; sources capability sets, _SETTLEMENT_PRIORITY; sports watch keys and league id sets.

utils/playercard.py, cogs/playercard.py
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
utils.playercard.about_member_text (_SENT_SPLIT, names.name_pattern / match_forms) exact alias forms memory notes + aliases sentences fed to the Opus blurb KEEP KEEP-CLOSED exact alias match; the model judges after – ””  
utils.playercard.board_ranking / format_card_pages aliases closed alias sets model tool argument board / page KEEP KEEP-CLOSED closed-label parser with a self-describing miss – hint  
cogs.playercard._who_autocomplete fold contains keystrokes autocomplete list KEEP KEEP-CLOSED search fold; the user picks – []  
cogs.playercard._resolve_target fold exact, then FIRST contains-hit /playercard text whose card renders KEEP KEEP-CLOSED (CB-31) Measured 2026-10-18 and FIXED in code: best_name_match adds two exact start steps (whole name, then a word) before contains. 517 queries from the real roster: 12 better, 1 tie changed, 0 other worse. Jev Choice 10 of 11 and Haiku 9-10 of 11 on the decidable ties, the start steps 11 of 11. Old plan: fuzzy identity join. Exact fold stays the fast accept; when 2+ members contain the text, Jev Choice over those display names (closed list) <1 not_found / first hit (today)  
cogs.playercard._resolve_member_id exact name, else FIRST substring /ask engagement tool target whose stats the tool reads KEEP KEEP-CLOSED (CB-31) Same best_name_match as _resolve_target (CB-31). Old plan: same shape as above (“sam” -> “samantha” before “sam”) <1 (engagement tool: 4 calls in 30d) first hit (today)  
cogs.playercard scope / board aliases closed sets model tool argument scope / board KEEP KEEP-CLOSED – – hint  

Structural: _STATS weights, archetype_for, _BADGE_RULES, top_public_channel, _SUPERLATIVES; cog aspect in (member, card).

utils/retrospective.py (555 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_FRAMES / is_retrospective / retrospective_frame / retrospective_note / _TAG_RE anchored throwback leads wire text throwback instruction HAIKU (B3) OWNED:W19-B3 – – –  
states_past_interval / _PAST_INTERVAL_RE “N years ago” wire text cover vs photo HYBRID OWNED:W19-B3 – – –  
frames_as_past / _PAST_MARKERS / _FUNCTION_WORD / _ANNIV_COUNT past markers take drop HYBRID OWNED:W19-B3 – – –  
retrospective_values, look_back_values, _interval_count, _COUNT_WORDS numbers phrase / take number grounding KEEP OWNED:W19-B3 the brief keeps the arithmetic (number check) – –  
utils/steam_art.py, utils/entity_image.py, utils/scrapecreators.py
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
steam_art.pick_app / _fold exact alphanumeric fold, 2+ hits -> None leg name vs Steam names appid art KEEP KEEP-CLOSED exact identity; image_subject kind=video_game gates the rung (Uno incident) – None  

Structural: steam_art.first_screenshot, _LIBRARY_URL; entity_image _SPORT_SEARCH_WORD, subject_search_query (artist-in-name query build), _deterministic_art kind ladder, _artist_rung, _BRAND_SUFFIXES, _card_bare_logo, _team_fallback_card, _resolve_primary order (all routing on image_subjects output); scrapecreators tweet / TikTok / IG shape maps, _IG_MEDIA_TYPES, short-link host rewrite, FailoverProvider._active, credits_remaining key.

Image and media utils
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
image_gen._is_safety_reject / _SAFETY_SIGNATURES “safety”, “moderation”, “content policy” substrings OpenAI 400 body fail over to Grok KEEP KEEP-CLOSED upstream error text. Better closed key: the error body’s code field (content_policy_violation) – no failover  
audio_tags.filter_to_allowlist / ALLOWED_TAGS / _TAG_RE 11-tag allowlist model output which [tag] reaches ElevenLabs KEEP KEEP-CLOSED closed allowlist on a model-output protocol – drop tag  

Structural: vision_image (magic bytes, SUPPORTED_MEDIA_TYPES, pbs host bound, redirect codes), xai_image._ASPECT_RATIOS, image_gen _ASPECT_SIZES / _FAILOVER_KINDS, image_inspect colour anchors and grid thresholds, grid_vision thresholds, image_hash threshold, image_codec limits; media_edit (_OP_SPECS, HEIC brands, extensions, _COPY_CONTAINERS, clamps), video_trim._PTS_RE, clip_pick (_PTS_RE, _LOUD_RE, marker keys), video_fetch (host lists, _YOUTUBE_ID_RE, caption language order, VTT parsing, X length limit), video_ingest (_enabled, _needs_summary), audio ffmpeg args.

utils/radio.py, utils/artist_socials.py
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
radio.COVERED_FORMATS (+ CUT_FORMATS) 3 Mediabase formats upstream format name which charts make stories KEEP KEEP-CLOSED upstream format enum, owner-curated – out (absent)  
radio.standing song_key equality + artist_watch.credit_matches Mediabase rows radio context block KEEP, flag KEEP-CLOSED fixed #3454: exact house identity – ””  
artist_socials._ARTIST_SOCIALS / socials_for / name_for_handle curated map, exact fold artist name / handle official accounts KEEP KEEP-CLOSED curated on purpose (a guessed handle seeds an impersonator) – skip  

Structural: radio spin / add thresholds, _KIND_EYEBROW, card_fields, board_groups (shared identity home).

Measurements

All Axiom queries are on dataset tootsies. “30d” = now-30d to now (2026-10-18).

  1. claude_api | summarize count() by purpose, 7d. In scope: market_sport_check 4027, image_subject 276, market_act_check 470, sports_beat 83, market_intent 59, market_runner_up_check 15, winner_extract 9, outcome_extract 3, market_take_check 2, drop_score 169, sports_desk_score 30. Decides: the take judge runs about twice a week, so removing the fast-path accept (P2-A) adds calls bounded by the gated takes, not by all posts.
  2. market_label_check | summarize count() by purpose, reason, 30d: market_runner_up_check cut 893, kept 0; market_sport_check same 42,006, changed 13,165, judge_failed 1; market_platform_check 0. Decides: the runner-up judge has never overruled a nomination, so sending the music-cue hits to it (P2-B) is cheap and only adds cuts on false cue hits. The platform check has had no input in 30 days.
  3. tool_call | summarize count() by tool, 30d: chart_history 71, search_messages 29, look_at_image 8, engagement 4, spotify_chart 1, box_score 1, game_plays 1. No kalshi_* or SGO tool call. Decides: kalshi_channel_routing, derive_kind_tags, the detect_league fast accept in sgo_snapshots, the “kalshi” source-order test and the kalshi_player_props token guard are DEAD paths (KEEP, revisit on traffic). The two playercard resolvers see under 1 call a day.
  4. market_drop_scored | summarize total, shipped, leader-gate declines, 30d: 1,375 rows, 303 shipped, 0 leader_fabrication / leader_check_unavailable. Decides: the volume bound for P2-A (at most about 10 gated takes a day), and that the gate never fired in 30 days (every gated take went through the regex fast path or the judge passed it).
  5. market_filtered | summarize count() by reason, 30d: non_leader_chart 3,290, netflix_views 276 (plus price and horizon reasons). Decides: the runner-up cut is active; the music exemption decides before the judge on every ranked music board.
  6. error | where source in (markets_classifier, markets_manager), 30d: 0. Decides: the classify_intent regex fallback inside _classify never ran (DONE, fallback only).
  7. claude_api 30d, market_intent 135, image_subject 497, sports_beat 83, topic_dedup 13,611. Decides: per-call volumes used in the batch table.
  8. surface == 'sports_desk' or event startswith 'sports_desk', 30d: 18 sports_desk_posted, 531 slot_muted. Decides: the sports-story signature dedup (P2-D) is low impact.
  9. Local reproduction (pure functions, no network): classify_intent and looks_like_sports return sports for “general-chats”, “stats-and-facts”, “conflict zone”, “nuclear takes”, “comma club”, “gamma rays”, “whats good”, “beats and bars”; _looks_like_music returns True for “#2 Most Streamed Show: Trap”, “Runner-Up Netflix Show: Songs of Freedom”, “#2 Google search: Grape”. Decides: P2-B brittleness = M (raw substring tests, not word boundaries).

Proposed batches

Merged into the Part 0 batch plan.

Notes

Inventory P3: conversation, X, gates, output checks

Repo state: branch claude/classifier-audit-wave19 = origin/main 4e1b6c0. Read-only pass. Old audit section: docs/CLASSIFIER_AUDIT.md lines 1174-1506. Axiom window: 30 days to 2026-10-18 unless a row says 7 days.

Ownership applied:

Counts

file semantic structural KEEP-CLOSED MODEL-HAIKU MODEL-JEV KEEP-PROTECTED DONE OWNED
claude_client.py 16 ~30 10 0 0 2 2 2
utils/output_checks.py 30 8 7 10 0 2 1 10
utils/slop_score.py 1 1 1 0 0 0 0 0
utils/x_reply_draft.py 1 3 0 0 1 0 0 0
cogs/x_reply_draft.py 1 3 0 0 0 0 0 1
utils/perplexity.py 4 3 0 2 0 0 2 0
utils/dedup.py 4 4 1 1 0 0 2 0
utils/engagement.py (+ engagement_labels) 3 3 0 0 0 0 3 0
utils/curator.py, cogs/curator.py, cogs/curate.py 1 4 0 0 0 0 1 0
utils/curate.py 1 1 1 0 0 0 0 0
utils/x_mentions.py 3 5 0 0 0 0 3 0
cogs/x_mentions.py 2 5 2 0 0 0 0 0
utils/x_crosspost.py 1 6 0 0 0 0 1 0
utils/x_game.py, cogs/x_game.py 2 4 1 0 0 0 1 0
utils/reference.py 3 4 0 3 0 0 0 0
cogs/games.py 4 4 2 0 1 0 1 0
utils/wheel.py 1 2 0 0 0 0 0 1
cogs/awards.py 1 1 1 0 0 0 0 0
utils/starboard.py, cogs/starboard.py 2 6 1 0 1 0 0 0
utils/roles.py, cogs/roles.py 2 4 1 0 0 1 0 0
cogs/chimein.py 1 3 1 0 0 0 0 0
cogs/memory.py + memory utils 1 4 0 0 0 1 0 0
cogs/ask.py 4 8 2 1 0 0 1 0
cogs/commentator.py 1 6 0 0 0 0 1 0
cogs/scheduled_poster.py, utils/abuse_tracker.py 2 2 0 0 0 0 2 0
cogs/order.py 1 2 0 0 0 1 0 0
utils/image_signal.py (+ voice/gif/emoji tag files) 1 9 1 0 0 0 0 0
the other ~40 files (structural only, see Inventory) 0 ~60 0 0 0 0 0 0
total 94 ~205 32 17 3 7 21 14

Inventory

claude_client.py (23236 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
is_empty_response EMPTY exact / prefix / last sentence model output skip slot KEEP KEEP-CLOSED sentinel protocol - skip  
_SELF_CORRECTION_MARKERS, _strip_self_correction, _after_correction_clause 16 lowercase phrases model output keep last paragraph HYBRID OWNED:#4422 - - ship  
_parse_wheel_items + _WHEEL_*_WORKING_TASK has_self_correction + is_narration, then classify(decline_narration) wheel lines drop slice HYBRID OWNED:#4422 already a Haiku label behind the lists - keep slice  
_final_card_line, _extract_track_line, _extract_xpost_directive EMPTY, TRACK:, XPOST: tags model output split / link / crosspost mode KEEP KEEP-CLOSED protocol tags - no link  
_TRENDING_LABEL_LEAK, _clip_for_reaction [A] / clip A leak; link in text react output retry KEEP KEEP-CLOSED scaffolding leak, structural - ship  
_parse_* closed-set validators (market_intent, mention_intent, music_mention, music_news, image_subject, video_subject, sport_beats, gif_pick, picked_tickers, album_track_matches, keyword_terms, song_pool, scored-JSON spine, topic_duplicate, qualifier_verify, names_leader, outcome_value, winner_pick, match_verdict) enum / JSON literal checks model output route / drop KEEP KEEP-CLOSED model-output protocol - per method  
_parse_x_post unit_from model-quoted words must be a substring of the story and not of the title/artist Haiku classify_x_post reply + story keep song/album unit - KEEP-CLOSED grounding of a model quote in its source (exact containment) - unit “” NEW
_artist_named_in, _artist_named_in_strict, _mark_grounded_artists folded whole-word containment model artist vs source artist_grounded stamp KEEP KEEP-CLOSED exact folded grounding check outside the model (#2691) - ungrounded  
_has_perplexity_grounding (forced retry) is_hedged Perplexity block skip forced web_search HYBRID DONE (B14 #3437) _perplexity_grounded -> Haiku perplexity_found_answer first, list on None 0 in 30d (pplx_found_answer purpose absent) retry  
draft EMPTY / len > 400 prefix + length X reply drafts drop draft KEEP KEEP-CLOSED protocol + length - drop  
preflight_order verdict parse ALLOW/PLUMBING/REJECT prefix Sonnet output /order gate KEEP KEEP-PROTECTED protected path - reject  
label parses (classify_abuse ABUSE, wire_subject_named NO, media/clip/admat/identity YES/NO/DIFFERENT, classify() prefix match) prefix on upper-cased label model output label KEEP KEEP-CLOSED closed-label parser - per method  
_IMAGE_FAILURE_PHRASES, _GENERIC_FAILURE_PHRASES, _IMAGE_BLOCK_RE, _is_image_error API error substrings Anthropic 400 text drop one image KEEP KEEP-CLOSED upstream error vocabulary - not image error  
_MEMORY_FENCE prompt text - - KEEP KEEP-PROTECTED constitutional - -  
_WORKING_LABEL_RE ^WORKING: on scorer reason Haiku scorer reason tag a WORKING fail - KEEP-CLOSED model-output protocol - - NEW
model judges added since 09-18 (claim_grounded, universal_scope_claim, is_flourish_clause, is_music_artist_mention, perplexity_found_answer, _SHOWS_ITS_WORKING_RULE, _IDENTITY_RULE) - - - - DONE these ARE the model side see Measurements - NEW

Structural: fence-strip / first-{...} regexes in every _parse_*, _BARE_INT_KEY, _MARKET_PCT_RE, capability sets (_SAMPLING_MODELS, _THINKING_OFF_PARAM, _NO_FORCED_TOOL, _ALWAYS_THINKING, _OPENAI_TRUNCATED_STOPS, _OPENAI_MODEL_IDS, _RETRYABLE_STATUS), tool type.startswith("web_search"), robots.txt/disallowed exception text, URL strip in tool messages, memory_rollup stop reason, _format_kalshi_candidate substring, all *_TOOL schemas. Cross-partition note: _parse_music_news calls music_news.later_week_first_week_claim and FIRST_WEEK_PHRASE_RE (music partition owns them).

utils/output_checks.py (2658 lines; was ~1400 at the old audit)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_TOOL_NARRATION_RE, has_tool_narration first-person tool verbs model output check_text flag KEEP KEEP-CLOSED (DEAD) no production caller; only tests 0 -  
_THINK_PIECE_WORDS, think_piece_hits, check_text 9 critic words; dispatcher model output flag KEEP KEEP-CLOSED (DEAD) no production caller; delete or wire 0 -  
_SELF_CORRECTION_RE, _DRAFT_SEPARATOR_RE, has_self_correction correction markers model output drop HYBRID OWNED:#4422 - - drop  
_ALLTIME_CLAIM_RE, _POSSESSIVE_*, has_career_claim, career_claim_hits, career_claim_ungroundable, has_alltime_claim, has_ungrounded_career_claim superlative / ordinal regex model output drop HYBRID (C8) OWNED:#4412 - 3,428 drops / 30d (career_claim) drop  
_DECLINE_NARRATION_RE, _POS_PAIR_RE, decline_narration_hits, has_decline_narration decline verbs + objects model output drop HAIKU (B4) OWNED:#4422 - 6 drops / 30d (newsroom) drop  
_MARKET_DECLINE_RE, market_decline_hits, has_market_decline decline verb + figure model output drop - OWNED:#4422 - - drop NEW
_PRIOR_POSITION_RE, prior_position_hits (phrase arms), has_prior_position “last week”, “was #9”, “after ranking”, “previously” fall take drop (fall lanes) - MODEL-HAIKU classify on a regex HIT only: “does LINE state the title’s earlier chart position or an earlier week?” labels PRIOR / CURRENT_ONLY; the phrase list fed as examples. Miss branch: one rubric line in music_desk_score (0 calls). Coordinate with #4422 (same lanes, same ship_guard). 5 drops / 30d -> ~0.2 drop on error NEW
_ANY_POS_RE arm of prior_position_hits (current=) any other #N than the take’s rank fall take drop - KEEP-CLOSED number compare - drop NEW
_ROW_ARITHMETIC_RE, has_row_arithmetic combined / double / 3x / half board take drop (KEEP) MODEL-HAIKU classify on HIT: “does LINE add rows up or state one row as a multiple of another?” SUM_OR_RATIO / OK; exemption list (cert levels, “double album”, HITS Daily Double) fed. Miss branch: rubric line. 0 desk drops / 30d -> ~0 drop on error  
_HITS_SOURCE_RE, names_hits_source, _MARKET_SOURCE_RE, _CROSS_SOURCE_COMPARATIVE_RE, _SETTLED_COUNT_RE, has_cross_source_comparison three regexes must all hit; settle exemption desk take drop (owner ban) HYBRID (C8, not wired) MODEL-HAIKU classify on HIT: “does LINE weigh a prediction-market figure against HITS Daily Double’s figure?” COMPARES / OK. Miss branch: rubric line. 0 drops / 30d (music_desk), market_drop also calls it drop on error  
_STREAM_FIGURE_RE, _UNITS_FIGURE_RE, _STREAM_UNIT_RANKING_RE, has_stream_unit_comparison units fig + stream fig + ranking word desk take drop - MODEL-HAIKU same shape: “does LINE rank a units/sales figure against a stream count?” 0 / 30d drop on error NEW
_ROW_TALLY_RE, row_tallies, ungrounded_row_tally “N of the top 10” vs source board take drop KEEP OWNED:#4412 (number check; KEEP-CLOSED in substance) 0 / 30d drop  
has_row_tally (sports_boards, cinema_desk) “N of the top M” board take drop KEEP KEEP-CLOSED fixed count format - drop  
_IDENTITY_FAMILIES, _IDENTITY_SUBSET_RE, identity_subset_hits, has_identity_subset_claim rank word + lowercase gender word, source grounding take drop - KEEP-PROTECTED constitution-adjacent hard fail; the model _IDENTITY_RULE already decides every desk/drop post beside it (#4261/#4276) 0 / 30d drop NEW
_HITS_VERBS, _HITS_PUBLISHER, _ATTRIBUTION_TAG_RE, attribution_tag_hits, has_attribution_tag source names + reporting verbs + possessive numbers take re-ask then drop KEEP MODEL-HAIKU classify on HIT: “does LINE credit a data source (per X / X says / X’s count), or only name a chart or product?” CREDIT / CHART_NAME; the source list fed. Measured false positive: all 8 drops in 30d were “HITS’ Top 50” (a chart name the code comment says must pass). 8 drops / 30d -> ~0.3 drop on error  
_CONFIDENCE_HEDGE_RE, _HEDGE_* allowlists, confidence_hedge_hits “unverified”, “reportedly”, “is reported to” newsroom take drop - MODEL-HAIKU classify on HIT: “does LINE disclaim confidence in its own figure?” DISCLAIMER / OTHER; _HEDGE_NON_FIGURE_NOUNS fed as examples. Measured: 1 of 6 drops hedges a non-figure fact (“negotiations reportedly fell through”). Miss branch: the existing CONFIDENCE DISCLAIMER rubric line. 6 / 30d drop on error NEW
_SELF_COUNT_CREDIT_RE, _ANONYMOUS_CREDIT_RE, self_count_credit_hits, anonymous_credit_hits “per X’s own count”, “a third-party tracker” newsroom take drop - MODEL-HAIKU same call as the hedge row (label WRONG_CREDIT added) 0 / 30d drop on error NEW
_CLAUSE_SPLIT_RE, trailing_clause, strip_trailing_clause last clause finder desk take nominate clause - DONE Haiku is_flourish_clause decides every clause (#3257) 14 / 7d keep clause NEW
_METRIC_FOREIGN_WORDS, metric_foreign_word wrong-metric word table settle / alert take drop HYBRID (C8, not wired) MODEL-HAIKU one cached classify per line: “which album metric does LINE state its figure in?” PURE_SALES / UNITS / STREAMS / NONE / SEVERAL; the word table fed as reference shared with next row drop on error  
_STREAMS_WORDS, ALBUM_METRIC_KEYS, album_metric_named, same_album_metric_posts inverse of the word table recent posts which posts the topic judge may compare - MODEL-HAIKU same label, cached per post text (history posts are re-read each slot) ~60 new texts/day (music_alert_scored 414 / 7d) keep post comparable NEW
_FIGURE_TOKEN_RE, states_figure, figure_value, settled_figures number at printed precision take settle repeat test - KEEP-CLOSED number check - - NEW
_TITLE_EDITION_RE, _title_forms (lead 3 words of a long title), missing_release_title folded containment, edition strip, prefix settle take vs subject drop / dedup filter - MODEL-HAIKU exact fold stays the free accept; on a miss, classify “does LINE name the release TITLE (an edition or short form counts)?” YES / NO few / day drop on error (today) NEW
_WORD_VALUES, _NUMBER_RE, ungrounded_numbers, numeric_grounding number set difference take vs source drop KEEP OWNED:#4412 (number check) - drop  
_AGE_CLAIM_RE, ungrounded_age_claim age figure take vs source drop KEEP OWNED:#4412 - - drop  
_CAREER_ORDINAL_RE, _FLAGGED_ORDINAL_RE, ungrounded_career_ordinals, ungrounded_flagged_ordinals ordinal vs source take drop HYBRID OWNED:#4412 - - drop  
_RANKING_CLAIM_RE, _RANKING_STOP, _UNIVERSAL_SCOPE, ungrounded_ranking_claims, universal_scope_candidates ranking regex + stopwords take drop / nominate HYBRID OWNED:#4412 - - drop  
_RANK_SUPERLATIVE_RE, _SOURCE_RECORD_RE, unsupported_rank_claims rank superlative vs evidence newsroom take drop - OWNED:#4412 (#4471) - 14 / 30d (unsupported_rank) drop NEW
memory_note_violations mention / snowflake / URL regex memory note reject note KEEP KEEP-PROTECTED fence cogs/memory.py:_tag_and_store via scrub_memory_note (#4528) -  
has_context_tag_leak, has_track_line own tags model output flag KEEP KEEP-CLOSED (DEAD) no production caller 0 -  
too_short_to_ship length < 15 model output skip KEEP KEEP-CLOSED length - skip  

Structural: has_em_dash / strip_em_dashes / strip_markdown_emphasis / strip_citation_tags (formatting), emoji_ok, visible_len / length_ok / count_sentences / link_count, _MENTION_RE, _DISCORD_ID_RE, _TRAILING_URL_LINE_RE, _TRACK_LINE_RE, _CONTEXT_TAG_RE, _LINK_CEILING_SURFACES / _POST_SURFACES / _RUNDOWN_SURFACES.

utils/slop_score.py (116 lines)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | _NOT_X_BUT_Y, _lexicon_re, slop_hits, slop_score | vendored EQ-Bench lexicon | post_preview | telemetry field only (emit auto-stamp, x_audience) | KEEP | KEEP-CLOSED | decides nothing; the independent lexicon signal is the point | - | - | | Structural: _WORD_RE.

utils/x_reply_draft.py (314 lines)

| symbol | pattern | input | decision | old | new | shape + input | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | _TICS, tic_hits | 7 named tic regexes | Sonnet X reply draft | one re-ask naming the phrase | KEEP | KEEP-CLOSED (CB-31) | Measured 2026-10-18 on 88 real Sonnet drafts (hand labels): regex 7 false-hit drafts + 1 miss; Jev 7 Nouls 35-42 false hits, 14 drafts flipped run to run; Haiku 11-13 false hits. The models lose; the regex stays. Old plan: 7 Nouls in one Jev request, one per tic NAME (“does draft lean on the phrase , e.g. ?"); a yes > 0.5 names that tic in the re-ask. The regex decides on a Jev error. The measured regression (9/35 -> 19/36) was the re-ask SHOWING drafts, not detection, so the re-ask wording stays. | ~5 (x_reply_draft 154 / 30d) | original draft | | Structural: `_URL_RE`, `DRAFT_MARKER` / `is_draft_message`, `_STAGING_MARKER` / `is_staged_audit`, `find_x_status_link`, `beat_subject` split.

cogs/x_reply_draft.py (682 lines)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | banger_subject | first search term name_in_text the post | banger post | subject for check_source_qualifier | - | OWNED:#4412 | feeds the qualifier check (#4467) | - | author fallback | NEW | Structural: _QUALIFIER_TRIGGERS, _OWN_HANDLES, "tootsiesbar" in author, author_name label strip, _CHANNEL_SOURCES.

utils/perplexity.py (812 lines)

| symbol | pattern | input | decision | old | new | shape + input | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | is_hedged at the forced-retry gate | _HEDGE_MARKERS (11 phrases) | Sonar answer | skip retry | HYBRID | DONE (B14) | Haiku first | - | retry | | | is_hedged at its OTHER callers: cogs/music_desk.py (pplx research), cogs/music_news.py x2 (artist pull, figure research), utils/market_outcome.is_no_result, utils/milestone_qualifier.is_no_qualifier | same 11 phrases | Sonar answer | DROP the research (fast-path drop, decides alone) | (telemetry) | MODEL-HAIKU | on a list HIT only, call the existing ClaudeClient._perplexity_grounded / perplexity_found_answer (FOUND / NONE, cached by body hash); a miss needs no call because the compose and verify judges read the research. The milestone_qualifier caller is qualifier-epic territory: hand that one to #4412. | ~9 (59 hedged of ~4.8k Sonar answers / 7d) | list verdict on error | | | _DISPUTE_RE, research_disputes | dispute words in the opening paragraph | Sonar verify answer | dispute flag | - | DONE | the verify judge’s web_disputes decides first; the regex only on no flag | - | no dispute | NEW | | _MAGNITUDE_RE, _NOT_UNITS_RE, strip_industry_projection (+ names_hits_source) | HITS named + units magnitude per sentence | Sonar text (model INPUT) | cut the sentence | KEEP | MODEL-HAIKU | per candidate sentence that names HITS (names_hits_source), classify “does this sentence give HITS Daily Double’s projected first-week units?” CUT / KEEP; the regex verdict on error | <5 (music_desk projection lane, pplx_music_desk 389 / 7d, few name HITS) | cut on error (today) | | Structural: _SEARCH_CONFIG, _CATEGORY_QUERIES, build_search_query, answer_body, _SENTENCE_SPLIT_RE, _extract_sources.

utils/dedup.py (639 lines)

| symbol | pattern | input | decision | old | new | shape + input | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | duplicate_match signals (SequenceMatcher 0.6, 40-char run, _STOPWORDS content overlap) + arbitrated_match(catch=True) | mechanical + Haiku topic_duplicate both directions | composed post vs history | drop / ship | KEEP (hybrid) | DONE | judge decides hits and misses | topic_dedup 13,618 / 30d | mechanical on None | | | arbitrated_match(catch=False) miss branch (cogs/discourse.py x2, cogs/chimein.py x2) | the fuzzy ratio alone says “fresh” | room post | ship without the judge | KEEP | MODEL-HAIKU | pass catch=True (the existing judge); this is the only branch where a fuzzy text ratio decides alone | ~7 (discourse_score 195 / 30d; chime-in 0 calls in 30d) | ship on None | | | _POSITION_RE, _PAST_RANK_RE, _post_ranks, _same_rank (same_chart_position signal) | current vs past rank per clause | chart line vs history | mechanical hit | - | DONE | a hit goes to the judge (overturn); dedup_overturned 1,774 / 30d shows it works | - | judge | NEW | | chart_claim, _titles (quoted / “X by Y”), _fold_title, same_chart_claim, comparable_topics, judge_evidence | exact folded title + chart key join | chart line vs history | which posts are comparable; empty -> no judge call | - | KEEP-CLOSED | identity join on exact folded keys (charts_named itself belongs to the music partition) | - | ship (no comparable) | NEW | Structural: canonical_url, _urls, _MENTION_RE, _URL_RE, _CHART_WEEK_RE, _FEATURE_RE / _EDITION_RE title strip, summary_for_dedup, FORMAT_SIGNALS.

utils/engagement.py (1113 lines) and utils/engagement_labels.py (333 lines)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | is_own_laugh (_LAUGH_RE, _OWN_LAUGH_EMOJI) | laugh tokens | message | laugh stat | KEEP + JEV | DONE | Jev Noul per message (#4318), shape rule = stored key + fallback | typesafe_api engagement_labels 68 / 30d (rebuild only) | shape rule | | | is_question (endswith("?")) | shape | message | question stat | KEEP + JEV | DONE | same | same | shape rule | | | is_caps | caps ratio | message | hype stat | KEEP + JEV | DONE | same | same | shape rule | | Structural: _MEE6_TRIGGER_DOMAINS, LAUGH_EMOJI / BANGER_EMOJI reaction sets, attachment_kind ext lists, _URL_RE, _ALL_COUNTERS; engagement_labels mention/emoji/URL scrubbers.

utils/curator.py (546), cogs/curator.py (976), utils/curate.py (150), cogs/curate.py (972), clipboard (utils 66 + cog 714)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | _IMAGE_TOPIC_TERMS, topic_is_image_room, prefers_media, called through judged_image_mode (cogs/curator, cogs/curate) | topic word list | channel name + topic | image vs text judge | HAIKU (B6) | DONE (#3429) | density first; Haiku IMAGE / LINK below it; list only on no verdict | curator_image_room 2 / 30d | density | | | is_curate_post (🔁 / via @), ENDORSING_EMOJIS | own marker | message | seed eligibility | KEEP | KEEP-CLOSED | own repost marker | - | not curate | | Structural: handle / id / shortcode regexes, _RESERVED, _PLATFORM_ALIASES, _VISION_UNFETCHABLE_HOSTS, _human_endorsed, handle caps (.lower() sets), clipboard autocomplete cur not in name.lower(), route_caption slug.

utils/x_mentions.py (595) and cogs/x_mentions.py (1759)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | asks_question, _QUESTION_STARTS, _REQUEST_CUES, mention_asks | first word / cue phrases / ? | mention | fetch parent | SHIPPED A7 | DONE | scan flag first, regex on None and inside the two mechanical bounds | music_mention_scan 450 / 30d | not a question | | | charts_named, _CHART_CUES, _kind_nearest, _ALBUM_WORD_RE, _SONG_WORD_RE | chart cue words | mention | which charts get fact slots | SHIPPED A12 | DONE | charts_from_scan first | same | whole family | | | scan_watched (name_in_text) + confirm_scanned | whole-word watchlist names | mention | subject | KEEP / N1 | DONE | Haiku x_artist_mention on one-word hits; classifier drops lexical hits on a clean empty read | x_artist_mention 35 / 7d | no hit | | | cogs _resolve_subject name_in_text(text, ex_artist) | literal-name grounding of the classifier’s artist | tweet | accept off-watchlist act | KEEP | KEEP-CLOSED | grounding check on a model answer | - | absent | | | cogs is_bare_name, title_in_text (fold_tokens equality) | exact folded tokens | mention vs name / title | reaction vs new subject; song ownership | - | KEEP-CLOSED | exact folded join | - | - | NEW | Structural: _URL_RE, _LEADING_MENTIONS_RE, _RETWEET_*, _BOT_AUTHOR_FLAGS, _REPLY_TO_* keys, _STREAMS_METRIC_RE / _UNITS_METRIC_RE (our own format_reading output), _PLATFORM_KEYS, _GLOBAL_KEYS / _US_KEYS, _is_stale, dedup keys.

utils/x_crosspost.py (1551)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | _media_gate / _clip_gate with _IDENTITY_VERIFIED_ART, _DROP_VERDICTS, _OCCASION_CHECKED_ART | own rung names / model labels | art rung + vision verdict | attach or drop image | KEEP | DONE | the gate is the vision model (media_coherence 1,017 / 7d) | - | drop image | | Structural: OPT_IN_SURFACES, _DEDUP_SURFACES, _MARKET_LINK_HOSTS, _BARE_SOCIAL_RE, markdown strip, _dedup_key, URL path canon, MARKET_ROOM_SURFACES.

X utilities with no semantic site

utils/x_audience.py (_LEADING_HANDLES_RE, _CANON_RE, telemetry), cogs/x_audience.py, utils/x_followers.py (numeric floors), utils/x_poster.py (_URL_RE, x_len), utils/x_fetch.py (_STATUS_RE, _RES_RE, media type), utils/twitterio.py (_credits_exhausted_message, payload keys), utils/x_filter_rules.py, utils/social_search.py (_REL_RE, _PHRASE_STRIP, "verif" in an API field, parse_trending_phrases validates a model list), utils/social_graph.py, utils/social_profile.py, utils/grok_search.py (_X_URL_RE, _X_HOST_RE, exact handle set).

utils/x_game.py (195) and cogs/x_game.py (632)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | _artist_matches (_norm_artist equality) | exact fold | seed vs iTunes row | round pick | KEEP | KEEP-CLOSED | exact folded join | - | skip seed | | | reply grading (grade_guess HARD + jev_grade_and_log) | containment + Jev | X reply | award round | KEEP / #4311 | DONE | Jev decides; code only grants a bare title + artist on no answer | 0 in 30d (no rounds) | no win | | Structural: _ART_TAG, reply_order_key, reply_targets_round, slot math.

utils/reference.py (757)

| symbol | pattern | input | decision | old | new | shape + input | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | _CHART_INTENT (extractor predicate), _TABULAR_CUE | intent regex on query + aspect | /ask tool args | run chart extractor / render tables | HYBRID (B7 not built) | MODEL-HAIKU | one cached classify per lookup: CHART / TABLE / PROSE, both regexes fed as cue examples; regex on None | 0.17 (5 reference_lookup / 30d) | prose page | | | _SUBPAGE_HINT, _ASPECT_SYNONYMS, _aspect_weights, _relevant_subpage | synonym weights over sub-article titles | question vs link titles | which sub-page to fetch | HAIKU (B15 wiki half not changed) | MODEL-HAIKU | classify with the candidate sub-page titles as the closed label set (+ MAIN); synonym table fed | same | main page | | | _TITLE_STOPWORDS, _title_tokens, _title_names_subject | stopword-dropped token overlap | resolved title vs subject | reject page / lead image | KEEP | MODEL-HAIKU | exact fold stays the fast accept; on a REJECT, classify “is TITLE a page about SUBJECT?” YES / NO | same | reject on error | | Structural: _EXTRACTORS, _SOURCES, must_contain in title search pick, _wiki_url, size caps. Owner decision needed: B7 measured these “not worth it” at 5 lookups in 30 days; under the 2026-10-18 rule they are still MODEL. Lowest priority.

Structural only: link platform / host / extension tables, _TWITTER_STATUS_RE, _BLUESKY_POST_RE, [deleted]/[removed], verify_url_alive; watch_link host predicates, _AGE_RE date freshness (fail closed) above the Haiku pickers; url_guardrail URL_RE, _BARE_WWW_RE, _MD_LINK_RE, _MEDIA_PREFIX_RE, allowlist; comments / reddit host routing and over_18; message_search DSL (HAS_KINDS, AUTHOR_TYPES, parse_has, parse_order, parse_match_reply ANSWER protocol), cog mention / snowflake / CDN regexes and exact-then-substring member resolvers; embeddings similarity floor.

utils/wheel.py (309), cogs/wheel.py (1562)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | _NARRATION_PHRASES, _NARRATION_OPENERS, is_narration | phrase lists | wheel lines | drop slice | HAIKU (B4) | OWNED:#4422 | - | - | keep slice | | Structural: _SPLIT_RE, _MARKER_RE, normalize_items, slice caps; cog _CUSTOM_ID_RE, result strings.

cogs/games.py (3862)

| symbol | pattern | input | decision | old | new | shape + input | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | _grade_guess TITLE win (containment, _WIN_RATIO, _CLOSE_RATIO) | tight containment + char ratio | chat message | win / close | HYBRID (#4311) | DONE | Jev names_title + several_titles decide; code on no answer; Haiku judge_song_guess on EASY close | 0 Jev guess calls in 30d (1 game started) | grader | | | _artist_named, _mentions, _ARTIST_FUZZY_RATIO 0.82, _credit_acts, _names_credit, _win_components (+ _NON_ARTIST_CREDIT_WORDS, _title_credit_artists) | whole word, longest token, fuzzy ratio | chat message vs credited acts | artist / feature points; artist-only consolation | KEEP | MODEL-JEV | add a Noul names_act to the SAME Jev request (“does guess name one of the acts in acts, including a nickname or a small misspelling?”); acts = primary credit parts + features + remix credits; the non-artist words fed as “not an act”. 0 added requests. Code rule on no answer. | 0 added | code rule | | | _FEAT_RE, _REMIX_CREDIT_RE, _extract_features | feat / remix credit format | catalog title | candidate guests | KEEP | KEEP-CLOSED | fixed upstream title format; the list of candidates now goes to Jev | - | no credit | | | _DECADE_TITLE_MARKERS, _decade_title_ok | decade tokens | playlist title | accept a fuzzy playlist hit | KEEP | KEEP-CLOSED | closed date-token format | - | reject | | Structural: _MUSIC_HOSTS, is_rap genre enum, _hint_mask, difficulty enums.

cogs/awards.py (968), utils/game_lookup.py, cogs/calendar_view.py, cogs/settings.py (3183), cogs/tune.py

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | awards _quotable | strip emoji / URL / mentions, need one word | highlight | quote or skip | KEEP | KEEP-CLOSED | “has a word” is mechanical | - | skip | | Structural: game_lookup.ASPECTS, _BALANCE_EXCLUDED_SURFACES, _SINGLETON_CHANNEL_KEYS, tune unit labels. settings.py has no text decision site.

utils/starboard.py (286), cogs/starboard.py (1328)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | looks_like_wall_channel | “fame” and (“wall” or “hall”) | channel names | auto-detect the MEE6 wall | KEEP | KEEP-CLOSED (CB-31) | Measured 2026-10-18: 34 real text channels in the 2 guilds, 1 true hit (#wall-of-fame), 0 false hits, 0 misses. The stored channel id wins first anyway. Old plan: Jev Choice over the guild’s readable channel names + NONE (“which channel is the members’ hall-of-fame / wall-of-fame board?”); substring rule on error; a /star channel: pin still wins | <1 (auto-detect only when unpinned) | None | | | cog _find_member_by_name | exact casefold, ambiguous -> None | repost author name | fold repost | KEEP | KEEP-CLOSED | exact join | - | drop | | Structural: STAR_EMOJIS, _FIXER_RE, _DISCRIM_RE, _AVATAR_ID_RE, _JUMP_RE, classify_message, _member_roster.

utils/roles.py, cogs/roles.py, utils/polls.py, cogs/polls.py, utils/discord_info.py, cogs/discord_info.py, cogs/scheduled_events.py, utils/scheduled_events.py, utils/schedule_calendar.py

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | SAFE_PERMISSIONS, parse_permissions | allowlist | mod args | grant / refuse | KEEP | KEEP-PROTECTED | permission allowlist | - | refuse | | | _EVERYONE_TOKENS, _BOTS_TOKENS, cog _SELF_TOKENS | own command tokens | args | resolve target | KEEP | KEEP-CLOSED | closed command vocabulary | - | - | | Structural: _NAMED_COLOURS, mention / snowflake / jump regexes, _DURATION_RE, _CUSTOM_EMOJI_RE, discord_info.ASPECTS, role / event substring resolvers, calendar firehose sets.

cogs/chimein.py (973)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | SKIP_VIBES | vibe label in set | Haiku chimein_score label | skip slot | KEEP | KEEP-CLOSED | policy over a closed model label | 0 (chimein_score 0 calls / 30d) | skip | | Structural: thresholds, voice_present, react emoji pick. No per-message word list exists here, so the “per-message chime-in” hot path has no list to move.

cogs/memory.py (1191), utils/memory_context.py, utils/context_beat.py, utils/attribution.py, utils/nickname_seed.py, utils/users.py

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | memory pipeline (forgotten_names, memory_note / memory_rollup, tag_memory_note) | names / aliases privacy + Sonnet fence | notes | store / skip | KEEP | KEEP-PROTECTED | fence + /forget privacy matching | - | skip | | Structural: ACTIVITY_THRESHOLD, _prepend_legend, context_beat framing, nickname_seed policy sets, users.mention_or_name.

cogs/ask.py (5405)

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | classify_mention_intent label routing (intent in ("discourse","icebreaker")) | model label | mention | route | KEEP | DONE | Sonnet + Jev shadow | mention_intent 44 / 30d | ask | | | player-props filter any(n in p.player.lower() or p.player.lower() in n ...) | substring both ways | model tool arg vs odds-api rows | which props to show | KEEP | MODEL-HAIKU | exact folded name stays the fast accept; for the non-exact residue, classify with the candidate player names as the closed label set (+ NONE) per asked name | <1 (ask 177 / 30d, props a small share) | include on error (today) | | | "us" in query.lower() (spotify_chart top_songs) | tool arg documented as ‘us’ / ‘global’ | model tool arg | US vs global | - | KEEP-CLOSED | model-output protocol (tidy to equality) | - | global | | | game_lookup aspect alias sets, find_image match enum, "stream" in wanted (canonical metric key) | enum aliasing | model tool args | tool routing | KEEP | KEEP-CLOSED | validating model tool args | - | help text | | Structural: _TOOLSIDE_WEB_MODELS, _strip_html, self-mention strip, URL / content-type prefixes.

cogs/commentator.py, cogs/recap.py, cogs/scheduled_poster.py, cogs/event_poster.py, utils/history_gate.py, utils/abuse_tracker.py, utils/reactions.py, utils/tunables.py, experiments.py, utils/permissions.py, utils/gates.py, utils/kill_switch.py, utils/mention_shadow.py

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | commentator _passes_quality | commentate_score floor | model score | ship / skip | KEEP | DONE | model | commentate_score 33 / 30d | exception ships | | | scheduled_poster arbitrated_match + too_short_to_ship | dedup + length | composed post | ship / skip | KEEP | DONE | hybrid (see dedup) | - | - | | | abuse_tracker | Haiku classify_abuse labels | - | mute | KEEP | DONE | model + Jev shadow (#4406) | classify_abuse 134 / 30d | open | | Structural: commentator trigger enums (_MILESTONE_TRIGGERS, _RETRY_TRIGGERS, _ODDS_OPENING_TRIGGERS, _ODDS_SUPPRESSED_TRIGGERS, _CONTEXT_TRIGGERS) and sport maps; recap size guards; tunables / experiments GRADUATED_* / permissions MASTER_USER_IDS / gates / kill_switch enums; mention_shadow is a Jev shadow (model).

cogs/order.py (1507), persona.py, constitution.py, config.py, cogs/help.py, cogs/models.py, cogs/menu_style.py, utils/reference_types.py, utils/wheel_card.py, utils/engagement_window.py

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | order preflight_order verdict mapping | Sonnet label | /order | state | KEEP | KEEP-PROTECTED | protected path | - | reject | | Structural: _FIX_ISSUE_LABELS / _FIX_INTERNAL_LABELS (GitHub labels), order search substring filters, _resolve_member_mention, config _flag, _RETRY_STATUSES. persona / constitution / help / models / menu_style / wheel_card / engagement_window: no sites.

Voice and tag files: utils/voice_signal.py, gif_signal.py, image_signal.py, emoji.py, should_voice.py, voice_cues.py, tts.py, stt.py, voice_ingest.py, discord_voice.py, cogs/voice.py, utils/voice.py, utils/gifs.py

| symbol | pattern | input | decision | old | new | shape | calls/day | fail | NEW? | |—|—|—|—|—|—|—|—|—|—| | image_signal _ASPECT_SYNONYMS | aspect words in the model’s <image aspect=> tag | model output | image aspect | KEEP | KEEP-CLOSED | model-output tag vocabulary | - | default aspect | | Structural: <voice>/<sing>, <gif: q>, <image>/<remix> tag regexes, emoji regexes, tts [ sing tag, voice / sing char caps, gif rendition ladders, stt diarize. voice_cues.py synthesises tones only (no text decision), so the “voice cues” hot path has no list to move.

Measurements

All Axiom APL, dataset tootsies.

  1. claude_api | summarize count() by purpose, 7 days: 27,078 calls. In this partition: topic_dedup 1,726, curate_route 400, curator_pick 331, music_desk_score 2,666, discourse_score 63, music_mention_scan 61, classify_abuse 59, x_artist_mention 35, flourish_check 14, mention_intent 10. Absent: chimein_score, quiet_room_score, song_guess_judge, pplx_found_answer, decline_narration. Decides: the conversation surfaces are near idle, so a per-message Jev move there buys little today.
  2. Same, 30 days, conversation purposes: topic_dedup 13,618; curate_route 13,460; curator_pick 5,914; music_mention_scan 450; discourse_score 195; ask 177; classify_abuse 134; mention_intent 44; x_mention_reply 46; curator_image_room 2. Decides the calls/day column.
  3. typesafe_api | summarize by purpose, 30 days: only engagement_labels 68 (all ok). guess_grade 0, mention_shadow 0 recorded. guess_game_started 1 in 30 days. Decides: the games hot path has no live traffic; the Jev artist-credit Noul adds zero requests.
  4. music_desk_scored phase=shape_reject | summarize by reason, 30 days: career_claim 3,428 (291 distinct previews); attribution_tag 8; every other shape guard 0 (cross_source_comparison, stream_unit_comparison, self_correction, identity_subset, decline_narration, prior_position, row_arithmetic, row_tally). Decides: the owner-ban fences fire about never on the desk, so a Haiku confirm on a hit costs about 0 calls.
  5. music_desk_scored reason=attribution_tag | project post_preview, 30 days: 8 of 8 are genre_board lines naming “HITS’ Top 50” (for example “Iceman by Drake leads hip-hop albums on HITS’ Top 50 …”). The code comment says “HITS Top 50 names a chart and is not in this list”, but _HITS_PUBLISHER matches the possessive HITS’. Decides: _ATTRIBUTION_TAG_RE has a measured false-positive class; it is the top item in batch P3-B1.
  6. music_news_scored shipped=false | summarize by reason, 30 days: unsupported_rank 14, confidence_hedge 6, decline_narration 6, prior_position 5, album_unmarked 5, ungrounded_number 4; wrong_credit 0. Samples: one confidence_hedge drop hedges a non-figure fact (“U2’s 2027 world tour skips Chile after Santiago negotiations reportedly fell through”); the 5 prior_position drops all name a peak run (“after three weeks at No. 1”), which the owner steer covers.
  7. music_alert_scored / music_desk_scored reason metric_word:* or release_title, 30 days: 0. Decides: metric_foreign_word and missing_release_title never dropped a post in 30 days; their value is now in the dedup read (album_metric_named), where the Popstar case (2026-10-04) lost a units settle.
  8. pplx_* | countif(hedged), 7 days: about 4,800 Sonar answers, 59 hedged (music_desk 49 of 389, pop_desk 4, music_news 3 of 3,866, others 3). Decides: a Haiku read on is_hedged HITS only is about 9 calls a day.
  9. x_mentions | summarize by kind, reason, 30 days: scan no_artist 340, scan hit 101, boards shipped 80, reply shipped 71, reply no_facts 6. Decides: the mention lane is low volume and already model-first.
  10. Events, 30 days, partition surfaces: curator_evaluated 5,681, post_dedup 9,479, dedup_overturned 1,774, topic_dedup 1,630, reference_lookup 5, x_reply_draft 154, ask_answered 130, game_match 329, guess_game_started 1, discourse_scored 2. Decides the reference (5 lookups) and tic (about 5 a day) volumes.

Proposed batches

Merged into the Part 0 batch plan.

Notes

Inventory P4: charts, cinema, ops, infra

Read-only pass, 2026-10-18, branch claude/classifier-audit-wave19 = origin/main 4e1b6c0. Old section: docs/CLASSIFIER_AUDIT.md lines 1508-1809. Git history in this checkout is shallow (50 commits), so “NEW” means “not in the 2026-09-18 inventory”, found by reading the code. Most files grew a lot since then (kworb 2517 to 3616 lines, chart_boards 4191 to 4815, luminate 762 to 1142, ops_monitor about 5000 to 6879). Most of the growth is structural or already model-backed.

Counts

file semantic structural KEEP-CLOSED MODEL-HAIKU MODEL-JEV KEEP-PROTECTED DONE OWNED
utils/kworb.py 10 14 5 2 0 0 3 0
utils/riaa.py 7 8 4 1 0 0 2 0
utils/riaa_boards.py 1 5 0 1 0 0 0 0
utils/billboard.py 4 10 3 1 0 0 0 0
utils/luminate.py 6 6 3 0 0 0 3 0
utils/chart_boards.py 7 8 5 1 0 0 1 0
utils/chart_data.py 2 3 0 2 0 0 0 0
utils/chart_records.py 3 1 3 0 0 0 0 0
utils/wikidata.py 6 3 2 2 0 0 2 0
utils/cinema_boards.py 3 2 3 0 0 0 0 0
utils/cinema_numbers.py 4 2 2 0 0 0 0 2
utils/cinema_news_stories.py 1 2 0 1 0 0 0 0
cogs/cinema_desk.py 4 6 2 0 0 0 0 2
cogs/cinema_news.py 3 2 0 0 0 0 0 3
utils/rotten_tomatoes.py 1 5 1 0 0 0 0 0
utils/rt_reconcile.py 2 3 1 0 0 1 0 0
utils/healthcheck.py 1 3 0 0 0 1 0 0
db.py 4 5 1 0 0 3 0 0
manual scripts (riaa_credit_audit, music_drop_manual, versuz_card_manual) 3 2 3 0 0 0 0 0
structural-only files (17, listed below) 0 ~60 0 0 0 0 0 0
Total 72 ~150 38 11 0 5 11 7

No MODEL-JEV site in this partition. Every MODEL site here either needs world knowledge (is this a music act, is this the same recording) or runs at under ~50 calls a day, so Haiku classify with the durable cache is the better shape. No site is a per-message hot path.

Inventory

utils/kworb.py (3616 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
find_video_match (unique branch) _fold_phrase whole-phrase containment of the leg label in a YouTube title; exactly one distinct video = unique=True Kalshi YouTube leg label vs kworb video titles which video’s thumbnail + credit a market card uses; unique=True makes the X vision gate treat it as verified identity (_IDENTITY_VERIFIED_ART) HYBRID, then C11 no change MODEL-HAIKU on the non-equal residue Keep fold-EQUAL (label == title fold) and the video-id suffix (#4127) as exact accepts. A containment-only unique hit asks classify SAME / DIFFERENT with the leg label, the video title, the channel, and the event title. Cache 14 days per (label fold, video_id). ~1 (35 market_subject source=youtube_chart in 30d, all unique; a share are fold-equal) DIFFERENT or error: return the row with unique=False, so the vision gate keeps authority (not a silent drop) no
find_video_match qualifier drop (_BASE_LABEL_RE) drop a trailing (...) then require one distinct video same same, unique=True HYBRID MODEL-HAIKU (same call as above) Same SAME / DIFFERENT question; the dropped qualifier (“Remix”) is part of the input. <1 same no
find_video_match (non-unique pick) most-viewed of several containment hits, unique=False same thumbnail pick HYBRID DONE The vision media gate decides (nominate-only). 0 picks in 30d. 0 gate keeps authority no
_artist_credits / _ARTIST_CREDIT_SPLIT / strict find_chart_entry, _find_by_artist, find_song_entry separator split (&, feat, ft, with, vs) + exact fold equality artist credit vs ranking rows which row is the act KEEP KEEP-CLOSED Separator parse of a credit format + exact folded key. - no row no
fragment_match / confirm_act / all_parts_ranked (K2) part-of-credit match goes to Haiku ONE / COLLAB unranked multi-act credit keep or refuse the row - DONE classify purpose kworb_act_check. 1 event in 30d. ~0 no row NEW since 09-18 (#4170/#4277)
_REQUEST_LEAD_SPLIT lead widening cut the request credit at its first , & ` x ` feat request credit the lead may match a row credit - KEEP-CLOSED Separator parse; the row must ALSO carry the exact same title. - no row NEW
loose mode (strict=False) of find_chart_entry, _find_by_artist either-way folded containment artist row KEEP (Tier D) KEEP-CLOSED (DEAD) No production caller passes strict=False (grep of cogs/, utils/, bot.py). Delete the branch. 0 - no
_strip_feature [(\[](w/|feat|ft|with)...$ + title_forms.bare_title title identity fold KEEP KEEP-CLOSED Feature-marker format. - - changed (#4172)
is_functional_audio regex branch (_FUNCTIONAL_AUDIO_RE) ~80-word functional-audio phrase list artist name on the streams / listeners ranking (~1000 rows a fetch) DROP the row alone, with no model check HYBRID MODEL-HAIKU on the drop branch only Send each DISTINCT regex-positive name to the existing functional_audio_check (cached 30d), with the matched phrase. The regex stays the bulk filter. ~0.1 (2 distinct names in 30d: “White Noise Radiance”, “Rain Sounds”; 2685 artist_filter reason=functional_audio events re-drop the same two) model says MUSIC: keep the row; error: drop (today’s rule) no
screen_functional_top (B9) Haiku on the regex-negative rendered top-N rendered rows drop - DONE functional_audio, 51 calls in 30d, 0 model drops. ~2 regex verdict no
_UPLOAD_TAG_RE + trending_credit (official|lyric|letra|audio|video|visualizer|mv|hd|4k...) bracket strip; " - Topic" channel; first " - " split YouTube upload title + channel display title and credit on the US trending board - KEEP-CLOSED YouTube upload-tag convention + Topic-channel naming; a miss leaves a tag on the card, empty artist when unsure. - absent artist NEW (#3768)

Structural: HTML row/cell/link regexes, _PEAK_DAYS_RE, _PAGE_DATE_RE, _VIDEO_ID_SUFFIX_RE + label_video_id / display_leg(s) (NEW #4127/#4342), _one_track track-id anchor (NEW #4451), find_artist_song track id then song_key (NEW), */^ feature marks, _US_VIDEO_CHART_SIZES, _BLOCKED_STATUSES, "anglo" in path, _PER_ENTITY_QUERIES, “total”/”global” header rows, stream-shape gate (numbers).

utils/riaa.py (2465 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_NOT_AN_ACT in own_credit 11 filler cells (soundtrack, various, cast, original broadway cast …) RIAA artist cell that names one act is the cell an act that can be named on a board HYBRID, C11 measured no change MODEL-HAIKU classify ACT / NOT_ACT on cells NOT in the list and that would rank on a board; the 11 entries go in the prompt as examples of NOT_ACT. Durable cache 90 days per cell. The exact list stays the fast NOT_ACT. ~731 cells once (C11 count), then ~1 a day (new feed cells) error: today’s rule (named as an act) no
_TITLE_FEATURE_RE, _FEATURE_AND_RE / _feature_guests feature markers; bare “and” split only when every piece resolves award title guest credits KEEP KEEP-CLOSED Marker format; each piece must exactly resolve to a listed act. - uncredited no
_PERSONA_AS_RE \s+as\s+ in the artist cell artist cell credit both halves KEEP KEEP-CLOSED RIAA cell convention (“X AS Y”); halves go through credit_matches. - - no
_CURATED_OMITTED_CREDITS + generated JSON exact (artist, title_identity) keys award row add guests KEEP KEEP-CLOSED Owner-curated exact keys, built by a manual audit. - - no
title_identity + resolve_title_collisions feature-strip fold; Haiku on base-equal / whole-different groups titles dedupe key KEEP DONE riaa_same_release. 0 calls in 30d. ~0 merge (old rule) NEW since 09-18 (#3959)
display_title recasing _INITIALS_RE, _SMALL_WORDS, _ROMAN_RE … ALL-CAPS cell card text HYBRID DONE riaa_recase per card (B16). 1 call in 30d. <1 rules no
display_credit joins _CREDIT_JOIN_RE, _CREDIT_SLASH_RE credit joiner text KEEP KEEP-CLOSED Separator format, cosmetic. - - no

Structural: _ROW_RE, _BADGE_RE, _TD_RE, _TALLY_ROW_RE, _ARTIST_ROW_RE, _TIMELINE_BLOCK_RE, _LEVEL_LABEL_RE, _LATIN_LABEL_RE, page-shape guards.

utils/riaa_boards.py (1357 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_bare_title (_BRACKET_RE, _IDENTITY_BRACKET_RE, _CREDIT_BRACKET_RE) keep a bracket only if it holds ‘s Version / remix / mix / live / acoustic / part / interlude / reprise / intro / outro title on a public RIAA card drop or keep the bracket; a miss prints “Love Story” for “Love Story (Taylor’s Version)” KEEP, fixed #3455 MODEL-HAIKU (fold, 0 added calls) Fold into the existing riaa_recase call in recase_board: return the display title with the identity brackets kept. The word list goes in the prompt as the examples of KEEP; feature / soundtrack / edition brackets as examples of DROP. Validate: output must be the input with whole brackets removed. same as riaa_recase (<1) invalid answer or error: _bare_title rule changed (#3455)

Structural: _fold, chart-key prefixes, _CERT_PREFIX, fmt == "ALBUM", _MONTHS, _WHOLE_RANKING_PREFIXES.

utils/billboard.py (1914 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
chart_context needle in _norm(title) or needle in _norm(artist), best rank wins the desk subject vs every row of every held chart which chart line (“#N on Hot 100, up 3”) goes into a market / prediction / music-desk compose blob KEEP MODEL-HAIKU on the containment residue Step 1 (deterministic, first): pass the structured artist + release (the luminate subject reads “Release – Artist”, which _norm never contains, so most market calls get no line today) and use credit_matches + exact title fold as the accept. Step 2: a containment-only hit asks classify SAME / DIFFERENT with the subject, the row title + artist. Cache per (subject, chart, week). upper bound ~380 (music_desk_score 2666 in 7d is the compose volume); after exact accepts and the weekly cache, est. under 20 error or DIFFERENT: no chart line (absent) no
_lead_credit + _slug_is_the_lead / _history_slug head segment + slug-word compare credit vs artist slug history slug KEEP + fix KEEP-CLOSED Fixed by #3451 (slug must be the cut lead’s own page). - no slug changed
find() title containment, best rank wire title chart row HYBRID, C11 KEEP-CLOSED (DEAD) No production caller (C11 and today’s grep). Delete. 0 - no
rank_of exact _norm title + artist title rank KEEP KEEP-CLOSED Exact fold. - None no

Structural: HTML regexes, _STAT_LABELS, _REENTRY_FRAGMENTS badge parse, _KIND_WEIGHT, TOP_MOVE_KINDS, _FALL_KINDS, _KIND_EYEBROW, _slug_key, _BLOCKED_STATUSES.

utils/luminate.py (1142 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
ALBUM_METRICS / metric_cues / metric_from_text Kalshi metric words in registry order Kalshi event title metric label HYBRID DONE B13: one clear cue = fast path (Kalshi’s fixed metric vocabulary); none or two cues go to luminate_subject. see Notes cue result no
_SUBJECT_RE, _METRIC_TAIL_RE, enrich_event_title Kalshi title template “Will X by Y have …” leg title release / artist HYBRID DONE Template parse; no parse or two acts go to luminate_subject. see Notes cue result no
_LEG_TWO_ACT_RE, _TITLE_VS_RE “have … than … by”, “vs” leg / event title send to the model; credit no act - DONE Nominates the residue; the outcome (no act credited) is the absent direction. Kalshi’s own template words. - no credit NEW (#4211)
_KALSHI_CATEGORY_LABELS exact “music, fashion, film” release slot collapse to artist KEEP KEEP-CLOSED Exact upstream category. - - no
release_stage ticker substrings, _WORLDWIDE_STREAM_SERIES, stream_scope ticker family names series ticker stage; “worldwide” word KEEP KEEP-CLOSED Ticker schema. - ”” (no word) partly NEW
"stream" in reading.metric our own label metric streams reading KEEP KEEP-CLOSED Our enum. - - no

Structural: _NUM_RE, _SUFFIX, _PERIOD_RE + date parses, "lumin" settlement source.

utils/chart_boards.py (4815 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
recovery_candidates zero-overlap branch a tracklist title that shares no 3+ char token with any unused kworb row gets no candidates album track title the track is left out of the album’s stream total; the model never sees it KEEP MODEL-HAIKU (low) Fold into the existing album_track_match call: a zero-overlap title gets the top unused rows by total as candidates. Same recovered_total ceiling. ~0 (album_track_match had 0 calls in 30d) absent (undercount) no
_album_track_fold + album_track_match recovery fold, prefilter, Haiku pick album tracks which rows are the album’s KEEP DONE Reference HYBRID. 0 in 30d fail closed no
APPLE_GENRE_GROUPS, DEEZER_GENRE_GROUPS, confirmed_genre_group exact upstream genre names published genre tags genre board membership KEEP KEEP-CLOSED Upstream API fields with a fixed vocabulary; two sources must agree. - absent no
_VERSION_SUFFIX_RE / _version_stripped_fold ” - From …”, “ - Remastered” Spotify title same recording - KEEP-CLOSED Spotify’s fixed suffix format. - - NEW
_release_identity / _act_leads_credit fold_tokens prefix ending on a connector credit lead act key - KEEP-CLOSED Exact token prefix with a boundary. - - NEW (#3657)
_match_hits, song_total_streams etc. exact keys + credit_matches rows join KEEP KEEP-CLOSED Exact fold (credit_matches is artist_watch’s, OWNED:W19-C1 for its aliases). - - no
_label_case <=4-char caps kept label casing KEEP KEEP-CLOSED Cosmetic. - - no

Structural: _RANK_NOTABILITY, _SECONDARY_CHARTS, _GENRE_LABELS, GENRE_MEMBERSHIP, kind frozensets, _CREDIT_EXAMPLES / _PRINTED_STEERS / _PLATFORM_STEERS / _PROJ_STEERS / _WATCH_COLUMN_FENCE (prompt text, not decisions), _PERIOD_END_RE.

utils/chart_data.py (424 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
detect_chart “billboard 200”, \buk\b, “canad”, “hot 100”, “album” /ask reference query + aspect which Wikipedia chart table is fetched HAIKU, B7 not built MODEL-HAIKU One classify over the closed key set (hot100 / billboard200 / uk_singles / uk_albums / canada), the cue words in the prompt as examples. Shares the call with the wikidata intent below (batch P4-E). ~0 (4 reference_lookup in 30d, all source=Wikipedia, 0 reached the chart or Wikidata source) error: cue rule no
render_tables keyword row filter any(k in row.lower()) relevance table rows vs query words which rows the /ask model sees KEEP (structural) MODEL-HAIKU (or remove) Simplest: hand the /ask model the capped table and drop the filter, so the calling model decides. ~0 full table no

Structural: wikitable parser, _clean_title, _is_chart_col, _MAX_PEAK.

utils/chart_records.py (1338 lines)
symbol pattern (short) input decision old verdict new verdict shape calls/day fail direction NEW?
RECORDS curated all-time table - record claims KEEP KEEP-CLOSED Curated facts, exact keys. - - no
_same_song, _credit_corroboration_set, _lead_act exact fold; artist_watch.first_party title / credit record identity KEEP KEEP-CLOSED Exact folds; _lead_act fixed #4265. - - changed
_record_phrase / _CLOSE_MARGINS number margins numbers phrasing KEEP KEEP-CLOSED Numbers. - - no
utils/wikidata.py (716 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_FACTOID_INTENT / matches born, birth, how old, age, died, founded, inception, what year … /ask reference query + aspect route to the Wikidata factoid source HYBRID, B7 not built MODEL-HAIKU Same call as detect_chart: add the label set FACTOID_BIRTH / FACTOID_DEATH / FACTOID_FOUNDING / NONE; the regex words go in as examples. ~0 (see detect_chart) error: regex no
want_founding (in lookup) founded / founding / inception / established aspect founding vs birth render KEEP MODEL-HAIKU Same call (FACTOID_FOUNDING label). ~0 regex no
_MUSIC_DESCRIPTION / _WORK_DESCRIPTION musician / band … ; album / film / tour … Wikidata hit description ORDER of claims reads; after the first act, read only a music-looking second candidate KEEP DONE Ordering only; is_music_act (Q-ids) decides and _pick_namesake (Haiku wiki_namesake) picks among acts. The second-act admission is a request-budget gate on 0 cards / 14d (#3319). 0 first act changed (#3319)
artist_portraits + _pick_namesake Haiku ONE / TWO / THREE / UNCLEAR same-name acts which portrait - DONE wiki_namesake, 0 calls in 30d. 0 no photo NEW (#3319)
_MUSIC_OCCUPATIONS, _MUSIC_GROUP_CLASSES, is_music_act Wikidata Q-ids claims is a music act KEEP KEEP-CLOSED Structured ids (C12 measured). - not an act no
_norm_act_name / _hit_name_candidates exact fold label vs name candidate KEEP KEEP-CLOSED Exact. - - no
utils/cinema_boards.py (856 lines)
symbol pattern (short) input decision old verdict new verdict shape calls/day fail direction NEW?
_POSITION_CLAIM_RE / strip_position_claims, _ROW_POSITION_RE, _PACKED_SCORE_RE “#N”, “number N”, “ranks N” model output strip rank figures for the number check KEEP KEEP-CLOSED Number check. - - no
_debut_lead weeks / gross thresholds BOM numbers debut framing KEEP KEEP-CLOSED Numbers. - - no
short_studio / short_director (_STUDIO_SUFFIXES, _NAME_PARTICLES, _NAME_SUFFIXES) suffix / particle lists studio / director name display shortening KEEP KEEP-CLOSED Display only; never decides inclusion. - full name no
utils/cinema_numbers.py (1044 lines) – OWNED:#4412 (coordinator update: result-verb lists, PR #4513)
symbol new verdict note
_clean_news (“no notable” / “no major” / “nothing “ on a short Perplexity answer) OWNED:#4412 C11: 0 events from the three news lanes. Not batched.
result-verb lists (PR #4513, not yet on main) OWNED:#4412 Not batched.
_is_new_opener / _notable_holdover KEEP-CLOSED Numbers.
norm_subject KEEP-CLOSED Exact fold.
utils/cinema_news_stories.py (252 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
story_signature / dedup_key (_STOPWORDS, first 5 content tokens) stopword-dropped token prefix wire post text same moment: a match DROPS the story (durable cinema_news_events key, and the per-slot used set) KEEP MODEL-HAIKU Keep exact signature equality as the trigger; a hit against a RECORDED key asks the existing dedup arbiter (dedup.topic_duplicate / arbitrated_match, Haiku) with both wire texts, the stopword list is not needed as input. Also emit an event on a signature drop (none today, so a wrong merge is silent). <1 (19 cinema_news_posted in 30d; collisions are a fraction of candidates) error: drop (today’s rule) no

Structural: _URL_RE, _HANDLE_RE, _TOKEN_RE, _MIN_HEADLINE_CHARS.

cogs/cinema_desk.py (1716 lines)
symbol new verdict note
_board_unshippable, ungrounded_numbers KEEP-CLOSED Number checks behind cinema_desk_score (40 calls / 30d).
budget lookup tmdb.search_movies(title, year) + title_matches + year KEEP-CLOSED Tier D fix landed (exact title + year).
has_self_correction (L1417) OWNED:#4422  
check_source_qualifier, qualifier_trim.restates_cut OWNED:#4412  

Structural: _SUBJECT_DEDUP_KINDS, _NEWS_KINDS, _POSTER_CARD_KINDS, _CARDED_KINDS, _QUALIFIER_CHECKED_KINDS, _CINEMA_CARD_SOURCE, _SOURCE_LABEL.

cogs/cinema_news.py (811 lines)
symbol new verdict note
drop_self_correction OWNED:#4422  
retrospective_note / frames_as_past / retrospective_frame OWNED:W19-B3  
check_source_qualifier / restates_cut OWNED:#4412  

Structural: _VISION_HOSTS, is_source_photo. news_age.is_stale_recap and wire_lanes are called here but live outside this partition.

utils/rotten_tomatoes.py (403), utils/rt_reconcile.py (328)
symbol pattern decision old verdict new verdict note
normalize_title + title_matches + year_matches exact alnum fold, year slack 1 same film KEEP KEEP-CLOSED Exact identity with year.
rt_reconcile.threshold_from_label, RTCall.verdict / cleared “above / over / more than / at least N”; distance bands Kalshi strike and the hit / miss verdict KEEP KEEP-PROTECTED Price threshold math (settlement class).
rt_reconcile.is_rt_event, film_from_event KXRT prefix; “rotten tomatoes score” tail series membership, film name KEEP KEEP-CLOSED Ticker + Kalshi template.
utils/healthcheck.py (443 lines)
symbol new verdict note
_is_read_only KEEP-PROTECTED Permission allowlist.

Structural: Bearer strip, X webhook auth, debug routes (NEW /debug/clip, /debug/usage).

db.py (10396 lines)
symbol new verdict note
bookie_bet_outcome + _canonical_name KEEP-PROTECTED Bookie payout.
side_label void-to-won correction KEEP-PROTECTED Settlement.
alias_index(exclude_nicks) / aliases_for_text KEEP-PROTECTED names / aliases matching.
_MEMORY_QUERY_TOKEN / _or_search_terms KEEP-CLOSED Query syntax rewrite for websearch_to_tsquery.

Structural: _FACT_CLAIM_SURFACES, POST_HISTORY_RETENTION_HOURS, sql_op labels, jsonb codec, FTS SQL.

Manual scripts
symbol new verdict note
scripts/riaa_credit_audit.py _SAME_RECORDING, _NOT_AN_ACT_NAME, accept() KEEP-CLOSED (manual) A human reads every output before it enters the curated JSON.
scripts/music_drop_manual.py _VISUALISER_RE, _LYRIC_RE KEEP-CLOSED (manual) The music-drop skill; the owner reads the result.
scripts/versuz_card_manual.py _SCORE_RE, _LOOSE_FEAT_RE, _SUFFIX_RE, token overlap KEEP-CLOSED (manual) The versuz-card skill; the owner reads the card.

scripts/artist_sweep.py (NEW, 271 lines) and scripts/called_shot_scorecard.py: no semantic site (they call riaa_boards / called_shot).

Structural-only files (one line each)

Measurements

All Axiom, dataset tootsies.

# query (short) window result what it decides
1 claude_api count by purpose, purposes of this partition 30d functional_audio 51, cinema_desk_score 40, cinema_news_score 34, riaa_recase 1; kworb_act_check, riaa_same_release, luminate_subject, wiki_namesake, album_track_match: 0 Volume of the shipped model rungs. No MODEL-JEV case: nothing here is high-volume. See Notes on the zero rows.
2 artist_filter by reason, make_set(detail.dropped) 30d reason functional_audio: 2685 events, distinct names {“White Noise Radiance, Rain Sounds”}; reason functional_audio_model: 0 The regex drop branch fired on 2 distinct names, both right. A model confirm costs ~2 cached calls. Low priority (P4-D).
3 market_subject by source, source startswith 'youtube' 30d youtube_chart 35, youtube_chart_pick 0 find_video_match: 0 non-unique picks; ~1 unique leg a day reaches the card (P4-B).
4 reference_lookup by source 30d 4, all Wikipedia detect_chart, _FACTOID_INTENT, render_tables route ~0 traffic (P4-E).
5 cinema_news_posted / cinema_desk_posted 30d 19 / 22 Volume ceiling for story_signature (P4-F).
6 kworb_act_check / luminate_subject events 30d 1 / 0 Confirms #1: the K2 and B13 rungs barely run in production.
7 claude_api music_desk_score (and related) 7d music_desk_score 2666, market_drop_history 244 Upper bound for chart_context callers (P4-A).

Proposed batches

Merged into the Part 0 batch plan.

Notes

  1. Shipped model rungs in this partition show (almost) no production calls in 30 days. luminate_subject (B13, #3436): 0 claude_api calls and 0 luminate_subject events, while 968 market_subject events ran and the B13 dry run sent 50 of 121 live events to the model. kworb_act_check (K2): 0 calls, 1 event. riaa_same_release, wiki_namesake, album_track_match: 0 calls. Some may be idle for a real reason (durable caches warmed by the dry run, the trigger shape did not occur, #3319 measured ~0/day). But luminate_subject at 0 against the dry-run rate is a candidate silent failure: check that music_markets._checked_subject (L2083) is reached in production and that bind_subject_cache ran. NOT verified in this pass; worth a follow-up issue.
  2. Dead branches to delete, not convert: billboard.BillboardClient.find() (no production caller) and the strict=False loose mode of kworb.find_chart_entry / _find_by_artist / find_song_entry (no caller passes it).
  3. billboard.chart_context likely misses most market subjects. The luminate desk subject is “Release – Artist” (luminate._subject), and _norm keeps the two spaces, so the needle is almost never a substring of one row title or artist. The context line the #1981 steer asked for may be absent on most market reads. Not measured (no event). This is a deterministic bug before it is a model question (P4-A step 1). The old audit missed this site’s fuzzy shape (it listed it as KEEP).
  4. story_signature drops are silent. No event records a cinema story dropped as a duplicate, so a wrong merge cannot be counted. The fix is an event (P4-F).
  5. Old-audit items now resolved: Tier D _lead_credit (#3451), TMDB budget lookup (title + year check in cinema_desk), _BRACKET_RE (#3455), find_chart_entry strict default (#3456), chart_records._lead_act (#4265).
  6. Display-only word lists kept closed on purpose: cinema_boards.short_studio / short_director, kworb._UPLOAD_TAG_RE, riaa.display_credit. They change how a name prints, never whether a row ships. riaa_boards._bare_title is the exception (it can remove a recording’s identity), so it is in P4-C.
  7. cinema_numbers.py: whole file OWNED:#4412 per the coordinator update (result-verb lists, PR #4513, not yet on main). Listed, not batched.
  8. Infra / ops: no semantic site. ops_monitor classifies only our own emitted enums and log lines; its model judging runs in the GitHub workflow, not here.

Inventory P5: the 16 remaining files

Repo state: 4e1b6c0 (origin/main). Read-only pass. Old audit section: docs/CLASSIFIER_AUDIT.md lines 1811-2067. Worklist items that already shipped in this scope: A10 (#3422, discourse rubric “shows its working”), B6 (#3429, curator image room), B15 (#3430, TMDB film pick), C5 (#3443, house genre), C6 (#3444, chart_ages confirm), C7 (#3445, reissue check), #4235 (album-chart match), C11 (#3449, measured looks_like_sports: no change). B7 (reference router) was NOT built.

The clone is shallow (50 commits), so “NEW” is decided by comparing each site to the old audit text, not by git log.

Counts

file semantic structural KEEP-CLOSED MODEL-HAIKU MODEL-JEV KEEP-PROTECTED DONE OWNED
cogs/artist_curator.py 1 3 0 0 0 0 1 0
cogs/curate.py 5 4 3 0 0 0 2 0
cogs/discourse.py 13 5 6 4 0 0 1 2
cogs/music_releases.py 7 4 4 2 0 0 1 0
utils/awards.py 0 4 0 0 0 0 0 0
utils/chart_ages.py 7 5 6 0 0 0 1 0
utils/chart_standings.py 7 2 6 1 0 0 0 0
utils/feed_status.py 0 2 0 0 0 0 0 0
utils/filler_solver.py 0 3 0 0 0 0 0 0
utils/market_art.py 1 2 1 0 0 0 0 0
utils/music_policy.py 5 2 4 0 0 0 1 0
utils/openai_chat.py 5 1 5 0 0 0 0 0
utils/reference_movie.py 2 3 0 1 0 0 1 0
utils/release_age.py 2 4 1 1 0 0 0 0
utils/subject_image.py 2 4 1 0 0 0 1 0
utils/the_odds_api.py 0 6 0 0 0 0 0 0
total 57 54 37 9 0 0 9 2

No KEEP-PROTECTED site is in this partition. No settlement math, constitution, fence, permission allowlist, /forget matching or preflight path lives in these 16 files.

Inventory

cogs/artist_curator.py (401 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
claude.pick_curator Haiku vision pick candidate frames + captions + room topic which post ships, or none KEEP (model) DONE already a model judge curator_pick 5,914 / 30d (shared with curate) none_fit / pick_error: skip no

Structural: filter_candidates(require_media=True) + is_fresh (timestamp window), channel.topic or parent.topic (attribute fallback to _DEFAULT_TOPIC), _PLATFORMS (own provider table).

cogs/curate.py (972 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_compose_caption: artist.lower() == source.lower() exact handle fold upstream X handles caption a repost of a distinct act, or bare link KEEP KEEP-CLOSED exact identity join on a handle (closed format) - equal: bare link no
_compose_caption: "\n" in cap or link_count or visible_len > _CAPTION_MAX shape gate model caption drop caption KEEP KEEP-CLOSED count / length on model output - drop caption no
judged_image_mode(...) (was prefers_media + _IMAGE_TOPIC_TERMS) density, then cached Haiku IMAGE/LINK room name, parent, topic, 6 samples image vs text judge + prompt HYBRID DONE (B6 #3429) classify, purpose curator_image_room; the topic list is the model-error fallback 2 / 30d (all six live rooms decided by density) model error: old density + topic list yes (#3429)
home.get(p.external_id) == channel_id routed-home equality map built by claude.route_curator which room gets the post KEEP DONE model decides (curate_route 13,460 / 30d) ~450 route error: fail open no
is_empty_response (L324, L754) EMPTY sentinel model output skip vs ship KEEP KEEP-CLOSED protocol sentinel - sentinel: skip no

Structural: _fetchable (host allow-list _VISION_HOSTS), _platform_label (host substrings over canonical_host), parse_external_id tweet-id dedup, reason not in ("none_fit","no_candidates") (own reason enum).

cogs/discourse.py (1999 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_market_routing_query: sportsy_channel = classify_intent(name + topic) (markets._SPORTS_PATTERNS, _PM_PATTERNS, _WILL_X_BY_RE) substring list + regex channel name + topic on a quiet slot (no category, no convo): fetch markets at all KEEP (prefilter) MODEL-HAIKU (miss branch only) classify labels SPORTS / PREDICTION_MARKET / OTHER on name + topic; _SPORTS_PATTERNS and _PM_PATTERNS fed as examples; cache key = name+topic, ttl 7d <1 (one per channel per name/topic change; discourse is mostly muted, see Measurements) model error: today’s keyword rule no
icebreaker _market_line: looks_like_sports(name + topic) (classify_intent == "sports" or \bsport) substring list + regex channel name + topic whether a betting line may feed the icebreaker HYBRID; C11 measured, no change MODEL-HAIKU (miss branch; hit branch is nominate: markets.get_context runs the Haiku market_intent) the SAME cached label as the row above (SPORTS -> allow) <1 (1 icebreaker in 30d) model error: today’s rule no
drop_self_correction at news lane (L593), retry (L720), trending lane (L1192) _SELF_CORRECTION_RE model take drop take -> fallback KEEP; A10 moved the judgment into discourse_score OWNED:#4422 - - drop no
icebreaker has_self_correction(line) (L1743) _SELF_CORRECTION_RE model opener drop opener (no score judge on this lane) (not listed) OWNED:#4422 - - drop yes (missed by old audit; #2755 sweep)
icebreaker arbitrated_match(..., catch=False) (L1773) text ratio / shared run / same link; Haiku only overturns a hit opener vs 30 days of own openers drop a repeat opener HYBRID (already) MODEL-HAIKU (miss branch) set catch=True: claude.topic_duplicate (purpose topic_dedup) reads the opener vs the recent openers; the mechanical signals stay as the hit path <1 catch: fail open (no verdict = ship, as today) no
scheduled take arbitrated_match(..., catch=False) (L1940) same take vs last 20 discourse posts drop a repeat take HYBRID (already) MODEL-HAIKU (miss branch) same, catch=True <1 same no
trending canonical_url(c[3]) not in posted_links canonical URL fold clip links vs 21d of own posts drop already-posted clips KEEP KEEP-CLOSED exact URL identity - DB error: no dedup (fail open) no
picked = next(c[3] for c in clips if c[3] in line) URL substring model take telemetry only KEEP KEEP-CLOSED exact URL in text - - no
_perplexity_phrases -> parse_trending_phrases line-shape regex Perplexity output which phrases get searched KEEP KEEP-CLOSED protocol parse of a line-per-phrase answer - junk line: wasted search no
_native_x_phrases -> claude.filter_trending_topics Haiku match of X trends to room X trends + name + topic which trends fit (noted) DONE model decides (trending_filter 1 / 30d) <1 Haiku error: top trends unfiltered no
_trending_search_phrases._add: p.lower() dedup exact fold phrases drop a repeat phrase KEEP KEEP-CLOSED exact fold - - no
t != "(no caption)" own sentinel own placeholder exclude from Grok prompt KEEP KEEP-CLOSED own enum - - no
is_empty_response (L593, L713, L1182, L1732, L1922) EMPTY sentinel model output decline KEEP KEEP-CLOSED protocol sentinel - skip no

Structural: _trending_clip_url (host allow-lists + YouTube id regex), x_fetch.x_tweet_id (_STATUS_RE), _parse_retry_after_seconds (header parse), filter_recent(keep_undated=False) (date parse), u.startswith("data:") (URL scheme).

cogs/music_releases.py (1867 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_REISSUE_RE in _reissue_keys (\d+ year, anniversary, reissue, remaster, super deluxe, expanded edition) regex word list Apple album title fast YES: omit the first-night stat, drop from the first-week card HYBRID -> C7 kept it as the cheap yes MODEL-HAIKU (fast-yes branch) send the hit to the existing claude.release_is_reissue (REISSUE / NEW, purpose release_reissue, cached per title); feed the matched word as a hint (“title carries ‘anniversary’”) ~2 (10 album_total reason=reissue in 30d) model error / no verdict: omit the stat (today’s direction) yes: “super deluxe” and “expanded edition” were added after C7 (Rubber Soul, 2026-10-06)
_REISSUE_ASK_RE (deluxe, edition, version, live, expanded) regex word list Apple album title hit: ask Haiku; miss: NEW, no call (C7) hit DONE; MODEL-HAIKU for the MISS branch ask release_is_reissue for every roster title the stat / first-week card reads (_STAT_SCAN 4 + first-week roster up to FIRST_WEEK_SCAN 20); pass the release date and the release_version_kind label already fetched for that title ~10-20 new titles/day, then cached (est. from code: roster size x 1-2 slots, release_reissue 22 calls in 3 days) model error on a title with no regex word: NEW (today); on a regex word: omit yes (#3445)
_standout_stats: song_date != rel.released date equality iTunes date vs feed date credit a charting track to this drop KEEP KEEP-CLOSED exact date - no stat no
_standout_stats: entry.title.casefold() == rel.title.casefold() exact fold kworb vs Apple title show song name or blank KEEP KEEP-CLOSED exact fold - name shown no
kworb.album_chart_entry(..., known=known) strict credit + exact folded title kworb vs Apple Top Drops rank KEEP KEEP-CLOSED exact join (helper in utils/kworb.py) - left off no
_version_verdicts -> claude.release_version_kind Haiku new / recut / edition (artist, title) board roster (not present) DONE model decides (release_version 195 calls in 3 days); the regex in utils/release_board.py decides only a None row ~65 None: regex decides that row yes (#3441)
label == "new music friday" own label own plan label caption text KEEP KEEP-CLOSED own enum - - no

Structural: _CHART_DEBUT_DAYS days window, rb._roster_key(rel) in reissues (own key set), f"first:{mark}" in self._delivered_keys() (own key), rb.outside_first_week (Spotify date window).

utils/awards.py (288 lines)

No semantic site. Structural: metric.split("+") on own metric keys, "{month}" in t (own template), _FIELD_LABEL / _BY_KEY lookups (own keys), recent winner frozenset (own ids).

utils/chart_ages.py (647 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_matching_pick containment (non-exact) + confirm_pick fold + containment either way MB release-group title + credit vs Billboard row the row’s original date (oldest-songs board, RIAA plaques card) HYBRID DONE (C6 #3444) an exact title+credit pick makes no call; any other pick goes to Haiku SAME / DIFFERENT (purpose chart_age_check) <1 (6 in 30d) DIFFERENT: undated + cached miss; error: undated, not cached no
_matching_pick(exact=True): _bare_title equality + artist_watch.credit_matches exact fold after bracket strip same debut-gate date (release_age MusicBrainz rung, no model client) (not present) KEEP-CLOSED (exact fold). The alias half of credit_matches is OWNED:W19-C1 - - no match: next rung yes (#2958)
_NOT_THE_RECORD / _NOT_THE_ALBUM on secondary-types set of MB type names MB secondary-types drop compilation / live / remix / DJ-mix / demo groups (not present) KEEP-CLOSED (upstream fixed vocabulary) - - group skipped yes (#3483, #4235)
_self_titled title fold == artist fold row use exact match + official filter (not present) KEEP-CLOSED (exact fold) - - - yes (#4295)
_self_titled_pick official status, no secondary type, one year per primary type MB JSON fields candidate set for a self-titled row (not present) KEEP-CLOSED (structural fields + year compare) - - conflict: undated yes (#4295)
_bare_search_title bracket strip row title second MB search (not present) KEEP-CLOSED (structural) - - - yes (#4371)
confirm_pick answer parse label == "SAME" closed-label parser Haiku answer keep date - KEEP-CLOSED (protocol) - - not SAME: undated yes (#3444)

Structural: _lucene_escape, _year_of, _credit_name (joinphrase rebuild), _BRACKET_RE, earliest-year / most-precise-date pick.

utils/chart_standings.py (1226 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
credit_parties(row.artist, known) (artist_watch._CONNECTORS) connector split, corroborated by exact known keys Billboard credit which acts an entry counts for KEEP KEEP-CLOSED (a split stands only on an exact folded key in known) - - under-split: collab partner loses an entry no
is_placeholder_credit(party) (artist_watch._PLACEHOLDER_CREDITS: soundtrack, various artists, original cast, karaoke, …) exact folded word list Billboard credit party drop a non-act from the census / catalog / debut boards KEEP MODEL-HAIKU (the miss branch: an unlisted stand-in, e.g. “Cast of Wicked”, counts as an act alone) classify ACT / PLACEHOLDER on the credit, _PLACEHOLDER_CREDITS fed as examples; scope it to the credit that would LEAD a board (the public claim), cached 30d per credit <1 (boards per week, est. from code) model error: today’s list no (the list grows by incident, #2739)
_match_key [^a-z0-9] strip + fold_tokens(lead_artist(...)) Billboard vs kworb vs sales rows attach a figure to a row KEEP KEEP-CLOSED exact fold - blank cell no
genre_entries: fold_tokens(board.subject) not in notable exact set kworb top-artist keys suppress an unknown genre leader KEEP KEEP-CLOSED exact fold - empty set: fail open no
lead-act tally (lead_artist, fold_tokens) feature-clause strip Billboard credit “from N different acts” KEEP KEEP-CLOSED (fixed credit format) - - - no
lead_party(row.artist, known) grouping connector split + exact known Billboard credit group entries per lead act (folded into lead tally) KEEP-CLOSED - - - no
row_key lower-case identity exact fold rows cluster fold KEEP KEEP-CLOSED - - - no

Structural: _KIND_TITLE / _KIND_COLUMN_NOTE dict lookups on own kind.

utils/feed_status.py (160 lines)

No semantic site. Structural: _PROVIDERS / _FAILOVER keyed on own source ids; pct >= LOW_PCT numeric.

utils/filler_solver.py (221 lines)

No semantic site. Structural: _OPPOSITE / corner_cell on own corner names, AREA_PALETTE / COLOR_NAMES (RGB table), _NEIGHBORS.

utils/market_art.py (185 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
norm_name(label) == key in leg_image_in / find_leg_image NFKD + punctuation strip + casefold, exact Polymarket groupItemTitle vs subject name use that leg’s portrait KEEP KEEP-CLOSED (exact folded identity join) - - next art rung no

Structural: img == event_img (URL identity), big_enough (image dims).

utils/music_policy.py (244 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
in_house_genre / _IN_HOUSE_TAGS (fast accept) exact tag allow list iTunes primaryGenreName rap / R&B / pop: post HYBRID KEEP-CLOSED (upstream field with a fixed vocabulary; owner-curated fast path) - - - no
judged_in_house_genre (off-list tag) cached Haiku RAP / RNB / POP / NONE artist + title + tag rescue an off-list record (C5) DONE (#3443) house_genre_check, purpose house_genre 0 in 30d (no off-list tag reached the gate) NONE / error: refuse yes (#3443)
_NEVER_RESCUE (indie pop, k-pop, pop latino, pop/rock) exact tag set iTunes tag fast drop, no model call (not present) KEEP-CLOSED (closed upstream vocabulary; each tag rejected by the owner by name) - - refuse yes (#3443)
APPLE_GENRE_IDS in in_house_genre_ids integer id set Apple feed genres ids filter the “just dropped” pool KEEP KEEP-CLOSED - - row refused no
drop_verdict order (genre, new, watched, certified) ordered boolean gate bools + date post / skip + reason KEEP KEEP-CLOSED (own enum; watched membership is resolved in cogs/music.py) - - unknown_artist no

Structural: normalize_tag (spelling folds), is_new_release (ISO date window).

utils/openai_chat.py (688 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_classify_failure ("insufficient_quota" + status sets) documented error code + status OpenAI error body fail over vs surface KEEP KEEP-CLOSED (API error code) - - provider: fail over no
_read_api_response "insufficient_quota" same same do not retry KEEP KEEP-CLOSED - - retried no
is_reasoning_model prefixes, _NO_EFFORT_NONE, min_effort prefix + set own model id effort param + token reserve KEEP KEEP-CLOSED (own config id) - - non-reasoning no
finish == "length" / status == "incomplete" (reasoning_exhausted, responses_exhausted) enum literals OpenAI body retry once KEEP KEEP-CLOSED (protocol) - - ship fragment no
_content_to_text None on empty shape OpenAI body refusal = failed call KEEP KEEP-CLOSED - - canned line no

Structural: parse_responses_body (item["type"], url_citation tags).

utils/reference_movie.py (246 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
movie_matches (_FILM_WORDS, _STRONG_CUES) substring lists /ask reference query + aspect route to TMDB+OMDb or fall through to Wikipedia HAIKU (B7, not built) hit branch DONE (nominate: _pick_film judges every TMDB hit and NONE falls to Wikipedia via reference.lookup); MODEL-HAIKU for the MISS branch cheapest form: make movie_matches true whenever TMDB is provisioned, so the existing pick_reference_film judge decides every non-music question (it already answers NONE for a non-film question). The two lists move into the judge prompt as cues <1 (5 reference_lookup in 30d) judge NONE / error: Wikipedia (today) no
_pick_film -> pick_reference_film cached Haiku pick over TMDB hits question + up to 5 “title (year)” lines which film, or none (B15) DONE (#3430) purpose reference_film_pick 0 in 30d NONE / no verdict: Wikipedia yes (#3430)

Structural: top.release_date[:4], len(lines) == 1, the provisioned gates.

utils/release_age.py (372 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_catalog_release_date: deezer.album_title_matches(title, row["title"]) on the iTunes row token overlap >= 60% + sequel / bracket-tag rules requested title vs the resolve_music_row row title accept or refuse the iTunes date (old audit: 0 semantic sites) MODEL-HAIKU (the pass band only) replace the bare check with deezer.confirm_album_rows(title, [row], kind=...): the exact pass stays the fast accept; a pass inside album_match_band goes to the existing cached Haiku pick (match_catalog_title, purpose catalog_title_match); the requested title + the candidate are the closed input ~1-3 (est.: iTunes answered 797 of 5,096 resolves in 30d; the band is a fraction; results are cached per pair) refuse = undated = no debut claim (today’s refuse direction); model error removes the row yes (#3176, #3998)
MusicBrainz rung chart_ages.first_release_date(exact=True) exact fold MB groups first-release date (not present) KEEP-CLOSED - - timeout: catalogs answer yes (#2958)

Structural: _MISS_PREFIX cache sentinel, date.fromisoformat, max(rd, it) later-date rule for an album, partial-date _PARTIAL_MIN_AGE_DAYS rule.

utils/subject_image.py (474 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
pick_subject_image -> confirm_subject_image Haiku rank + Sonnet confirm downloaded candidates + host + caption which photo ships KEEP (model) DONE subject_image_pick 202, subject_image_confirm 34 in 30d ~8 none: no photo no
is_source_photo own enum rung string delivery trust KEEP KEEP-CLOSED - - - no

Structural: URL scheme check, image/ content type, _MAX_IMAGE_DIM, _host.

utils/the_odds_api.py (982 lines)

No semantic site. Structural: market-key literals (h2h, player_ prefix, outrights), _clean_link ({/} placeholder + scheme), _is_permanent_4xx / _request_ok status classes, x-requests-* header parse, score keyed by exact home_team / away_team, counts.most_common(1) consensus line.

Out-of-scope helpers the old section flagged (lines 2018-2031)
helper owner verdict here
utils/markets.py _SPORTS_PATTERNS, _PM_PATTERNS, _WILL_X_BY_RE, _LEAGUE_KEYWORDS, classify_intent, detect_league, looks_like_sports P2 (markets) Not planned here. Batch P5-B wraps only the two discourse CALL SITES and leaves markets.py unchanged, so it cannot collide with P2.
utils/curator.py _IMAGE_TOPIC_TERMS, topic_is_image_room (none named) DONE (B6 #3429): the list is now the model-error fallback only.
utils/artist_watch.py _CONNECTORS, _PLACEHOLDER_CREDITS, lead_artist, credit_parties, fold_tokens, credit_matches aliases P1; aliases OWNED:W19-C1 _CONNECTORS split is KEEP-CLOSED (corroborated by exact keys). _PLACEHOLDER_CREDITS is the MODEL-HAIKU site in batch P5-D; if P1 plans is_placeholder_credit itself, drop P5-D.
utils/output_checks.py _SELF_CORRECTION_RE, ship_guard.drop_self_correction OWNED:#4422 DONE in part (B4): Haiku shows_working decides at drop_self_correction; the lists are the trigger, the examples and the error fallback. The direct has_self_correction sites are a follow-up. See the B4 row.
claude_client.is_empty_response P3 KEEP-CLOSED (protocol sentinel).
utils/social_search.py parse_trending_phrases, filter_recent (none named) KEEP-CLOSED: a line-shape parse of a model answer, and a date parse.
utils/reference.py _SOURCES chain (genius_matches, wikidata_matches, musicbrainz_matches) (none named; /ask) MODEL-HAIKU in principle (B7 router), but 5 reference_lookup calls in 30d, each to the right source (#3430). Each matcher’s hit is nominate-only (a None result falls to the next source); the miss branch decides alone. Lowest rank; only movie_matches is in a batch (P5-E) because its judge already exists.

Measurements

All queries on Axiom ['tootsies'], window now-30d (to 2026-10-18), unless noted.

  1. where event == 'claude_api' | summarize count() by purpose: the model calls already in this scope: curate_route 13,460, curator_pick 5,914, curate_caption 1,471, topic_dedup 13,611 (all surfaces), subject_image_pick 202, release_version 195 (since 2026-10-15), discourse_score 195 (shared with music, music_desk, chimein), release_reissue 22, chart_age_check 6, curator_image_room 2, trending_filter 1, discourse_icebreaker 1, house_genre 0, reference_film_pick 0. Decides: which sites are DONE and live; C5 and B15 have not fired yet.
  2. event in (discourse_scored, discourse_icebreaker, discourse_dedup, dedup_overturned, reference_lookup, music_house_gate, release_date_resolve) | summarize count() by event: discourse_scored 2, discourse_icebreaker 1, discourse_dedup 0, reference_lookup 5, music_house_gate 9, release_date_resolve 5,096. Decides: the discourse batch and the movie gate are LOW volume (rank L); release_age is the high-volume site in this partition.
  3. event == 'slot_muted' | summarize count() by surface: discourse 500 (music 1,091, curator 673). Decides: discourse is mostly muted, so its batch is low impact today.
  4. event == 'dedup_overturned' | summarize count() by surface: discourse 0 (music_desk 1,214, music_news 518). Decides: the discourse mechanical gate had no hit to arbitrate in 30d; only the catch direction can add value there.
  5. event == 'release_date_resolve' | summarize count() by reason, source: itunes_title_mismatch 81 (called_shot_age 47, debut_release_age 30, x_gate 4), no_date 248, musicbrainz_timeout 8. Decides: the 60% overlap rule fires about 3 times a day, so the ambiguous region is populated.
  6. event == 'release_date_resolve' | summarize count() by provider, mode: deezer/song 1,752, musicbrainz/album 1,371, musicbrainz/song 813, itunes/song 463, deezer+itunes/album 253, itunes/album 81, none 268. Decides: 797 resolves (about 27/day before the per-process cache) took an iTunes date that passed only the bare overlap rule, with no band confirm.
  7. event == 'music_house_gate' | summarize count() by reason: watched 5, new_release 3, certified 1, off_genre 0. Decides: the music_policy allow list refused nothing in 30d; KEEP-CLOSED needs no measurement beyond this.
  8. event == 'album_total' and reason in (reissue, outside_window): reissue 10, outside_window 4. Decides: the reissue omit path is live (about 2 a week).
  9. claude_api purpose in (release_reissue, release_version, ...) by bin 7d: all of release_reissue (22) and release_version (195) fall in the week from 2026-10-15. Decides the calls/day estimate for P5-A.

Proposed batches

Merged into the Part 0 batch plan.

Notes

Inventory P6a: files the first audit never read (charts, X tags)

Checkout: claude/classifier-audit-wave19 at 4e1b6c0. utils/qualifier_trim.py was also read at origin/main (fba1952, which adds the #4516 detectors). The old audit has no inventory section for these files, so every site counts as NEW unless the worklist (lines 1-360) names it. The worklist names these items: B17 (x_gate / classify_x_post), N1 (x_artist_tags scan), A16 (chart_history._HEDGE_RE, billboard_stories._LEADER_TITLE_RE), C3/N11 (album_milestones._relation ambiguous residue), K2 (it lists chart_history._credited and artist_priors as follow-ups that still use the old split rule), and #2016 (market_sibling_claims).

Counts

file semantic structural KEEP-CLOSED MODEL-HAIKU MODEL-JEV KEEP-PROTECTED DONE OWNED
utils/x_artist_handles.py 1 2 1 0 0 0 0 0
utils/x_artist_tags.py 9 6 5 3 0 0 1 0
utils/x_gate.py 1 5 0 0 0 0 1 0
utils/chart_fact.py 0 6 0 0 0 0 0 0
utils/chart_peak.py 0 4 0 0 0 0 0 0
utils/chart_priors.py 0 4 0 0 0 0 0 0
utils/itunes_charts.py 0 5 0 0 0 0 0 0
utils/stream_report.py 3 3 2 1 0 0 0 0
utils/forecast_backtest.py 1 1 1 0 0 0 0 0
utils/source_watch.py 0 1 0 0 0 0 0 0
utils/youtube_oembed.py 0 1 0 0 0 0 0 0
utils/youtube_grounding.py 1 2 0 1 0 0 0 0
utils/qualifier_trim.py 14 3 0 0 0 0 0 14
utils/qualifier_check.py 4 1 0 0 0 0 0 4
cogs/chart_history.py 0 3 0 0 0 0 0 0
utils/chart_history.py 10 9 3 5 0 0 1 1
utils/album_milestones.py 3 2 2 1 0 0 0 0
utils/artist_priors.py 2 2 1 1 0 0 0 0
utils/billboard_stories.py 8 6 2 5 0 0 1 0
utils/market_sibling_claims.py 2 1 1 0 0 0 1 0
utils/title_forms.py 2 1 2 0 0 0 0 0
total 61 68 20 17 0 0 5 19

MODEL-JEV is 0 on purpose. Every MODEL site here needs one of these, and Jev is weak at all of them: music world knowledge (is “Drake” the rapper, is “Scary Hours Edition” a version), a recent act’s identity, or text extraction (Nora subjects, Billboard figures). Volume is also low (under about 25 calls a day per batch), so Haiku’s cost and latency do not matter.

Inventory

utils/x_artist_handles.py (2569 lines, NEW)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
REVIEWED_NAMES 124 catalog spellings a finished post (read by x_artist_tags.names_in_text) which reviewed acts a post names – KEEP-CLOSED An exact, case-sensitive match on a closed, owner-reviewed list. The meaning check for the risky one-word and everyday-word hits is scan_names (DONE, N1). 0 – NEW

Structural: VERIFIED_HANDLES, REVIEWED_ACCOUNT_IDS are exact folded-key / handle-to-ID lookups (data only; no logic in the file).

utils/x_artist_tags.py (1165 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_FAN_BIO in verify_profile 12 bio phrases (“fan account”, “parody”, “unofficial”) live X profile bio refuse the handle (fan_bio) alone – MODEL-HAIKU classify OWN / FAN / UNSURE. Input: the display name, the handle, the bio, and the 12 phrases as reference examples. Cache per handle for 30 days, beside the handle cache. The phrase hit stays the fallback on a model error. under 1 (Axiom: 0 fan_bio refusals in 30d; about 7 uncached profile checks a day) Refuse the tag (absent). FAN or UNSURE = refuse. NEW
_inside_longer_name + _CAP_BEFORE_RE / _CAP_AFTER_RE / _CREDIT_WORDS one-word name next to a capitalized word, except credit words finished post drop the scanned name alone, before the Haiku check worklist N1 (scan stays) MODEL-HAIKU (drop branch) Send the hit to the existing claude.is_music_artist_mention(name, text) instead of dropping it. Add the neighbour word as a hint (“next to: ‘Maye’”). The _CREDIT_WORDS list goes in the task as the credit connectors. 1-3 (the whole scan made 35 x_artist_mention calls in 7d) Model error or NO: no tag (absent), as now. NEW branch
_is_title_position + _BY_AFTER_RE inside double quotes, or before “by " finished post, and the tag_text path for lane-passed names drop the name alone (title, not artist) worklist N1 MODEL-HAIKU (the “by” branch only) Same Haiku call, with the hint “the name sits before ‘by X’”. The quote-count branch is a structural parse of the quote marks and stays KEEP-CLOSED. under 1 No tag (absent). NEW branch
_SCAN_SKIP, needs_artist_check, scan_names 60 everyday-word names; one-word names finished post tag or not N1 SHIPPED DONE Every one-word or skip-list hit goes to Haiku; a multi-word reviewed name is an exact closed-list hit. – – no
_spellings, _SMALL_WORDS, _MC_DJ_RE, _EDGE_SYMBOLS_RE case / accent / small-word variants the reviewed names which exact spellings the scan looks for – KEEP-CLOSED It only widens the exact spellings of a closed list. 0 – NEW
spelling_ok, _DOTTED_KEYS “B.o.B” needs its dots a lane name refuse a dotted key spelled without dots – KEEP-CLOSED An exact form rule on a closed list. 0 – NEW
verify_profile name check normalize_name(artist) equals display name or handle profile name_mismatch – KEEP-CLOSED An exact folded-key identity join. The ID check (REVIEWED_ACCOUNT_IDS) is also exact. 0 – NEW
_name_pattern case-sensitive whole-word name post text where to put the tag – KEEP-CLOSED An exact match of a closed name. 0 – NEW
artist_from_subject split on “ – “ / “ - “ the desk’s own Title - Artist subject which name a desk lane tags – KEEP-CLOSED It parses our own subject format. A bare subject gives “” (absent). 0 – NEW

CB-27 result (2026-10-18). Measured on 30 days of real text: 2,192 unique posts (1,200 @tootsiesbar tweets from twitterapi.io plus 1,000 post_dedup_history rows, tags stripped) and the live bios of the 223 handles that tagged in 30 days (191 with a bio). Two runs of the real Haiku model on each set.

site old new old errors new errors runs agree
_inside_longer_name drop drops alone held_names -> is_music_artist_mention(hint=...), purpose=x_artist_position 2 wrong of 3 (“Four Drake songs”, “BTS World Tour”; “Luis Miguel” right) 0 of 3 3 of 3
_before_by (“by” branch of _is_title_position) drops alone same call, the “by” hint; a YES is recorded so tag_text tags there 2 wrong of 2 (“f/Don Toliver by Drake”, “f/Ava Max by BIGXTHAPLUG”) 0 of 2 2 of 2
_FAN_BIO refuses alone bio_refused -> Haiku x_bio_owner OWN / FAN / UNSURE, purpose=x_bio_owner; UNSURE or error = the phrases 0 refusals of 191 real bios (0 wrong) 0 refusals of 191 (0 wrong) outcome 191 of 191; label 185 of 191 (6 OWN / UNSURE flips, same outcome)
quote branch of _is_title_position KEEP-CLOSED unchanged (a parse of quote marks) – – –

Goldens on the real model, both runs: “Madonna by Drake”, “Drake Maye”, “Luis Miguel”, “The Eminem Show” stay NO; a fan, an updates and a parody bio read FAN (the updates bio has none of the 12 phrases, so the phrase rule passed it). Two earlier prompt arms were measured and not shipped: the first position hint said YES on “The Eminem Show”; the first two bio prompts read 1 and then 2-3 real accounts as FAN (Gunna’s “BRING DRIP SEASON BACK !”, “Official Ed Sheeran HQ account”, Jack Johnson’s management bio). The batch row said UNSURE should refuse: measured, 34-42 of 191 real bios read UNSURE (a link, a symbol, one word), so UNSURE uses the phrase rule instead. Calls: about 0.2 a day for the position check (6 held names in 30 days); the bio check runs once per handle and bio per 7 days (durable cache), at most about 30 a day. Cost under $0.01 a day. Median latency 0.52 s per call. After merge: ['tootsies'] | where event == 'claude_api' and purpose in ('x_artist_position', 'x_bio_owner') | summarize n=count(), ok=countif(ok==true) by purpose and ['tootsies'] | where event == 'x_artist_tag' and reason in ('fan_bio', 'by_not_artist', 'longer_not_artist', 'by_check_failed', 'longer_check_failed') | project _time, reason, artist=parse_json(detail)['artist']. A fan_bio row on a reviewed artist is a regression.

Structural: _HANDLE_RE (X handle format), _GROK_LINE_RE / parse_grok_handle (the Grok HANDLE: protocol), _POSSESSIVE_TAIL_RE / _BARE_POSSESSIVE_RE (where the tag is inserted), the "://" URL test, mode() / _MODES, SCAN_SURFACES (our own surface enum).

utils/x_gate.py (350 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
classified -> claude.classify_x_post Haiku topic / unit / artist / title newsroom story text STREAMS / CHART / OTHER for the X gate B17 SHIPPED DONE Already a model call (purpose=x_gate_classify, 196 calls in 7d). 28 (Axiom) Fail-open: OTHER, the post goes to X. no

Structural: _POS_RE (a “#N” figure), static_reason / streams_reason / forecast_reason (number thresholds), forecast_facts (Kalshi fields), top_position (calls kworb.find_chart_entry strict=True with known, which is K2’s matcher and not in this partition).

utils/chart_fact.py (254 lines), utils/chart_peak.py (453), utils/chart_priors.py (349), utils/itunes_charts.py (658), utils/source_watch.py (160), utils/youtube_oembed.py (89), cogs/chart_history.py (1132)

No semantic sites.

utils/stream_report.py (348 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_fold (strict key) feature mark cut, non-alphanumerics dropped kworb song titles which page row is the milestone song; the catalog ordinal – KEEP-CLOSED An exact folded-key join. 0 No row: no report. NEW
_fold_loose / _TAIL_RE residue in build_report cuts any “ - …” tail kworb row vs iTunes tracklist a row that matches a track ONLY by the loose key (“Plot Twist - Sped Up”) counts as that track’s version – MODEL-HAIKU (loose-only residue) Send each loose-only row to the existing cached claude.release_version_kind(title, base) (purpose=release_version). RECUT or EDITION = the same track (old rule); NEW = a separate song, not counted. Keep the exact track-key join as the fast accept. under 1 (only stream milestones with an album resolved) Model error: the old loose rule. NEW
is_single / _SINGLE_TAG_RE ” - Single” iTunes collection name; fewer than 2 folded tracks iTunes collection no album ordinal – KEEP-CLOSED An upstream field with a fixed form, plus a count. 0 – NEW

Structural: _EDITION_TAIL_RE / display_album and _REMASTER_TAIL_RE / display_title change the shown text only (every key keeps the full title); _nth; _lead_fact ranking by counts.

utils/forecast_backtest.py (229 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
GENRE_BUCKETS / genre_bucket Apple genre name -> 7 buckets Apple primaryGenreName grading bucket – KEEP-CLOSED An upstream field with a fixed vocabulary. A tag not listed is other. Only the offline scripts/backtest_forecast.py reads it. 0 – NEW

Structural: HITS_KINDS (our HITS chart keys), the grade arithmetic. It imports artist_priors.same_act (see that file).

utils/youtube_grounding.py (114 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
take_names_a_leg (-> market_subject_image.take_references_leg) whole-phrase match of a leg title in the take composed market_alert take on a YouTube daily-chart board, non-leader takes ship (any leg phrase found) or drop alone C4 kept it MODEL-HAIKU (both branches) classify NAMES_LEG / OTHER_SONG / NO_SONG. Input: the take, the leg labels, and the kworb upload titles from youtube_grounding_block (they name the artist). The phrase match stays the fallback. An alternative: reuse market_take_names_leader with the legs as the closed set. 1-3 (code: non-leader YouTube alert takes) Fail closed: OTHER_SONG or model error = drop (as now). NEW verdict

Structural: leading_legs (price max, yes_label field), youtube_grounding_block (exact case-folded compare to skip a redundant line), fetch_youtube_rows.

utils/qualifier_trim.py (619 lines at 4e1b6c0; about 790 at origin/main fba1952) – OWNED:#4412

All 14 semantic sites are OWNED:#4412 (coordinator steer, 2026-10-18). Do not put any of them in a batch.

symbol new verdict
_QUALIFIER_RE / has_qualifier OWNED:#4412
_RANK_RE / _RANK_ORDINAL / _QUANTITY / _rank_matches / _rank_spans (#4471) OWNED:#4412
_RELEASE_NOUN_RE / _RELEASE_VERB_RE OWNED:#4412
_SPAN_WORDS / _SPAN_RE / time_span (#4516) OWNED:#4412
_JOINS_ONLY_RE / joins_as_only (#4516) OWNED:#4412
_MOST_RE / states_most (#4516) OWNED:#4412
_QUALIFIER_BEFORE_RE / _DURATION_BEFORE_RE / _LOOKBACK_RE OWNED:#4412
_FILLER / _MAX_CONTENT_WORDS / _INTENSIFIERS / _is_qualifier_token OWNED:#4412
_ABBREVIATIONS / _sentence_starts OWNED:#4412
_PRONOUNS / _capital_mask OWNED:#4412
_DETERMINERS / _COPULAS / _PHRASE_STOP / leaves_dangling OWNED:#4412
cut_runs / cut_qualifiers / is_cut OWNED:#4412
restates_cut OWNED:#4412
_RANK_SAID_RE / cut_note OWNED:#4412

Structural: _TOKEN_RE, _QUOTED_RE / _quoted_spans / _title_spans, _SENTENCE_END_RE.

utils/qualifier_check.py (235 lines) – OWNED:#4412
symbol new verdict
_FIRST_RE (first / only / ever / never -> chart-history research) OWNED:#4412
_SPOTIFY_GLOBAL_RE (Spotify + global in the claim) OWNED:#4412
_WITH_RE / _MORE_SEPARATORS / _AND_RE / _name_key (credit split for the kworb history read) OWNED:#4412
check_source_qualifier (check_claim_qualifier path) OWNED:#4412

Structural: _HISTORY_MAX and the research plumbing.

utils/chart_history.py (1541 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_HEDGE_RE in parse_nora_text / hedged_rank_sentences hedge words Nora sentence drop the rank A16 OWNED:W19-B3 – – – no
nora_rank_is_call / nora_records_checked Haiku CALL / CONTEST hedged residue keep the rank A16 SHIPPED DONE purpose=nora_rank_check, 14 calls in 7d. 2 (Axiom) – no
_SUBJECT_HEAD_RE subject = text before one of 20 verbs (is, set, projected, aiming, grabs…) Nora post sentence (a) the sentence is a projection at all; (b) which words are the subject – MODEL-HAIKU One Haiku JSON extract per post (cached by tweet id): a list of {title, artist, units, rank, chart, hedged}. The verb list and the _TITLE_BY_RE / _POSSESSIVE_RE shapes go in as reference examples. Code keeps the exact checks: each units and rank must appear verbatim in the sentence (number check), the title and artist must be substrings of the post. 10-20 (Axiom: about 580 Nora rows stored in 30d from 78 store events; posts per day about 10-20) Model error: the regex parse (the old rule). A field that fails the verbatim check is dropped (absent). NEW
_TITLE_BY_RE / _POSSESSIVE_RE / _subject_parts “‘T’ by A”, “A’s ‘T’”, unquoted possessive with a space Nora subject split title from artist – MODEL-HAIKU Same extract. (same) (same) NEW
subject.lower().startswith(("nora ", "this week", "follow", "download")) 4 opener words Nora subject drop the sentence alone – MODEL-HAIKU Same extract (the model returns no row for a promo line). (same) (same) NEW
_chart_named “hot 100” substring Nora sentence Hot 100 vs Billboard 200 for a rank-only call – MODEL-HAIKU Same extract, chart as a closed label (billboard200 / hot100 / other). (same) Model error: the substring rule. NEW
_credited (in history_rows) key in kworb._artist_credits(row artist) store row credit vs the asked act admit the row into the act’s history K2 lists it as a follow-up MODEL-HAIKU (fragment residue) Reuse K2: a row admitted only by a PART of an UNRANKED multi-act credit goes to claude.classify ONE / COLLAB (purpose=kworb_act_check, cached 14d per credit). COLLAB admits (old rule); ONE drops. Whole-credit hits and all-ranked-part credits make no call (#4277 fast path). under 5 (code; K2 measured 10-30 a day across all its callers) Model error: the old rule. NEW
_RADAR_LINE_RE “Name - 46K” list line Nora RELEASE RADAR post a units row – KEEP-CLOSED A fixed list format with a number. 0 – NEW
nora_doc_kind “EARLY LOOK” / “RELEASE RADAR” Nora post doc kind label – KEEP-CLOSED Nora’s own fixed post headers; only a label. 0 – NEW
_DEBUT_TITLE_RE / _parse_debut_title “Will A’s ‘T’ debut” Kalshi KXALBUMDEBUT title (title, artist) – KEEP-CLOSED An upstream fixed question template; a miss is dropped (never a guess). 0 – NEW

Structural: _UNITS_RE / _RANK_RE (number formats), _PERIOD_HALF_RE / market_window (dates), _split_subject (our own Title -- Artist), _event_status (Kalshi status enum), _is_artist_family (ticker), _HANDLE_RE / _URL_RE / _TRAILING_RE (_clean), _sentences, CHART_LABELS / render code.

utils/album_milestones.py (332 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_relation (_VARIANT_WORDS_RE 36 words, _DISTINCT_RE sequel / re-record forms) the words a longer album title adds two kworb album titles, one a prefix of the other merge (variant) or count apart (distinct) alone; only “ambiguous” goes to Haiku C3 / N11 (list kept, residue to model) MODEL-HAIKU (the variant and distinct fast paths) Send every prefix pair to the existing cached claude.album_variant_relation (purpose=album_variant). Feed the word hit as a hint (“the list reads this as VARIANT: ‘edition’”). about 3-5 the first week, then under 1 (Axiom: 8 calls in 7d for the ambiguous residue; cached per pair) Model error: the word list; no list verdict: withhold the count (as now). NEW verdict
_group_releases prefix test (folded.startswith(g + " ")) folded prefix album titles which pairs are compared at all – KEEP-CLOSED A structural prefix on folded keys; only nominates pairs. 0 – NEW
split_credit / _CREDIT_SPLIT_RE split on “,” and “&” iTunes album credit acts to count the album for – KEEP-CLOSED Every part is verified by an exact join (lists_album: that act’s own kworb page lists the album id). A wrong split resolves to no page. 0 – NEW

Structural: base_title, _fold, album_key, lists_album, count_clause, _ordinal_word.

utils/artist_priors.py (352 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_credited / same_act (via act_keys) key overlap after kworb.credit_members split store row credit vs an act; HITS vs Billboard credit which openings form the act’s prior K2 lists it as a follow-up MODEL-HAIKU (fragment residue) Same K2 reuse as chart_history._credited (ONE / COLLAB, kworb_act_check). Only a match through a part of an unranked multi-act credit asks. under 2 (code: read per prior; cached) Model error: the old rule. NEW
act_keys alias expansion (canonical_credit, credit_names) the shared alias table an act name which other names count as the act – KEEP-CLOSED here An exact table lookup. The table itself is artist_watch aliases, OWNED:W19-C1; this file only reads it. 0 – NEW

Structural: party_set, the prior arithmetic, SAME_ACT_* fit constants.

utils/billboard_stories.py (584 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_anchors + _MOVE_VERBS (about 70 verbs) + _OTHER_CHART_RE (about 30 chart names) + _YEAR_RE look-ahead “No. N”, “ 10-9", skip when another chart or a year follows story paragraph which rank a units figure belongs to; skip other-chart ranks alone – MODEL-HAIKU One Haiku JSON extract per story (cached by post id): for each printed top-10 row (rank, title, artist off the chart page), the units figure the story states for THIS week, with the sentence it came from. The verb and chart-name lists go in as reference examples. under 1 (about 1 story a week, plus the settled-market reveal) Fail closed: no figure for a rank the model does not confirm (absent). Model error: the regex parse plus verify_rows (old rule). NEW
verify_rows folded title or artist is a substring of the segment candidate (rank, units, segment) vs printed row keep the figure for that rank – MODEL-HAIKU Same extract (it replaces the substring confirm). Code keeps the number check: the units figure must appear verbatim in the story. (same) (same) NEW
leader_row_for folded title substring of the headline + _artist_agrees settled Kalshi release vs story headline the story’s No. 1 figure is this release’s – MODEL-HAIKU classify YES / NO / UNSURE: “is this headline’s No. 1 the release by <artist>?". The exact folded-title hit stays the fast accept only when the artist parties also match exactly.</artist> under 1 Fail closed: NO / UNSURE / error = no row (absent, as now). NEW
_artist_agrees / credit_parties / _CREDIT_SPLIT_RE any credit party is a SUBSTRING of the folded headline raw Kalshi credit vs Billboard row or headline the same act – MODEL-HAIKU Folded into the two calls above (a substring of a folded headline is a fuzzy identity join: “ye” is inside many names). (same) (same) NEW
_QUESTION_RE headline opens with will / could / how / … WordPress post title drop the candidate alone (a preview question) – MODEL-HAIKU (or drop the filter) The cheapest fix is no model: remove the filter and let parse_story decide, as it already does for the second tier. Else one classify STORY / PREVIEW per post title. under 1 A wrong pass costs one page parse that returns None. NEW
_LEADER_HINT_RE (leader paragraph fallback) “No. 1”, “atop”, “leader” plus a units figure the first 6 paragraphs which paragraph is the No. 1’s – MODEL-HAIKU Same per-story extract. (same) (same) NEW
_LEADER_TITLE_RE / “billboard 200” title test leader words in a post title WordPress titles ORDER of candidates only A16 KEEP DONE Only orders the candidates; the page parse decides. The “billboard 200” test is also the WordPress search term. 0 – no
_units_in / _UNITS_RE / _QUAL / _split / _SPLIT_RE / _sea_streams / _pct number + “units”, “SEA units comprise N” paragraph the figure – KEEP-CLOSED Number extraction on a fixed phrase. Under the extract above it becomes the verbatim number check. 0 – NEW

Structural: _WEEK_ENDING_RE / _WEEK_RANGE_RE / _DATED_RE / _month_day / _date_near (dates), _TAG_RE / _STRIP_BLOCK_RE / _PARA_RE (HTML), story_window / story_posts_url.

utils/market_sibling_claims.py (133 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
same_subject_posts -> claude.market_sibling_claim shared title -> Haiku SAME / CONTRADICTS / DIFFERENT market post vs 6h of posts on other events drop #2016 DONE Nominate-only: the title match only chooses which posts the model reads, and the model decides every drop. A miss ships, which is this gate’s fail direction (fail-open). 0 in 7d (Axiom: 0 market_sibling_claim calls; 0 sibling_* drops in 30d) Fail-open. no
_QUOTED / _TITLE_BY / _names_in quoted span; “ by <Capital>" opening</Capital> our own market post text the title a post names – KEEP-CLOSED It parses the fixed formats our own lanes write. 0 – NEW

Structural: sibling_drop_reason (verdict enum to reason).

utils/title_forms.py (102 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
FEATURE_RE / _STRICT_FEATURE_RE / strip_features / strip_marked_features / bare_title feat / ft / featuring / f/ / w/ titles and credits from Mediabase, Billboard, kworb, the wire cut the featured part before an exact compare C3 cites it KEEP-CLOSED A closed set of credit markers (a fixed format), applied to both sides before an exact fold. 0 – NEW
_RADIO_EDIT_RE / strip_radio_edit “(Radio Edit)” / “- Radio Edit” titles same recording – KEEP-CLOSED One fixed tag. 0 – NEW

Structural: TRAILING_DASH_RE.

Measurements

All queries ran on ['tootsies'].

query (short) window result what it decides
claude_api count by purpose, for the purposes in this partition 7d x_gate_classify 196, release_version 195, x_artist_mention 35, release_reissue 22, nora_rank_check 14, album_variant 8, chart_age_check 6, market_take_check 2; kworb_act_check 0, market_sibling_claim 0 Volume of the existing model calls I reuse. album_variant 8/week is the ambiguous residue only, so all pairs will cost a few more a day.
x_artist_tag by reason, mode, ok 30d cached/ok 1331, not_reviewed 404, reviewed/ok 175, pinned/ok 48, error 5, unverified 3; fan_bio 0, not_artist 0, check_failed 0 Mode is reviewed in production. _FAN_BIO refused nothing in 30 days, so its batch is low priority. scan_names dropped no name with a NO verdict.
events with reason in (leader_fabrication, leader_check_unavailable, sibling_repeat, sibling_contradiction) 30d 0 rows take_names_a_leg and the sibling judge dropped nothing in 30 days. The take_names_a_leg drop branch costs nothing today. Its risk is the PASS branch: a take that names a leg title and still names the wrong act ships with no model read.
chart_history by source, phase, ok, reason 30d nora: 44 upkeep + 34 backfill stores, 580 rows, 18 no_projections; story: 111 ok (936 rows), 323 ok partial, 76 failed partial, 27 no_story; billboard 206 + 10; hits 132 + 60; kalshi 396 Nora volume for its extract (about 20 rows a day). The story parse returns partial on most backfill weeks (399 partial against 111 whole), so the regex anchors miss ranks often. That supports the Billboard batch.
market_filtered reasons containing fabric / sibling / leader 30d only non_leader_chart (kalshi) 3290 Confirms no leader-fabrication drops under market_filtered either.

Proposed batches

Merged into the Part 0 batch plan.

Notes

Inventory P6b: arcade, Jev callers, runtime scripts

Repo state: claude/classifier-audit-wave19 = origin/main 4e1b6c0. Read-only pass. Axiom window: 7 days unless the row says 30 days. Date 2026-10-18.

Scope read: cogs/arcade.py, all 16 utils/arcade/*.py, utils/guess_grading.py, utils/engagement_labels.py, utils/mention_shadow.py, utils/typesafe_client.py, utils/deploy_drain.py, utils/loop_watch.py, utils/axiom_dataset.py, and the 22 named runtime / manual scripts. For the Jev-caller check, I also read the call sites in cogs/games.py (round loop, _grade_guess, _artist_named, _credit_acts, _title_credit_artists), cogs/x_game.py (handle_webhook_tweets), cogs/ask.py (_shadow_jev, _names_other_channel), utils/engagement.py (_tally_content, the seed keys) and utils/playercard.py (the stat readers). Those files belong to other partitions. I list their sites only in the “Jev caller leftovers” section.

Counts

file semantic structural KEEP-CLOSED MODEL-HAIKU MODEL-JEV KEEP-PROTECTED DONE OWNED
cogs/arcade.py 0 8 0 0 0 0 0 0
utils/arcade/fighter_models.py 3 12 2 0 0 1 0 0
utils/arcade/memory_cities.py 1 2 0 1 0 0 0 0
utils/arcade/ (the other 14 files) 0 30 0 0 0 0 0 0
utils/guess_grading.py 4 2 3 0 0 0 1 0
utils/engagement_labels.py 1 6 0 0 0 0 1 0
utils/mention_shadow.py 1 4 0 0 0 0 1 0
utils/typesafe_client.py, deploy_drain.py, loop_watch.py, axiom_dataset.py 0 4 0 0 0 0 0 0
Jev caller leftovers (cogs/games.py, utils/engagement.py, utils/playercard.py) 5 0 0 0 5 0 0 0
scripts/artist_sweep.py 2 1 0 1 0 0 0 1
scripts/music_drop_manual.py 3 3 1 1 0 0 0 1
scripts/versuz_card_manual.py 3 3 1 2 0 0 0 0
scripts/versuz_recap.py 0 1 0 0 0 0 0 0
scripts/riaa_credit_audit.py 3 1 0 2 0 0 0 1
scripts/competitor_scoreboard.py 10 6 3 5 0 0 0 2
scripts/called_shot_scorecard.py 2 2 2 0 0 0 0 0
scripts/gen_yearbook.py 1 2 1 0 0 0 0 0
scripts/gen_yearbook_cards.py 0 3 0 0 0 0 0 0
the other 12 scripts (see Structural lines) 0 10 0 0 0 0 0 0
total 39 ~100 13 12 5 1 3 5

Inventory

cogs/arcade.py (3310 lines)

No semantic site. The cog takes no free text from a user: /arcade takes an opponent member, and the web hub sends our own op enums.

Structural: _AVATAR_HOSTS (Discord CDN host set), _SLOT_STATE (our kv slot format), meta.get("status") in ("final","failed") / ("failed","pending") (our row status enum), str(meta["error"]).endswith("bad_pose") x2 (our own reason slug), _PERSONAL_REFUSALS (our refusal reason codes), the likeness:* reason strings. The likeness gate calls are counted under fighter_models.

utils/arcade/fighter_models.py (2041 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_draw_sheet (OpenAI branch) "safety" in low or "moderation" in low substring on the HTTP 400 body OpenAI image-edit error body safety_reject (FINAL: the member never gets a fighter) vs http_400 (retryable) none KEEP-CLOSED not a model. The body is an upstream error. Read the JSON error.code (a fixed vocabulary, e.g. moderation_blocked) exactly, and keep the substring only as the fallback when the body is not JSON. 0 model calls; 29 safety_reject in 30 days (measured) unknown body: retryable http_400, as today NEW
_draw_grok any(w in low for w in ("safety","moderation","reject","policy")) 4-word substring list on the 400 body xAI error body safety_reject_xai (FINAL) vs xai_http_400 (retryable) none KEEP-CLOSED same as above: parse the xAI error code field exactly. “reject” and “policy” are wide words: a 400 about a size or a parameter that says “rejected” turns FINAL. 0 unknown: retryable NEW
LIKENESS_CHECKS / likeness_refusal / likeness_conditions code check per constitution.GENERATED_LIKENESS_CONDITIONS name the run’s LikenessRequest refuse the build none KEEP-PROTECTED constitution check. Not for Jev (brief). The likeness and pose vision judges (fighter_likeness_score, fighter_pose_score) are already models and are not word lists. 0 fails CLOSED (no_constitution, unchecked:<name>) NEW

Structural: FINAL_REASONS, _TRIPO_DONE_BAD, RETRYABLE, _AVATAR_OWNER (CDN URL shape), POSE_REBUILD_ALWAYS / EYES_REBUILD / QUALITY_REBUILD (owner-named subject_tag hashes, an id set), _ARTIST_OUTAGE, error.startswith("sheet: "), Mixamo bone prefix, glTF key tests ("TANGENT" in attrs, "matrix" in node, …), allowed={"convert_lo"}.

utils/arcade/memory_cities.py (132 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
cities_in (GAZETTEER ~170 cities + alternates, _RE whole-word, case-sensitive) name list as a regex up to 400 DAILY memory notes per guild which extra Deathmatch arenas a guild gets (a city needs 2 notes) none MODEL-HAIKU (DONE wave 20: confirmed_cities, purpose=memory_city_check; the regex nominates and is the fallback. Dry run 2026-10-18 on all 406 daily notes of the 2 servers: 329 notes asked, 0 run-to-run key-set changes, 1 false keep removed (a small server’s Paris rested on a person’s name), 1 boundary swap at slot 16 between two real cities) NOMINATE then confirm. The regex stays and nominates. For each nominated city, one cached classify PLACE / OTHER over the note sentences that name it (“Does this text use ‘' as the city, not as a person, a team, an album or a nickname?"). The gazetteer stays the closed candidate set (an arena needs a drawn landmark). Cache per (guild, city, hash of the matching sentences), 1 day. Examples to put in the task: "Sean Kingston", "Memphis Bleek", "Paris Hilton", "Denver Nuggets" are OTHER. measured 25 refreshes in 7 days (3.6/day), 13.2 cities each; with a 1-day cache about 15-50 calls/day model error: keep the regex hit (a wrong arena is harmless). Haiku only: the input is memory text, so do NOT send it to Jev (a new vendor for memory data needs owner sign-off; the brief excludes the memory pipeline from Jev). NEW (#3707, 2026-09-25)

Structural: _BUILT_IN_NAMES (exact set of our own built-in arenas), slug, key_of.

utils/arcade/ (the other 14 files)

Structural only: artist_portrait._HOSTS (photo host set), artist_roster.artist_slug / by_slug / stale_entries (exact slug join on our pinned roster; stale_entries has no production caller), DEEZER_IDS (pinned ids), discord_auth._TOKEN_SHAPE / INSTANCE_ID_SHAPE, hub._LIVE_URGENT_EVENTS / _ROSTER_CURSOR / op enums, live_feed _DOWN_STATES / _PHASE / _RANK / method enums, ring_kings.ACTIONS / state sets, deathmatch SOLID / LAYOUTS / CITIES / VEHICLE_CODE, live_card colour and phase tuples, room.GAMES / MODES, roster.TABS, web._FIGHTER_PATH / _ARTIST_PIC_PATH / _SCRIPT_SRC / _PROP_SET / _PROP_FILE / proxy host suffix. game_loop.py, token.py, __init__.py: nothing.

utils/guess_grading.py (194 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
names_title / several_titles Nouls + title_verdict threshold Jev Noul, 0.5 guess, title, artist title win HYBRID (#4311) DONE Jev decides the undecided residue. 0 in 30 days (measured: no typesafe_api purpose=guess_grade; the last /guess round was 2026-09-18, before Jev) Jev None: /guess uses _grade_guess; the X game fails CLOSED NEW
bare_answer + _JOINERS (by, ft, feat, featuring, x, and, with, the) exact title span + credited-name tokens + 8 joiners guess fast-path WIN, no Jev none KEEP-CLOSED It decides alone, but every word must be the exact folded title or a token of a credited name or one of 8 closed function words. It is an exact identity join on closed round data. Measured (docstring): Jev scored these shapes 0.12-0.33, so the model is the worse judge here. Small risk: a credited-name token that is a common word (“future”, “lil”, “kid”) counts as allowed filler. 0 n/a NEW
pool_title_hits (>= 2 hits = shotgun list) exact title spans of the round pool guess fast-path MISS, no Jev none KEEP-CLOSED Counting, which Jev cannot do. Exact folded spans of a closed pool. Risk to record: a one-word pool title used as chat (“my bad, its gods plan”) counts as a second title and the real answer loses. That is the #4311 class in the other direction. A miss lets the player type again, so the fail direction is safe. 0 fails toward miss NEW
title_contained (HARD “type it right”, #587) exact folded substring guess vs title HARD: no containment = miss before Jev none KEEP-CLOSED an owner-set game rule on an exact fold. Jev then decides answer vs chat. 0 miss NEW

Structural: core_title bracket split, TITLE_YES / SEVERAL_YES thresholds.

utils/engagement_labels.py (333 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
question_specs q/l/h Nouls, verdicts Jev Noul x3 per message, 20 per request cleaned message text labeled question / laugh / hype counts KEEP + JEV (#4318) DONE (as a labeler). See leftover L3-L5: no reader uses its output. Jev measured 68 requests (1,452 messages) in 7 days, all on 2026-10-18 09:00Z; 0 failed batches Jev None: the shape-rule verdict per message NEW

Structural: _URL_RE, _CUSTOM_EMOJI_RE, _USER_MENTION_RE, _ROLE_MENTION_RE, _CHANNEL_MENTION_RE (Discord markup), clean_text empty-text rule (no alnum).

utils/mention_shadow.py (165 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
abuse Noul + route / period Choices Jev shadow cleaned mention nothing ships; agreement metric only Jev shadow (#4406) DONE The shipped verdicts are already models (Haiku classify_abuse, Sonnet classify_mention_intent). 0 Jev calls so far (measured). The shipped judges: 59 classify_abuse + 10 mention_intent in 7 days, the last before the key went live. shadow: no effect NEW

Structural: route_verdict / shipped_route / abuse_verdict (closed-label answer parsers), ROUTES / PERIODS. The cog’s _names_other_channel recap -> ask override reads message.channel_mentions ids: structural.

utils/typesafe_client.py (377), deploy_drain.py (107), loop_watch.py (134), axiom_dataset.py (23)

Structural only: _RETRY_STATUSES, status in (400, 422), fs.filename.startswith(_ROOT). deploy_drain.py and axiom_dataset.py have no decision on text.

Jev caller leftovers (cogs/games.py, utils/engagement.py, utils/playercard.py)

The brief asked whether a Jev caller still leaves a word list deciding alone. Two do.

id symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
L1 cogs/games.py _artist_named (whole name, OR the longest token >= 4 chars, OR EASY SequenceMatcher >= _ARTIST_FUZZY_RATIO 0.82), via _win_components and _names_credit token containment + fuzzy ratio guess vs credited acts artist +1 point, feature +1 point, and the artist-only consolation (ARTIST_ONLY_POINTS) KEEP (#4311 left artist credit in code) MODEL-JEV Add a third Noul names_artist to the SAME jev_questions() request: “Does guess name one of the acts in acts?”. acts = jev_acts (already built per round: _credit_acts + features + _title_credit_artists), fed in the state as the closed candidate list. The rule stays the nominator: only a guess where _names_credit or _artist_named fires asks Jev about the artist. This is the #4311 class: “the future is bright” names Future by the longest-token rule and banks the consolation. ~0-30/day (29 /guess rounds in 30 days, all before 2026-09-18; 0 X game rounds in 30 days) Jev None: today’s rule NEW scope (sites old)
L2 cogs/games.py _title_credit_artists (_REMIX_CREDIT_RE + _NON_ARTIST_CREDIT_WORDS, 23 words) word list raw catalog title which remix credits count as guests KEEP MODEL-JEV (by L1) With L1 the list only builds the candidate acts list. A version phrase that slips through (“Sped Up Mix”) becomes one more candidate, and Jev decides. It turns NOMINATE-ONLY; no separate call. in L1 in L1 no
L3 utils/engagement.py is_question (endswith("?")) shape rule message the “questions asked” stat KEEP + JEV (#4318) MODEL-JEV The Jev label is already computed and stored as questions_labeled, but NO reader uses it. utils/playercard.py reads g("questions"). Fix: read questions_labeled when labeled_messages == messages, else the shape key (the rule the seed comment already states). Then run one full labeled /stats rebuild so the condition holds. 0 added. One rebuild: ~15k requests, ~$3.60 (#4318 docstring). Daily catch-up: ~70 requests (measured). Jev None: shape key (today) NEW finding
L4 utils/engagement.py is_own_laugh (_LAUGH_RE + _OWN_LAUGH_EMOJI) regex + emoji set message “times you laughed” KEEP + JEV MODEL-JEV same: playercard reads laugh_messages, not laugh_messages_labeled. in L3 in L3 NEW finding
L5 utils/engagement.py is_caps (caps ratio > 0.7) shape rule message “ALL-CAPS posts” KEEP + JEV MODEL-JEV the Jev key is hype_messages_labeled (excited hype), a different meaning from “ALL-CAPS posts”. The card label must change with the key (“hype posts”), or the card keeps the caps stat and adds hype. Owner choice; the label text is user-facing. in L3 in L3 NEW finding

Not leftovers (checked): the EASY _WIN_RATIO / _CLOSE_RATIO title rules run only when Jev has no answer, and the close band then goes to Haiku judge_song_guess (DONE). The X game passes pool=[title] and fails closed on no Jev answer (DONE). LAUGH_EMOJI / BANGER_EMOJI on REACTIONS is a closed emoji set (KEEP-CLOSED, structural).

scripts/artist_sweep.py (271 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
row_is_theirs (from utils/artist_watch) credit match chart row credit which rows are the act’s not inventoried OWNED:W19-C1 consumer only; the matcher is in the C1 scope (aliases, lenient_tier, s_variants). manual — no
board_stream_totals(..., ) called with no claude client the album-track Haiku match is skipped album rows a stream total, or blank not inventoried MODEL-HAIKU Wire the existing Haiku album-track match (the live lane already has it; the code comment says the sweep leaves it out). Pass a ClaudeClient when ANTHROPIC_API_KEY is set. No new method. manual, ~0-2 runs/day, a few calls per run model error: blank, as today no

Structural: _slug filename.

scripts/music_drop_manual.py (538 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
video_kind (_VISUALISER_RE, _LYRIC_RE, default “video”) 2 regexes YouTube title the claim says “official visualiser / lyric video / video” KEEP (manual) MODEL-HAIKU classify labels VISUALISER / LYRIC_VIDEO / MUSIC_VIDEO / AUDIO / LIVE / OTHER over the YouTube title + channel. The two regexes stay as the fallback and go in the task as examples. Today an “(Official Audio)” upload reads “video”, and the claim states “have released the official video”, which is false. AUDIO / LIVE / OTHER need their own claim wording or a refusal (prefer absent). manual, ~0-3/day model error: the regex label no (old KEEP)
title_cut exact casefold substring of the known title composed line flags that a qualifier cut removed the title none KEEP-CLOSED exact identity on a known string 0 — NEW
qualifier_checked -> Music._qualifier_gate qualifier rules composed line cut or refuse a wrong “first / ever / most” none OWNED:#4412 qualifier epic manual — NEW (#4473)

Structural: _SONG_HOSTS, release_clause (“ - Single” / “ - EP”: iTunes collection-name convention), X-Clip-* header filter.

scripts/versuz_card_manual.py (450 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_similarity / _closest (SequenceMatcher ratio OR title-word overlap, floor _MATCH_FLOOR 0.5) fuzzy ratio host’s poll option text vs the songs the music bot played which played song each option is (title, artist, link on the card) KEEP (manual) MODEL-HAIKU Keep an exact folded-title equality as the free accept. Else one classify per poll option with labels SONG1..SONGn + NONE over the unused played songs (“Title - Artist” each), so a candidate list is the closed label set. Cache by (night, option text). manual, ~1 night/week, ~20-40 calls/night model error or NONE: the option keeps its own text (today’s miss path) no (old KEEP)
option_song (_SUFFIX_RE “ - player”, _LOOSE_FEAT_RE “ft X”) delimiter regexes host-typed option title vs feature vs player name KEEP MODEL-HAIKU fold into the same call: when the option matches a played song, the played song’s title and credit win, and the shorthand parse matters only on NONE. On NONE, a small Haiku JSON extract {title, feature} is optional; low value. Today _SUFFIX_RE cuts any “ - X” tail, so “Song - Remix” loses “Remix”. in the row above regex no
sides_from_titles (_SCORE_RE) two numbers next poll’s title which option is side A KEEP KEEP-CLOSED number parse 0 default True / alternation no

Structural: poll_rows match in question (operator argument), apply_fixes startswith (operator argument), archive_sql, jockie_songs via parse_jockie (another partition).

scripts/riaa_credit_audit.py (203 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
_SAME_RECORDING + _recording_key in accept phrase list of same-recording brackets Deezer title vs RIAA title same recording or “different recording” KEEP (manual) MODEL-HAIKU Exact key equality stays the accept. On a mismatch, one classify SAME / DIFFERENT over the two titles, with the _SAME_RECORDING phrases as examples of SAME and “Remix”, “Live”, a re-recording as DIFFERENT. Reuse the C6 pattern (chart_ages._matching_pick). manual; ~264 rows per run, a residue of tens model error: reject (prefer absent; a wrong entry hands out a plaque) no
_NOT_AN_ACT_NAME (soundtrack, orchestra, tribute, karaoke, cover, band, ensemble) word list on a name Deezer contributor name reject the candidate KEEP MODEL-HAIKU classify ACT / NOT_ACT per guest name, list fed as examples. Keep the regex as an extra reject (both must pass). “band” also rejects real acts (“Band of Horses”); the model admits them. manual, a few per run model error: regex verdict no
credit_matches / riaa._credits_act credit match Deezer lead vs RIAA cell lead check KEEP OWNED:W19-C1 consumer of the artist_watch matcher manual — no

Structural: r.format.strip().upper() == "SINGLE".

scripts/competitor_scoreboard.py (940 lines)

Runs twice a day inside the ops-monitor GitHub job, not in the bot. It writes competitor_match events and feeds the ops-monitor “slow lane” finding (WIN_FLOOR 0.5 over MIN_MATCHES 10). It does not change what the bot posts.

symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
split_matchup no-separator branch (m.casefold() not in text.casefold()) substring heuristic our matchup + our post “BTS” (act) vs “Taylor Swift Patient Zero” (title + act) none MODEL-HAIKU (in the same_story call) The -- / - / “by” splits read our own field format (structural). Only the no-separator guess is semantic; the pair judge reads the raw matchup instead. in same_story — NEW
subject_matches / _artist_named (mn.artist_grounded_in_wire, mn.title_grounded_in_wire) wire grounding their tweet subject present none OWNED:W19-C1 consumer of the C1 grounding readers. In the batch below it becomes the NOMINATOR for the pair judge. — — NEW
_work_kind (_ALBUM_RE, _SONG_RE) word regex both texts album vs song conflict -> reject none MODEL-HAIKU in the pair judge in same_story — NEW
certs (_CERT_BOUND_RES 15-verb list, _CERT_CONTEXT_RE) verb list binds a level word both texts a cert level that agrees or conflicts decides the pair none MODEL-HAIKU in the pair judge. The level vocabulary (gold / platinum / diamond / multiplier) is closed; the verb list that decides “is this level a certification here” is not. in same_story — NEW
same_story final fallthrough (no figure: same chart or no chart) and the act-only rules (named, soft_kind) rule ladder pair pair or not none MODEL-HAIKU ONE classify SAME / DIFFERENT per nominated pair (subject match + time window + no hard number conflict). Input: our matchup, our 120-char preview, their tweet, both chart keys from chart_keys, both figure sets. Cache by (our event time + matchup, tweet id). The docstring’s rule list goes in the task as the definition. Axiom: ~80 tweets per run, 9 runs in 7 days, 161 pairs + 82 misses in 7 days. Estimate ~30-100 pair calls/day. model error: today’s rule ladder NEW
is_story cue admission (mn.chart_cue_strength >= 1) cue list their tweet counted as a MISS only if it passes none MODEL-HAIKU (or Jev Noul) A tweet with no cue never reaches the miss count: an admission prefilter deciding alone. Ask “is this a music chart / streaming / sales / certification numbers story” per non-reply tweet, cached by tweet id. Jev would fit (per-row shape label, no world facts), but the job has neither key today. ~160 tweets/day (Axiom: ~80 per run x 2) model error: the cue rule NEW
is_story is_retrospective frames regex their tweet drop look-backs none OWNED:W19-B3 — — — NEW
charts_relation (_BILLBOARD_GUESS, _BILLBOARD_FAMILY, _vague, _is_source) set logic on chart keys keys from mn.charts_named / claim_platform same / conflict / unknown none KEEP-CLOSED set math on our own chart keys. The text -> key step is in utils/music_news (A2/A12, another partition). 0 — NEW
figures_agree + counts (_COUNT_RE, _SCALE, _CHART_NAME_RE, FIGURE_TOLERANCE) number parse + 2% tolerance both texts a count conflict rejects none KEEP-CLOSED number check (brief: exact on a closed format) 0 — NEW
ranks / is_list / list_entries (_RANK_RE) “#5 / No. 5” both texts position agree / list split none KEEP-CLOSED number format 0 — NEW

Structural: COMPETITORS, OUR_EVENTS, _MATCHUP_SEP_RE / _MATCHUP_BY_RE (our own matchup format), tweet_to_their_post ("RT @" prefix, handle set), dedupe_ours (exact key), join_crossposts (time join).

scripts/called_shot_scorecard.py (242 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
kalshi_settled_units "equivalent" in title.lower() substring Kalshi KXALBUMEQUIV market title skip the odd pure-sales ladder KEEP KEEP-CLOSED upstream title of a templated series (a fixed vocabulary) 0 skip no
apply_billboard_units _key fold exact fold join album, artist attach the published unit count none KEEP-CLOSED exact identity join 0 no match: blank no

Structural: dated_label.upper(), accent_columns.

scripts/gen_yearbook.py (367 lines)
symbol pattern (short) input decision old verdict new verdict shape + input (list fed) calls/day fail direction NEW?
name.lower() in current_names exact name equality a departed member’s name vs current names skip the alumni portrait (same person on a new account) structural KEEP-CLOSED exact fold. Person identity: a model must not infer it (the names/aliases privacy rule). 0 skip no

Structural: avatar a_ prefix (animated hash), _CHARACTER_LINES (our archetype labels).

scripts/gen_yearbook_cards.py (705 lines)

Structural only: "deleted user" (Discord’s fixed placeholder name), _HOLO tier enum, name_sort / _name_key fold (sort only).

Other scripts, structural only

versuz_recap.py (SQL + argparse), gen_market_banner.py (colours, font paths), generate_help_examples.py (prompt lists, no decision), refresh_patrons_wall.py, build_share_doc.py ("#" in esc markdown escape), fit_chart_decay.py (SQL), tripo_props.py (glTF keys), arcade_artist_pilot.py, arcade_landmark_distances.py, arcade_loco.py, arcade_rk_mocap.py, audition_voice.py, run_evals.py (the --filter operator argument only).

Measurements

All against ['tootsies'].

# query (short) window result what it decides
1 typesafe_api count by purpose, surface 7d and 30d 68 calls, all engagement_labels / stats, 0 failed; first 2026-10-18 09:00:03Z, last 22 s later Jev went live on 2026-10-18. guess_grade and mention_shadow have made 0 Jev calls.
2 typesafe_shadow by purpose 30d 0 rows No agreement data exists yet for #4311 or #4406.
3 stats_seed last 6 rows, detail 7d 2026-10-18: labels_seen 1452, by_model 1350, no_text 102, fallback 0, flips question 76, laugh 13, hype 110. Earlier days: no label fields. Jev disagrees with the shape rule on 5.6% (question) and 8.1% (hype) of messages. Only today’s catch-up was labeled, so labeled_messages == messages is false for every member until a full rebuild. L3-L5.
4 readers of *_labeled keys (code grep, not Axiom) — 0 readers; utils/playercard.py lines ~896-901 read questions, caps_messages, laugh_messages The shape rules decide every shown stat alone. L3-L5.
5 guess_game_* / songbomb_* 30d songbomb_started 29, answered 25, expired 3, guess_game_started 1; last 2026-09-18 20:22Z /guess traffic is near zero; Jev title grading has never run live. L1 volume.
6 x_game_round / x_game_answer 30d 0 The X game is idle.
7 claude_api purpose classify_abuse, mention_intent 7d 59 and 10; last abuse call 2026-10-17 17:12Z The mention shadow had no mention after the key went live.
8 arcade_room action=cities 7d 25 refreshes, all ok, avg 13.2 cities memory_cities volume for the Haiku confirm.
9 competitor_match by kind 7d match 161, miss 82, run 9 competitor_scoreboard volume.
10 competitor_match kind=run count 7d 60-85 tweets per run ~160 competitor tweets/day for is_story.
11 fighter_model by reason, provider 30d safety_reject / openai 29 (FINAL); no safety_reject_xai The substring rule sends 29 members to a final refusal a month. The event keeps only “HTTP 400”, so I could not check whether any were false.

Proposed batches

Merged into the Part 0 batch plan.

Notes

  1. The #4318 labels have no reader. The seed writes questions_labeled, laugh_messages_labeled and hype_messages_labeled, and a code comment says a reader uses them when labeled_messages == messages. No code reads them (grep over cogs/, utils/, scripts/). The player card shows the shape-rule counts. Also, only the 2026-10-18 catch-up was labeled, so the equality is false for every member until one full labeled rebuild runs. This is the main answer to the brief’s Jev-caller question.
  2. The Jev callers have almost no production evidence yet. The key went live on 2026-10-18 09:00Z. guess_grade and mention_shadow have 0 typesafe_api events and 0 typesafe_shadow events. /guess has had no round since 2026-09-18 and the X game none in 30 days. So no measured miss rate exists for #4311 or #4406 in production.
  3. guess_grading fast paths stay code. bare_answer (fast win), pool_title_hits (fast miss) and title_contained (HARD rule) decide alone, but each is an exact fold on closed round data, and the docstring measured Jev worse on the bare-answer shape. One risk to record: a one-word pool title used as chat counts as a second title, so a real answer can lose (safe direction: the player types again).
  4. The fighter safety rule is a parsing problem, not a model problem. 29 FINAL safety_reject in 30 days come from a substring test on the error body. Reading the provider’s JSON error code exactly is the closed-set fix. The Grok list (“reject”, “policy”) is wider than the OpenAI one. Not filed: I could not see the error bodies.
  5. Old audit coverage. The old audit listed the manual scripts (riaa_credit_audit, called_shot_scorecard, music_drop_manual, versuz_card_manual, gen_yearbook) as KEEP for “manual”. It did not have competitor_scoreboard (2026-10-04), memory_cities (2026-09-25), the arcade, or the three Jev modules; those are NEW here.
  6. artist_roster.stale_entries has no production caller. Dead code, structural.
  7. Cross-partition owners. L1, L2 (cogs/games.py) and L3-L5 (utils/engagement.py, utils/playercard.py) are in files another partition reads. If that partition also lists them, merge the rows under one batch owner; P6b-1 and P6b-2 are disjoint files.