- The premise rule is narrow on purpose: a page may never assert a vendor ships no first-party product, server or docs for X when the page's own captured search results contain a vendor-domain result about X.
scripts/pseo/premise.mjsenforces it as a 14-pattern regex battery plus a structured-field check plus a silence obligation — not a single string match. - The retraction ledger (
docs/seo/data/connect_ledger_contract.json), read programmatically rather than summarized in prose, holds 23 separate "vendor ships no first-party X" sentences retracted across 12 vendor systems, every one dated the same day: 2026-07-25. One entry — Etsy's — labels itself "Fifth recurrence of this defect class" inside that single sweep. - Three project documents (
CLAUDE.md,bench-template-spec.md,converter-template-spec.md) all asserted this defect "shipped or survived four times." Checked against the ledger's own count, that number did not reconcile — no enumerated breakdown anywhere in the repository adds up to exactly four — so all three docs were corrected to point at the ledger (commit3080ba1, the day this piece shipped). - The two named incidents the gate is written against — a deleted Zendesk page and a shipped WooCommerce page — exist only inside the data layer's own retrospective account. No separate git commit ships or deletes either page; both the gate and the fully-corrected corpus land together in one commit. That is stated here as what git can and can't confirm, not smoothed over.
01 The easiest false claim to write is an absence
Two sibling pieces on this journal are about pages that state something wrong and about a live page that almost got deleted by mistake. This one is about a narrower, sneakier failure mode: a page that gets everything else right and is false about exactly one sentence — the sentence claiming a vendor doesn't offer something. "Nobody at Zendesk ships an MCP server for this" reads like a clean competitive angle. It is also a testable claim, and the evidence that tests it is sitting in the same row's own search-result capture, unread.
This repository is private, so what follows are receipts you cannot click — file paths, commit hashes, and a retraction ledger, quoted verbatim, that you're welcome to ask us to walk through live. Every fact below is checkable against this repo as of 2026-09-02, including a live gate run and a node one-liner I ran against the ledger file myself while writing this.
02 The rule, and why ranking difficulty is not the same test
The rule, stated identically in three places in this repository — CLAUDE.md, scripts/pseo/premise.mjs's own header, and docs/seo/data/connect_ledger_contract.json's premiseGate block — is this:
A page may never assert that a vendor ships no first-party product, server, app or documentation for X when that page's own captured SERP contains a vendor-domain result about X.
The reason this needs its own gate, rather than living inside the existing ranking-difficulty check, is that the two questions look alike and aren't. A keyword can be genuinely HARD to rank for — the vendor holds the whole top of the page — and a page can still be lying about whether the vendor ships anything at all, or genuinely rankable while resting on the exact same lie. premise.mjs's header states the mechanism plainly: ranking difficulty stays with connect_serp.json's composition test (vendor holds #1 and #2 with no UGC above #4, or three-plus distinct first-party surfaces in the top 10); mere presence of one vendor-domain result does not make a keyword HARD, and treating presence as a ranking signal would have pruned six of ten queued rows for no ranking reason. Presence is the input to a completely separate test — this one — about truth, not difficulty.
03 How the gate actually reads a page
checkPremise() doesn't look for one banned phrase. It runs three independent channels against every text field a page and its underlying row carry — page copy, connect_keywords.csv notes, blocked_reason, unique_data, and every record's pageWorthyReason and notes:
| Channel | What it catches |
|---|---|
| 1. Prose | A 14-family regex battery (NEGATIVE_EXISTENCE_PATTERNS) — "ships no," "does not offer," "has yet to ship," "nobody at X," and ten more paraphrase shapes — anchored so the vendor must sit near the negation and the negated object must be an MCP surface term, not an unrelated noun like a commit or a release. |
| 2. Structured | The literal field vendorShipsFirstPartyDocs: false in connect_mcp.json, checked against the same captured surfaces — the same assertion in a different shape. |
| 3. Silence | For directory and curation pages specifically — whose entire job is telling a reader what to use — never mentioning a first-party surface the page's own capture (or the declared ledger) contains is itself a hard failure, promoted from a warning in 2026-07-25 for exactly this reason: every one of the regex bypasses found by that date was a way of not saying a true thing, and none of them can satisfy an obligation to say it. |
The header comment is candid about channel 1's limits: it is idiom matching, not semantic understanding, and it names eleven specific replayed sentences it does not catch — "there is no WooCommerce-native MCP server," "WooCommerce's MCP story is nonexistent," a claim split across a question and its answer — precisely so the list "can never silently rot" into a false sense of coverage. That's why channel 3 exists: an evasion of channel 1 is still a failure to say a true thing, and silence catches that regardless of phrasing.
export const NEGATIVE_EXISTENCE_PATTERNS = [
{ id: 'ships-no', re: /\b(?:ships?|shipped|publishes?|published|offers?|provides?|maintains?|releases?|builds?)\s+(?:up\s+)?no\b/i, note: '"<vendor> ships no …"' },
{ id: 'has-not-shipped', re: /\b(?:has|have|had)\s+(?:not|never)\s+(?:yet\s+)?(?:shipped|published|released|built|launched)\b/i, note: '"has not shipped a …"' },
{ id: 'there-is-no', re: /\bthere\s+(?:is|are|was|were)\s+(?:currently\s+|still\s+|as\s+yet\s+)?no\s+(?:first[-\s]?party|official|native|vendor[-\s]|1st[-\s]?party|such\s+thing)/i, note: '"there is no official …"' },
// … 11 more paraphrase families, each documented against the exact sentence it exists to catch
];
04 A retraction is data, never prose
A gate that scans every sentence has an obvious failure mode: how do you correct a false claim without restating it? An early version of this gate let a marked quote — "the previous note read …" — pass unscanned, on the theory that a disowned claim isn't an assertion. Three separate bypasses were found through exactly that door: an endorsement idiom that authorised a quote instead of retracting it, chained quotes daisy-chaining off one marker, and an unterminated quote exempting the rest of a sentence. The fix deleted the exemption entirely and moved retraction into structure:
"rule": "A retraction is DATA, never prose. A withdrawn claim appears ONLY as the
value of a field named exactly retracted_claim, inside a retractions[] array. It
appears in no free-text field anywhere — not quoted, not paraphrased, not summarised.",
"gateContract": "scripts/pseo/premise.mjs skips the retracted_claim key BY SCHEMA —
the field's name is the disclaimer. EVERY other field, including corrected_to and
layer, is scanned normally with NO exemption of any kind."
The implementation matches the claim: collectScannableText() walks every field of every object it's handed and emits every string it finds as scannable prose — with exactly one skip, a key named literally retracted_claim, and only while already inside a container keyed retractions. A field named retracted_claim anywhere else is prose wearing a schema's name, and gets scanned like anything else. There's no character window, no marker phrase, no quote-pair parsing — the entire exemption surface is one key name in one specific position.
05 The two incidents the gate is named after
The gate's own header comment names two specific rows as the reason it exists, and the ledger's negativeClaimForm field names them by ID:
An absence claim must be NARROW and DATED … Undated, open-ended absence claims are not permitted in any field — that form is what produced defects B2 (Zendesk) and NEW-B1 (WooCommerce).
B2's claim, per the retraction record: "Zendesk ships no first-party MCP server and publishes no first-party MCP documentation" — contradicted by a live Zendesk-run marketplace MCP listing. The record notes the page that carried it was deleted. What's more striking is where the same falsehood turned up a second time, in a second layer, per a separate retraction entry for the same defect: "the original B2 falsehood, found still alive in the record layer months after the page that carried it was deleted" — this time inside connect_mcp.json's pageWorthyReason field for a different, still-live Zendesk record, a field nobody had checked because the page itself was already gone.
NEW-B1's claim was "Automattic ships no first-party WooCommerce MCP," carried in both connect_keywords.csv notes and connect_mcp.json's vendorShipsFirstPartyDocs: false flag — while the row's own capture had developer.woocommerce.com at #1 and woocommerce.com at #3. WooCommerce ships an official, write-capable MCP integration in core; the retraction record even states the shipped page "still carries the defect — rewrite pending" as of the date it was written. The live page today carries no such claim — it opens by naming WooCommerce's own MCP integration first, in the second sentence.
06 23 claims, 12 vendors, one day
Both named incidents are true and specific. They are also a small fraction of what the ledger actually records. connect_ledger_contract.json's retractions[] array holds 31 entries total — I parsed it directly rather than trusting a prose summary, filtering to the sentences that fit the exact claim shape the gate exists for ("<vendor> ships/publishes no first-party/no MCP/no docs"). That leaves 23 distinct retracted claims across 12 vendor systems — WooCommerce, Google Sheets, Adobe/Magento, Etsy, Meta (ads and WhatsApp), Amazon SP-API, Zendesk, Airtable, Mailchimp, Printful, Recharge, and SendGrid — and every single one carries the same retracted_on date: 2026-07-25.
The entries are candid about their own repetition. Etsy's reads: "Etsy ships and documents a first-party Dev MCP server … Fifth recurrence of this defect class — the pageWorthyReason layer had never been swept before that date." Amazon SP-API's notes that "the record's own platformFirstPartyMcp block already said exists:true — the record contradicted itself in two adjacent fields for three passes." Mailchimp's says nearly the same thing in different words: "the two files disagreed with each other in writing for three passes." These aren't hedged estimates — they're counts the ledger keeps of itself, and none of them says "four."
07 Checking our own "four times"
Until the day this piece shipped, three documents in this repository stated a specific number for how often this defect recurred. CLAUDE.md: "This defect shipped or survived four times, each in a data layer the previous sweep had not touched." bench-template-spec.md: "CLAUDE.md records a premise defect that shipped or survived four times in Cat 1." converter-template-spec.md: "the defect class CLAUDE.md records as having shipped or survived four times in /connect." Three consistent citations of the same round number.
A fourth document, docs/seo/category-research-2026-07-25.md, also contains the phrase "wrong in the dangerous direction for the fourth time" — but reading it in context shows it describes a completely different defect: inferring a MEDIUM SERP verdict from a rule instead of a live capture, the subject of CLAUDE.md's separate "never infer a SERP verdict" rule, not the premise rule this article is about. It's an easy citation to misattribute, and worth naming as a dead end rather than folding it in to pad the count.
What I could not do, after reading every premise-related passage in this repository, is reconstruct an enumerated list of exactly four incidents that the "four times" figure is counting. What the ledger itself counts, on the days it counts anything, runs past four inside a single sweep — Etsy's entry alone claims to be the fifth recurrence of the defect within one field type, in one day, well before the sweep reached vendor twelve of twelve. The honest statement is not "the real number is actually N" — I don't have grounds to assert a corrected total either. It's narrower than that: a specific, quotable number appeared three times in this repository's own documentation, unsourced to any enumerated list, and it didn't match the one place that does count concretely. That is, on its own terms, close to the shape of claim this entire gate exists to catch — a stated fact that nobody checked against the record sitting right next to it. So we treated it the way the gate treats a page: all three documents were corrected the same day this piece shipped (commit 3080ba1), and they now point at the ledger's retractions[] as the countable record instead of quoting a number from prose.
08 What the commit history will and won't confirm
Everything in the retraction ledger is real, structured data, sitting in a committed file, with real contradicting URLs. What I want to be precise about is a narrower claim: whether the B2/NEW-B1 story is a chronology git can independently confirm, the way the sibling pieces on this journal confirm a bug landing in one commit and getting fixed in another, hours or days apart.
It is not, and the honest way to say that is directly. git log --follow on scripts/pseo/premise.mjs shows exactly two commits ever touching it: its creation, 9c14c4c ("feat(pseo): /connect section + premise & unique-asset truth gates," 2026-07-25 22:56:58 +0800), and a later version-bump commit that trims 23 lines and adds none of the incident narrative. docs/seo/data/connect_ledger_contract.json and src/data/connect/woocommerce-mcp-server.mjs — the shipped page itself — both appear in git history for the first and only time in that same single commit, already in their corrected state, alongside the ledger's own note that the page's "rewrite" was still pending. And no commit in this repository, at any point, creates or deletes a file matching a Zendesk connect page — git log --all -S "connect-claude-to-zendesk" returns exactly one hit, inside the data files created in 9c14c4c, describing it as already deleted.
So what git actually shows is one commit that stood up an entire section — the pages, the gate that checks them, and a full retrospective ledger explaining why the gate exists — all at once. The incidents the ledger describes are real in the sense that the corrected facts are real and independently verifiable (Zendesk's marketplace listing, WooCommerce's developer docs both resolve). Whether they happened as a multi-step chronology this repository's commits can reconstruct is a different question, and the answer is no. CLAUDE.md records that this queue ran on a Google Sheet before queue.json existed — "The Google Sheet is dead" — which is a plausible, unglamorous explanation: whatever happened, happened before this repository's git-tracked pipeline did, and what landed in 9c14c4c is the aftermath arriving in one piece, not a live incident this repo's history can walk through step by step.
09 What the gate says right now
I ran the standing audit live while writing this (node scripts/pseo/check-pages.mjs, 2026-09-02): connect: 0 violation(s), 43 warning(s). The corpus carries zero premise violations today. The 43 warnings are the deliberately non-blocking noise floor this gate produces by design — unmeasured rows, scoped-and-acknowledged negatives, roster members that resolve to no known vendor — reported, never promoted, because CLAUDE.md is explicit that promoting all of them on day one would open an alarm channel with dozens of non-defects in it.
One gap remains open and admitted rather than hidden: premise.mjs resolves its data files from module-level constants pointed at connect_* — it does not check /flow pages at all. scripts/pseo/check-flow.mjs says so in its own comment: "premise.mjs is still hardwired to the connect_* data files, so NO premise check ran on them." Today that's a warning, not a failure — the live run reports flow: 0 violation(s), 1 warning(s) — because zero /flow pages currently exist to hide anything behind. check-flow.mjs's own comment is explicit that the day a /flow page ships, that warning is designed to become a hard failure automatically, precisely so the gap can't quietly become permanent the way the gap it's patterned after almost did.
None of this is a story about a rule that was written once and has been clean ever since. It's a story about a defect shape that recurred enough times, in enough different data layers, that this repository built a retraction schema just to be able to correct it without re-committing it — and then, in describing how often that happened, produced a number that didn't survive the same check it built the gate to perform — and retired that number the day the check was run.
If you're evaluating whether an AI-native pipeline can be trusted to check its own work, the sharper question isn't whether it has a rule against a specific lie. It's whether it will apply that same rule to its own retelling of the rule's history — and say so when the number doesn't hold up. That's the kind of system design we do.
Source table
| Claim | Source (this repo) |
|---|---|
| The premise rule, stated identically in three places | CLAUDE.md ("The premise rule"); scripts/pseo/premise.mjs header; docs/seo/data/connect_ledger_contract.json (premiseGate.rule) |
| Ranking difficulty vs. premise truth are separate, non-mergeable tests | scripts/pseo/premise.mjs header ("GOVERNING RULE, owner decision, 2026-07-25") |
| Three-channel gate (prose / structured / silence), 14 regex families, 11-of-20 documented bypasses | scripts/pseo/premise.mjs (NEGATIVE_EXISTENCE_PATTERNS, checkPremise, header comment) |
| Retraction-is-data-not-prose schema and the three bypasses it replaced | docs/seo/data/connect_ledger_contract.json (retractionSchema); scripts/pseo/premise.mjs (collectScannableText, RETRACTED_CLAIM_KEY) |
| B2 (Zendesk) and NEW-B1 (WooCommerce) named as the gate's origin incidents; Zendesk falsehood recurring in a second layer months later | docs/seo/data/connect_ledger_contract.json (premiseGate.negativeClaimForm; retractions[] entries for connect-claude-to-zendesk and reminia-zendesk-mcp-server) |
| Live WooCommerce page carries no false claim today | src/data/connect/woocommerce-mcp-server.mjs (read in full, 2026-09-02) |
| 23 "ships no first-party X" claims across 12 vendors, all dated 2026-07-25; Etsy's "fifth recurrence"; two independent "three passes" notes (Amazon SP-API, Mailchimp) | docs/seo/data/connect_ledger_contract.json (retractions[], parsed programmatically, 2026-09-02) |
"Four times" asserted in three places (retired in commit 3080ba1, 2026-09-02) | CLAUDE.md ("The premise rule"); docs/seo/bench-template-spec.md:195; docs/seo/converter-template-spec.md:157 — as of the parent of 3080ba1 |
| "Fourth time" at category-research-2026-07-25.md:254 is a different defect (SERP-verdict inference, not the premise rule) | docs/seo/category-research-2026-07-25.md:245-256 (Shopify GraphQL cookbook rejection) |
premise.mjs, the ledger contract, and the WooCommerce page all originate in one commit; no commit ever creates or deletes a Zendesk connect page | git log --follow --oneline -- scripts/pseo/premise.mjs; git log --oneline -- docs/seo/data/connect_ledger_contract.json src/data/connect/woocommerce-mcp-server.mjs; git log --all -S "connect-claude-to-zendesk" (all run 2026-09-02) |
| Pre-git-history queue ran on a Google Sheet | CLAUDE.md ("pSEO queue: queue.json is the source of truth") |
| Live gate run: connect 0 violations / 43 warnings; flow 0 violations / 1 warning | node scripts/pseo/check-pages.mjs, run live 2026-09-02 |
| /flow premise gap, self-escalating once a /flow page ships | scripts/pseo/check-flow.mjs (header comment + premise-gap check) |
07 Frequently asked questions
- What exactly does the premise gate check?
- One specific claim shape: that a vendor ships no first-party product, server, app or documentation for a given system. scripts/pseo/premise.mjs checks that claim, wherever it appears — page copy, row notes, a blocked_reason field, a structured vendorShipsFirstPartyDocs:false flag, or silence on a directory/curation page — against the page's own captured search results in connect_serp.json and against a separately declared ledger field, platformFirstPartyMcp. If either source shows the vendor has a first-party surface, the claim is a violation. Ranking difficulty is a completely separate test and is explicitly barred from being merged with this one.
- How many times has this defect actually happened?
- Three project documents in this repository (CLAUDE.md, bench-template-spec.md, converter-template-spec.md) stated "four times." Reading docs/seo/data/connect_ledger_contract.json programmatically instead of by prose summary turns up 23 distinct "vendor ships no first-party X" sentences retracted across 12 vendor systems, all dated 2026-07-25 — and one of those, Etsy's, calls itself "the fifth recurrence of this defect class" inside that same sweep. The two counts did not reconcile, so rather than picking whichever number sounded better, this article states the discrepancy directly — and the same day it shipped, all three docs were corrected (commit 3080ba1) to count from the ledger instead of quoting the prose.
- Are the Zendesk and WooCommerce incidents the gate is named after real, checkable events?
- The claims and their corrections are real and checkable — they live in the retraction ledger with verifiable contradicting URLs (a Zendesk marketplace listing, WooCommerce's own developer docs). What is not independently checkable is a chronology of them happening across separate commits: git shows the /connect section, the premise gate, the retraction ledger and the (already-clean) WooCommerce page all landing together in a single commit, 9c14c4c, on 2026-07-25, and no commit in this repository's history ever creates or deletes a Zendesk connect page. The honest reading is that whatever happened, happened before this repository's git-tracked pipeline existed — CLAUDE.md records that the queue ran on a Google Sheet before queue.json existed — and what git shows is the aftermath landing at once, not a live incident unfolding in commits.
- Why does the gate treat a retraction differently from every other sentence on a page?
- Because retracting a false claim by quoting it — "the previous note read X" — puts the false sentence back into a field the gate scans, which is exactly how three earlier bypasses were found. The fix is structural, not lexical: a withdrawn claim may live only as the value of a field named exactly retracted_claim inside a retractions[] array. premise.mjs skips that one key by schema and scans every other field in the object, including corrected_to, with no exemption. Prose is never allowed to name a false claim in order to forbid it; the true statement stands alone, and the history lives in structured data instead.