Repository navigation
Claim check for any campaign in check_campaign and the ready-to-send check - #28
Merged
Merged
Conversation
…check Dead links, figures not on the pages their paragraph links to, and figures with no source, from the Briefings fetcher's guarded reads in a check-claims job. Bounded per check, kept per campaign content hash, warnings by default, with a per-newsletter requireClaimCheck that send and schedule read.
Owner
Author
|
CI: the self-hosted runner had not picked up this PR's check job when this was written (it was still queued), so there is no CI result yet. Local results are in the description: full suite 3957 pass, 0 fail, 4 skipped; ESLint clean; size budgets pass. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What it checks
This adds a claim check for any issue: an editor draft, an agent's
create_campaign, a briefing's draft or an import. It runs as part of the ready-to-send check. It is the code half of the Briefings engine's check step, without a briefing's records: the issue's own links are its sources. The design note isdocs/CLAIM-CHECK.md.It reads every paragraph, list item, heading and table cell, plus the subject and preview text, and reports:
unsureLinks). That matches the existing link check (judgeStatus): those answers don't tell us whether a reader can open the link.Figure formats. These all count as the same figure:
$1.7 billion,1.7bn,$1.7B,1,700 millionand1,700,000,000. So do8.0%,8%and8 percent. A figure written with a scale also matches a more exact figure on the page that rounds to it, so1.71bnmatches1,712,000,000.Ignored:
issue 12,page 40)Q3,A320,COVID-19,v1.2.3)The check is code only and makes no model call. The doc describes a possible model step as a later option and leaves it out. It would check that a sentence says what its page says, read figures written in words, and read tables in "$ millions".
How it is bounded
Per check: 25 links judged, and 12 pages read whole, starting with the pages that figures need. 60 requests in all, robots.txt and redirects included. No request starts after 25 s. Anything not reached is marked "not checked", never failed.
Per request: the Briefings fetcher (
lib/briefings/fetch.jscreateFetcher), reused as is:FlockStack-Briefings/1.0 (+https://flockstack.io/docs/briefings-bot)The briefings-bot docs page now says it also reads the pages an issue cites when the issue is checked.
Per account: at most 30 checks started an hour.
Cache: the result is kept in a new
ClaimCheckrow per campaign for a day, keyed bycampaignContentHash(the existing campaign content hash). The same words never fetch again; any edit checks again. The hourly cron deletes rows nobody has looked at for a week, and the account purge deletes them too.No job needed: an issue with figures but no links is answered at once. An issue with neither passes at once.
Blocking decision
Findings are warnings by default. The check runs in its own QStash job (
check-claims), started by the ready-to-send check like the link check. A send never starts it, never waits for it and never fetches anything for it.A newsletter can turn on
requireClaimCheck, with the new "Claim check" switch in newsletter settings or theupdate_newsletter/get_newsletterfield. When it is on,getSendBlockerrefuses withsend_check_faileduntil a finished check for the current content hash has no failures.getSendBlockeris behindsend_campaign,schedule_campaign, the dashboard's Send, approved agent sends andget_campaign'ssendProblem. The ready-to-send item shows as a blocker while the check runs or fails. This costs one indexedClaimCheckread, only for those newsletters. "Couldn't be confirmed" never blocks. There is no per-campaign option and no plan gating.Known limit: a scheduled issue is checked when it is scheduled. If it is edited after that, it is not checked again when its send time comes.
Parity
Registry, REST and MCP:
check_campaigngains three things:claims: state, required, passed, deadLinks, unsureLinks, unmatchedFigures, unconfirmedFigures, uncitedClaims, counts, figuresChecked, linksChecked, linksTotal, pagesRead and checkedAtclaimsitem in its listchecking, which says whetherlinks,claimsor both are still runningGET /api/v1/campaigns/{id}/checkand the MCP tool are the same action; the tests cover the REST API.Dashboard: the review step's "Ready to send?" card and the approval card show the item through
PanelReady.PanelReadynow shows a spinner only on rows that are still running.use-ready-checknow polls for up to 50 s instead of 30 s, to cover the job.Link check: the existing link check is unchanged. When the Links row already names the same dead links, the claim row says "(see Links)" instead of listing them again.
Tests
New
tests/claim-check.test.mjswith 16 tests. Fetch and the DNS resolver are stubbed, so nothing reaches the network. They cover:check_campaignand the REST APIrequireClaimCheckrefuses send and schedule while the check runs or fails, and allows the send after a fixPanelReadyspins only the running rowOne existing expectation changed, because changing that behavior is the point of this PR.
tests/ready-check.test.mjsassertedisChecking === falseafter the link job ran.isCheckingnow also covers the claim check, so the test asserts thatcheckingis["claims"], which means the links are done.Full suite
npm test: 3961 tests, 3957 pass, 0 fail, 4 skipped (the existing skips). It ran around 23:40 UTC, inside the known automations-v2 clock window, and that test passed anyway.A few small fixes landed while the suite was running: exact month-name matching, links kept on headings, a case-insensitive https check and one new test. After them,
tests/claim-check.test.mjs,tests/ready-check.test.mjsandtests/ready-check-action.test.mjswere run again: 51/51 pass.npx eslinton every changed file is clean.Bundle impact
Both builds use
FLOCKSTACK_CLOUD_BUILD=trueand the samenode_modules. Each runsnpx opennextjs-cloudflare build,npm run build:apiandnpm run bundle:app, once on origin/main (23b1e77) and once on this branch:perf-budgets.jsonis unchanged, and every budget passes. The page-reading code is in the app only behind a lazyimport()in the new job route: the Briefings fetcher, robots.txt, the page-text reader andlib/claim-check-run.js. The API Worker gets only whatcheck_campaignand the send check need: the figure rules and the claim state and send check. It does not get the fetcher.