Files
adr-sml/scripts/check-claims.mjs
T
Pouya LajevardiandClaude Opus 5 4735989f0b feat: cut /legal/privacy/ §Who can see it to four plain statements; name SML Company Ltd on the consent; close Q64 moot
Two rulings from Pouya, 2026-09-02.

(1) The section stays generic — "it over-explains technical mechanics that
belong in the evidence file, not in front of an inquirer." Deleted: the
measurement paragraph, the root-credential sentence, the SSO/federated-login
enumeration, the resource-policy clause, the "company that runs a database"
aside, the deploy-credential sentence and the three-copies summary. All of it
stays true and stays measured in AGENTS.md §7 and the evidence file, which now
maps each shipped sentence to what it rests on.

(2) The consent string names the corporation: "I consent to SML Company Ltd
storing and using the information in this form…". docs/05 §Consent text moves
with it, proven byte-identical. Two new §4 rows carry the attestations the copy
rests on.

(3) The §Who can see it approval closes via the page read-through, which is now
blocker 2 in docs/06's callout rather than a checklist line.

Q64 closes MOOT — the paragraph it was about was deleted, so it gates nothing.
The underlying gap is unchanged: §7 records root as held by Pouya, not held only
by Pouya, and nothing about root custody may be published without asking again.

Two sentences were added back under review: the shared-account disclosure, to
§Where it is stored (a storage disclosure, never named in the ruling — without
it no page said the intake sits in a shared account), and one naming SML Company
Ltd in the policy, because a consent naming a company the linked policy never
mentions is an accountability gap.

adversarial-reviewer, two rounds, 14 findings, all resolved, none declined;
nine of round 2's ten were defects in round 1's own repairs. claims-auditor
correctly deferred to cutover per D20.

Gates, exit status read: check 0 · build 0 (23 pages) · check:claims 0
(12 patterns, 33 approved strings) · check:intake 0 · og:proof 0 · lint 0 ·
lighthouse 0, worst of 23 99/100/100/100. Tripwire proven both ways — exit 0 on
the revised page, exit 1 with 5 matches on the bd282aa bytes. Regex untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
2026-09-02 12:03:47 -04:00

819 lines
41 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* `npm run check:claims` — a mechanical gate on the SHIPPED OUTPUT.
*
* Pouya's ruling, 2026-08-29, and the reasoning is the point:
*
* "Your recurring failure this project is a specific shape: you write a rule
* into a header comment, then breach it in the file below, in the same
* change set. Q53's sweep, Q54's gate four times, the D13 'cannot' phrasing
* fifty lines under your own warning about it. That is not a discipline
* problem — prose in a comment does not govern the writing that follows it.
* The pattern that actually worked was mechanical: deleting the `class` prop
* so passing one is a build error. Nobody has breached that since."
*
* So this is not documentation. It is a build error.
*
* SCOPE — `dist/**\/*.html` AND NOTHING ELSE. Not `AGENTS.md`, not `docs/`, not
* `src/`. D19 bounds review to what ships, and §4's Forbidden table says in as
* many words that it "governs published pages… It is not a word filter over the
* repository's own documentation." A register that records a forbidden phrase in
* order to forbid it must not be failed by its own quotation of it.
*
* `<style>` BLOCKS AND NON-JSON-LD `<script>` BLOCKS ARE STRIPPED FIRST, and
* that is load-bearing rather than tidy. Measured against the step-4 build
* before this file existed: a bare case-insensitive sweep for `leading` returned
* **26 hits, 25 of them `var(--leading-body)`** inside Astro's inlined critical
* CSS. A check that cries wolf on every page on its first run is a check
* somebody deletes in week two. JSON-LD is deliberately NOT stripped — a claim
* in a `<script type="application/ld+json">` is still a claim, and docs/04 says
* so: "Marking an unheld credential as held in structured data is a
* misrepresentation that happens to be machine-readable."
*
* EVERY PATTERN CARRIES ITS `incident`, PRINTED ON FAILURE. Pouya's
* instruction: "so nobody deletes one for being noisy." A pattern whose cost is
* visible and whose reason is not gets deleted; this prints the reason at the
* moment the cost is felt.
*
* AND IT SELF-TESTS BEFORE IT SWEEPS. See `FIXTURES` below. A regex that has
* quietly stopped matching passes a clean sweep forever, which is the exact
* shape of AGENTS.md Q22 — a documented control that no longer existed.
*/
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join, relative, resolve } from 'node:path';
const ROOT = resolve(import.meta.dirname, '..');
const DIST = resolve(ROOT, process.argv[2] ?? 'dist');
/* Sources whose mtime, if newer than the newest built page, means this sweep
would be reading yesterday's output. `docs/` is absent on purpose: it directs
copy but does not produce it, so a spec edit does not stale the build. */
const SOURCE_DIRS = ['src', 'public'];
const SOURCE_FILES = ['astro.config.mjs', 'package.json'];
/**
* The patterns.
*
* `re` is matched against the page's text with `<style>` and non-JSON-LD
* `<script>` removed. All are global and case-insensitive.
*
* WORD BOUNDARIES ARE NOT DECORATION ON ANY OF THESE. `\bleading\b` does not
* match "pleadings" and `\blicensed\b` does not match "licensing" — both are
* words this site publishes, and both were live false positives when the
* patterns were first drafted. Loosening a boundary here re-creates the noise
* this file was built to avoid.
*/
const PATTERNS = [
{
id: 'licensure-of-pouya',
rule: 'D13 / §4 Forbidden — the site asserts the JD and nothing further.',
incident:
'§4 opened with "Pouya is a licensed legal professional", then "a legal ' +
'professional", then an assertion that LSO marketing rules apply — three ' +
'progressively weaker forms of the same unverified claim, each surviving a ' +
'sweep meant to remove it, in the section written to stop exactly that. ' +
'§4 does not establish licence status either way.',
re: /\blawyer\b|\bcalled to the bar\b|\blicensed\b|\bpost-nominal\b/gi,
note: '"licensing" and "licence" are NOT matched — /practice/technology/ ships "IP and licensing".',
},
{
id: 'acting-for-a-party',
rule: '§4 Forbidden — implication is the risk, not just the word.',
incident:
'§4 bars "acts for clients", "represents parties", "my clients", ' +
'"my litigation practice". The site sells neutrality; a partisan verb ' +
'undercuts the central claim before it reaches the licensure question. ' +
'Settlement counsel was struck from three specs for the same reason (Q42).',
re: /\bmy litigation practice\b|\bmy law practice\b|\bmy clients?\b|\bacts for\b|\bI act for\b|\brepresents?\b/gi,
note:
'"I do not act for a party" is the APPROVED sentence (/mediation/) and does ' +
'not match. THE SINGULAR WAS ADDED 2026-08-29: the pattern read ' +
'`my clients` and `/med-arb/` was shipping "The neutral who heard my ' +
'client in caucus" in its FAQ — the possessive belonged to the counsel ' +
'voicing the objection, not to Pouya, but the gate cannot tell and the ' +
'phrase is forbidden in his voice, so the copy changed to "our client" ' +
'and the pattern widened. Same shape as the capacity-phrasing hole: a ' +
'pattern narrow enough to miss the real form of the breach.',
},
{
id: 'capacity-phrasing',
rule: 'docs/03 — when a fact is [unestablished], write around the capacity question.',
incident:
'Attempt 1 "I do not give legal advice" was flagged as an ELECTION ' +
'(entitlement withheld by choice). Attempt 2 "I cannot give legal advice" ' +
'was flagged as a DENIAL of capacity. Both audits were right: they are ' +
'opposite answers to a question §4 holds neither side of. The shipped ' +
'sentence makes no capacity claim at all.',
re: /\b(cannot|can ?not|can't|do(es)? not|don't|will not|won't|unable to|not (qualified|permitted|able) to)\s+(give|provide|offer)\s+legal advice\b|\bI\s+(?:cannot|can ?not|can't|do not|don't|will not|won't)\b[^.<]{0,25}\badvis(?:e|ing)\b/gi,
note:
'THE SECOND ALTERNATIVE WAS ADDED 2026-08-29 BECAUSE THE FIRST MISSED A REAL ' +
'BREACH. `/practice/cross-cultural/` shipped "What I do not do is advise on the ' +
'law of another country" — the election form, narrowed by jurisdiction, which ' +
'makes it worse rather than better: naming only foreign law invites the reader ' +
'to supply the domestic half. `claims-auditor` caught it and noted, correctly, ' +
'that this pattern could not, because it required the literal object "legal ' +
'advice". The verb list is deliberately tight — give / provide / offer / advise ' +
'/ render — and the window is 25 characters, so the six approved first-person ' +
'negations this site ships ("I do not act for a party", "I do not run a case", ' +
'"I will not run a process", "I do not carry a number across the hall", "I will ' +
'not convert a mediation", "If I cannot decide a remaining issue") all stay ' +
'silent. Every one of them is in the mustNotMatch fixtures below.',
},
{
id: 'designation-name',
rule: "ADRIO's own form is Chartered Med-Arbitrator.",
incident:
'"Chartered Mediator-Arbitrator" is not the name of anything ADRIC or ' +
'ADRIO confers. docs/03 §About names the correct form and the source is ' +
'docs/reference/adrio-designations.md. A designation stated in a form its ' +
'own institution does not use reads as a designation not held.',
re: /Chartered Mediator[- ]Arbitrator/gi,
},
{
id: 'rule-set-name',
rule: 'ADRIC publishes the ADRIC National Mediation Rules.',
incident:
'`docs/01` §/mediation/ said "Model Mediation Rules", which is not the ' +
'name of anything ADRIC publishes — "Model" belongs to the Model Dispute ' +
'Resolution Clause INSIDE the rules. Caught at build step 4 only because ' +
'the rules were fetched rather than named from recall (R14).',
re: /Model Mediation Rules/gi,
},
{
id: 'counts-and-tenure',
rule: '§4 Forbidden — no count of matters, hours mediated, or years in practice.',
incident:
'The site this replaces carried "Since 2009", "sixteen years", London and ' +
'New York offices and a company number, all artefacts of a purchased ' +
'template and all false. §4: the practice is new, and small true numbers ' +
'do not persuade a sophisticated GC — they invite scrutiny.',
re: /\b\d[\d,]*\s*\+?\s*(matters|mediations|arbitrations|appointments|awards)\b|\b\d[\d,]*\s+years\s+in\b|\bsince\s+(19|20)\d{2}\b|\b(ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty|thirty|forty)[\s-]years\b/gi,
},
{
id: 'superlatives',
rule: '§4 Forbidden — unverifiable as written, and they read as insecure.',
incident:
'The reason §4 gives is editorial before it is regulatory: this audience ' +
'discounts everything after a superlative. AND THE FALSE POSITIVES ARE ' +
'REAL AND KNOWN — "pleadings" contains "leading" and every inlined ' +
'stylesheet contains var(--leading-body). Both are handled, by the word ' +
'boundary and by the <style> strip. Do not loosen the boundary and do not ' +
'delete this pattern because it once matched a token name.',
re: /\bleading\b|\bpremier\b|\btop[- ]rated\b|\bbest\b|\bworld[- ]class\b|\bunrivalled\b|\bunparalleled\b/gi,
},
/* --- Beyond Pouya's minimum list, 2026-08-29. Each names a real incident in
this repository and each has near-zero false-positive surface on this
site's vocabulary. Strike any of the three and the rest still stand. --- */
{
id: 'fabricated-founder',
rule: '§4 Forbidden — the fictitious founder from the template.',
incident:
'The site this replaces carried a founder who does not exist, a ' +
'testimonial attributed to a person who does not exist, invented matter ' +
'values and fabricated office locations. This register exists because of ' +
'that, and this is the one pattern whose match would be unambiguous.',
re: /\bS\.?\s?M\.?\s+Lawrence\b|\b07452218\b/gi,
},
{
id: 'q-arb-as-a-stage',
rule: '§4 — Q.Arb is HELD. It must never read as a stage, a pathway, or pending.',
incident:
'THIS PATTERN WAS INVERTED ON 2026-08-29 AND ITS PREDECESSOR IS GONE. It ' +
'used to bar Q.Arb reading as HELD, because §4 recorded it as "commenced ' +
'August 2026"; Pouya ruled it held (obtained July 2026, date NOT ' +
'published), so every stage word now understates a real credential. The ' +
'site carried the struck forms on six surfaces — an h1 reading "Available ' +
'now, and open about the stage", a whole /arbitration/ section headed ' +
'"Where I am in the arc", /about/\'s arc section, the footer strip, and ' +
'both JSON-LD nodes. Publish "Q.Arb (ADRIC / ADRIO)" and nothing more.',
/* ~50 characters either side, per Pouya's specification. The window stops at
the next TAG boundary so it cannot leap a paragraph. It stopped at a
sentence boundary too (`[^.<]`) until the fixture below proved that too
tight: "Qualified Arbitrator. Newly commenced" splits the anchor from the
stage word across a full stop, 22 characters apart, and that is precisely
the copy this pattern exists for. The self-test caught the regression.
⚠️ THE ANCHOR MATCHES THE EXPANSION TOO, and it did not on the first
draft. Tested against the nine strings this change set removed: it caught
seven and MISSED "Qualified Arbitrator. Newly commenced — not held, and
not nearing completion." — /about/'s own arc body, which never wrote the
abbreviation. An abbreviation-only anchor cannot see copy that spells the
designation out, and prose is exactly where it gets spelled out.
KNOWN LIMIT, stated rather than papered over: this cannot catch a stage
expressed without naming the designation at all. The h1 it replaced —
"Available now, and open about the stage" — matches nothing here and no
regex over shipped HTML would catch it without firing on ordinary copy.
That one is `claims-auditor`'s. */
/* WHAT EACH ALTERNATIVE IS FOR, because none is obvious from the regex:
(1) stage word after the designation, (2) before it, (3) an acquisition
DATE near it — §4 records July 2026 and bars publishing it, and docs/03
cited this script as enforcing that before it did (the Q22 shape) —
(4) "once/when I hold it", which must take the designation as its object
or it fires on "when I hold a preliminary conference".
THE ANCHOR MATCHES THE EXPANSION because `docs/03` authorises "Qualified
Arbitrator" as publishable prose, and prose is where it gets spelled out.
KNOWN LIMIT, stated rather than papered over: this cannot catch a stage
expressed without naming the designation. The h1 it replaced — "Available
now, and open about the stage" — matches nothing here, and no regex over
shipped HTML would catch it without firing on ordinary copy. That one is
`claims-auditor`'s. */
re: /(?:Q\.?\s?Arb|Qualified Arbitrator)\b[^<\uE000]{0,50}?\b(commenc\w*|in progress|pathway|not yet|not held|nearing completion|pending|working toward|under ?way)\b|\b(commenc\w*|in progress|pathway|not yet|not held|nearing completion|pending|working toward|under ?way)\b[^<\uE000]{0,50}?\b(?:Q\.?\s?Arb|Qualified Arbitrator)\b|(?:Q\.?\s?Arb|Qualified Arbitrator)\b[^<\uE000]{0,50}?\b(?:January|February|March|April|May|June|July|August|September|October|November|December)\s+20\d\d\b|\b(?:January|February|March|April|May|June|July|August|September|October|November|December)\s+20\d\d\b[^<\uE000]{0,50}?\b(?:Q\.?\s?Arb|Qualified Arbitrator)\b|\b(?:once|when)\s+(?:I|he)\s+(?:holds?|obtains?|receives?|completes?)\s+(?:it\b|the\s+)?(?:Q\.?\s?Arb|designation)\b/gi,
},
{
id: 'c-med-arb-struck',
rule: 'C.Med-Arb is out entirely — it appears on no page (Pouya, 2026-08-29).',
incident:
'It was the strategy brief\'s "explicit long-term professional narrative" ' +
'and it shipped on /, /about/, /arbitration/ and /med-arb/ as the endpoint ' +
'of a credentialing arc. Pouya dispensed with it: he holds Q.Med and ' +
'Q.Arb, which is what med-arb requires. THE DESIGNATION IS STILL REAL and ' +
'stays in AGENTS.md §11 as a definition — this bars naming it in shipped ' +
'output, not knowing what it is. Barred in every spelling both ' +
'institutions use, because the reinstatement risk is a well-meaning ' +
'implementer reading the brief, not a typo.',
/* ⚠️ THE LEADING \b AND THE MANDATORY DOT ARE BOTH LOAD-BEARING, and this
pattern shipped without them for one run. `C\.?\s?Med-Arb` matched
"ADRIC Med-Arb Rules" as "C Med-Arb" — the C of ADRIC, a space, then the
real rule-set name — and failed the build on approved copy on /med-arb/.
The abbreviation always carries the dot; "C Med-Arb" is not a form either
institution uses. The fixture below pins it. */
re: /\bC\.\s?Med-Arb(?:itrat(?:or|ion))?s?\b|\bChartered Med-Arb(?:itrat(?:or|ion))?s?\b/gi,
},
{
id: 'struck-universal-q39',
rule: 'Q39 — the class statement about arbitral gating was FALSE and was published once.',
incident:
'§4 carried "Anyone may be appointed an arbitrator in Ontario. Nothing in ' +
'law gates the role behind a designation." Pouya checked the ' +
'counter-example and confirmed it: "My assertion was FALSE as a ' +
'universal." Ontario family arbitrators must complete prescribed ' +
'training. The scoped replacement is COMMERCIAL arbitration and it is ' +
"Pouya's attributed position, not a verified fact. Swept four times; " +
'reached a public page once.',
re: /anyone (may|can) be appointed an arbitrator|nothing in law gates|no (licence|license|designation) is (required|needed) to (be appointed|arbitrate|act as an arbitrator)/gi,
},
{
id: 'sole-administrator-q62',
rule:
'Q62 — the sole-administrative-access claim on /legal/privacy/ was FALSE ' +
'and is permanently barred from returning. The page does NOT make it now; ' +
'if this fired, something reintroduced the shape. ' +
"⚠️ IF THE SECOND ADMINISTRATOR'S ACCESS IS EVER ACTUALLY REMOVED, this " +
'pattern starts failing the build on TRUE copy, and the answer is neither ' +
'to delete it nor to work around it: re-run the verification in ' +
'`docs/reference/intake-table-access-verification.md`, rewrite the page to ' +
'the new measured truth, and narrow THIS pattern deliberately with a ' +
'Change Log entry. Q23 records the Gitea instance as jointly administered, ' +
'so that removal is live rather than hypothetical.',
incident:
'The page shipped "nobody else has access to the table. There is no team, ' +
'no assistant and no external administrator." while TWO principals could ' +
'read it. It was in dist/, `build`, `check` and this script all exited 0 ' +
'over it, and the only thing between it and a live privacy policy was a ' +
'TODO(pouya) in a JSX comment — which Astro strips. The gate was human ' +
'memory. Pouya ruled the pattern PERMANENT on 2026-09-01: it no longer ' +
'describes live copy, it bars the shape. Do not delete it, and do not ' +
'loosen it into a rule about a class. Full history: AGENTS.md entry (an).',
/* THREE CONSTRAINTS, AND THEY ARE WHY THIS IS SHAPED AS IT IS.
1. `\s+`, NOT LITERAL SPACES. `compressHTML` collapses whitespace between
tags and leaves it alone inside a text node, so the published bytes
read "nobody else has access to\n the table". A literal-space
version runs, prints `ok`, and exits 0 with the breach in `dist/`.
2. EACH ALTERNATIVE IS ONE STRING THAT REACHED `dist/`, NEVER A FAMILY.
The first draft of alternative 3 was `(?:or|and)\s+(?:outside|external)`
— four phrasings where one was published, so three had no probe and no
negative fixture, which is the freeze's stated condition.
3. ALTERNATIVE 3 IS A DELIBERATE OVER-BAR AND THIS IS ITS COST. The
truthful receipt-scoped sentence CONTAINS the published string, so any
pattern catching one catches the other — adding that truthful form as a
negative fixture failed the self-test, which is the instrument check
working. So: the clause "no assistant or outside administrator" cannot
be published here in ANY scoping, true or false, without failing this
build. That is the right trade for a phrase that has already put a
false statement on a privacy policy, and `rule:` says what to do. A
lookahead tuned to one guessed phrasing would be the speculative
pattern the freeze bars.
The window in alternative 5 is `[^\w<]{0,8}`, not `\W{0,8}`: `\W` matches
`<` and the block sentinel, so it would have been bounded by neither. */
re: /nobody\s+else\s+has\s+access\s+to\s+the\s+table|there\s+is\s+no\s+team,\s+no\s+assistant\s+and\s+no\s+external\s+administrator|no\s+assistant\s+or\s+outside\s+administrator|one\s+administrative\s+account,\s+which\s+is\s+mine|honest\s+answer\s+to\s+[^\w<]{0,8}who\s+can\s+see\s+this[^\w<]{0,8}\s*is:\s+me/gi,
},
];
/**
* THE INSTRUMENT CHECK, and it runs before every sweep.
*
* `mustMatch` — a pattern that has stopped firing passes a clean sweep forever
* and looks identical to a clean site. `mustNotMatch` — the known-legitimate
* strings this site actually publishes; if one of them starts failing, the
* pattern got looser, not the copy worse.
*
* CLAUDE.md, five times over: "a measurement is a claim about your instrument
* until you check the instrument."
*/
const FIXTURES = {
mustMatch: {
'licensure-of-pouya': [
'He is a lawyer.',
'called to the bar',
'a licensed neutral',
],
'acting-for-a-party': [
'my litigation practice',
'acts for clients',
'I act for the claimant',
'represents parties',
'my clients',
/* The singular, which the pattern missed until 2026-08-29. */
'the neutral who heard my client in caucus',
],
'capacity-phrasing': [
'I cannot give legal advice',
'I do not give legal advice',
"I can't provide legal advice",
/* The real breach the first form of this pattern could not catch. */
'What I do not do is advise on the law of another country',
'I do not advise on foreign law',
],
'designation-name': ['Chartered Mediator-Arbitrator'],
'rule-set-name': ['the Model Mediation Rules'],
'counts-and-tenure': [
'40 matters',
'120 mediations',
'16 years in ADR',
'Since 2009',
'sixteen years',
],
superlatives: [
'a leading neutral',
'premier',
'top-rated',
'the best mediator',
],
'fabricated-founder': ['S. M. Lawrence', 'Co. № 07452218'],
'q-arb-as-a-stage': [
'The Q.Arb pathway commenced in August 2026',
'Q.Arb is in progress',
'Q.Arb — not yet held',
'once I hold Q.Arb',
'my Q.Arb pathway',
/* The expansion, which the first draft of this pattern missed. */
'Qualified Arbitrator. Newly commenced — not held, and not nearing completion.',
/* The acquisition date, which no stage word caught. */
'Q.Arb, obtained July 2026',
'Q.Arb (ADRIC / ADRIO), held since July 2026',
/* The `rule` line above named these and the regex never matched them —
a documented control not covering what it is cited for (Q22's shape). */
'Q.Arb is pending',
'Q.Arb — the designation I am working toward',
'the Q.Arb process is under way',
],
'c-med-arb-struck': [
'C.Med-Arb is the endpoint',
'Chartered Med-Arbitrator',
'the ADRIC Chartered Med-Arb designation',
/* The blend §11's own expansion invites, which the trailing \b in the
first draft made invisible: `C\.Med-Arb\b` cannot match when the next
character is a word char. */
'the C.Med-Arbitrator designation',
'Chartered Med-Arbitration',
/* Plurals: the trailing \b survived the first repair and blocked these. */
'Chartered Med-Arbitrators',
'C.Med-Arbitrators',
'C.Med-Arbitration',
],
'sole-administrator-q62': [
/* The FIVE published clauses from dist/legal/privacy/ as it stood at
`bd282aa`, before the Q62 correction. The first form of the pattern
caught only the first two: clauses 3 and 4 are the same falsehood in
different words in a different section, and clause 5 is the summary
that would have re-asserted the struck number. */
'nobody else has access to the table',
'There is no team, no assistant and no external administrator.',
'no analytics on the submission, and no assistant or outside administrator.',
'The table is reachable by the function that writes to it and by one administrative account, which is mine',
/* The third surface — the summary that would have re-asserted the struck
number four lines below the corrected paragraph. */
'So the honest answer to "who can see this" is: me, and Google as the company that runs my mail.',
],
'struck-universal-q39': [
'Anyone may be appointed an arbitrator in Ontario',
'nothing in law gates the role',
],
},
/* Mostly real published or spec-approved copy on this site. A few are
deliberate NEAR MISSES — truthful sentences about the same subject that
were never published — because a pattern also has to be proven silent on
the wording a correction is likely to reach for. Where a fixture is one of
those, the comment beside it says so. */
mustNotMatch: [
/* NEGATIVE FIXTURES FOR `sole-administrator-q62`. The first FIVE are LIVE
PAGE COPY, verbatim from the corrected `/legal/privacy/` — which is the
fixture that matters, because Q62's ruling required this pattern to be
proven silent on the true sentence as well as loud on the false one.
RE-SYNC THEM WHENEVER THAT COPY CHANGES. ⚠️ **A SENTENCE THAT LEAVES THE
PAGE LEAVES THIS LIST — it is not kept as a near miss.** Struck copy in a
list captioned "what this site legitimately publishes" is an invitation to
restore it. The rest below ARE near misses on the same subject: the
pattern is anchored on five strings that reached `dist/`, not on the ideas
in them, so a truthful sentence about administrative access must pass.
Rendered as text, without the `<strong>` wrappers — what is proven is that
the PATTERN is silent on the words. */
'The record in the table: me, and the small number of people who administer the account it sits in with me.',
'The system that receives what you send can only add a record — it cannot read back what is stored.',
"The notification goes to the practice's mailbox, which is read by me and by administrative staff and is hosted on Google Workspace — so Google holds a copy of whatever you send me.",
'The confirmation that went to you sits with whoever runs your email. That copy is in your hands rather than mine.',
'No one else is sent it. There is no CRM, no mailing list and no analytics on the submission.',
'The table is reachable by the function that writes to it.',
'Two accounts hold administrative access to the AWS account, and the function that writes to the table cannot read it.',
/* ⚠️ TWO FIXTURES WERE REMOVED FROM HERE ON 2026-09-02 AND THE REASON
MATTERS MORE THAN THE STRINGS: `'Nobody else has access to my mailbox.'`
and `'There is no team. Every inquiry is read by me.'` They were added as
harmless near-misses, and §7's `info@smlcompany.ca` row then established
that the mailbox is DELEGATED — Pouya and administrative staff — which
makes both FALSE. The second is also a paraphrase of a `mustMatch` breach
string. This list is documented as the copy the site legitimately
publishes, so a maintainer reaching for a tidier answer would have found
one here, which is how this page acquired its false sentence the first
time. Removing a false fixture keeps the list true; it is not a coverage
change and the freeze does not reach it. `adversarial-reviewer`, round 1. */
'I act as a neutral. I do not act for a party in a matter I take, and each party should have their own legal advice.',
'I run a process, I do not run a case for anybody in it.',
'I will not run a process whose shape nobody agreed to in advance.',
'I do not carry a number across the hall that I was not given to carry.',
'I will not convert a mediation into an arbitration on the day.',
'Commercial matters. I do not accept family arbitration.',
/* An OFFERING statement, not a capacity claim. The capacity pattern carried
`offer` for one pass and fired on this. */
'I do not offer family arbitration.',
/* Counsel's voice in /med-arb/'s FAQ, after the possessive was changed. */
'The neutral who heard our client in caucus then decides the case.',
/* Undertaking (c) — the nearest miss on the widened capacity pattern. */
'If I cannot decide a remaining issue without relying on something said to me in confidence, I say so and step out of the arbitral phase rather than decide on it.',
'Pleadings, disclosure, witnesses, experts, oral argument.',
/* Wrapped, because the wrapper IS what is being tested. The first draft of
this fixture was the bare declaration and the self-test failed on it —
correctly: an unwrapped token name is not something the strip can reach,
so the fixture was asserting the wrong thing. 25 of the 26 real hits were
inside <style>. */
'<style>.hero-lede{line-height:var(--leading-body);color:var(--text-secondary)}</style>',
'Software contracts, SLA and MSA breakdowns, data residency and processing, AI vendor diligence, IP and licensing.',
/* The approved replacements for the two negatives struck on 2026-08-29,
which were 'The Q.Arb pathway commenced in August 2026; C.Med-Arb is
the endpoint.' and a bare 'Chartered Med-Arbitrator'. Both are now
breaches, and both moved to mustMatch. */
'Q.Med (ADRIC / ADRIO) · Q.Arb (ADRIC / ADRIO)',
/* The expansion in approved use — §11 definitional, no stage word. */
'Q.Arb stands for Qualified Arbitrator, an ADRIC and ADRIO designation.',
/* `once` and `when` were BARE stage words for one run, and all four of these
compliant sentences failed the build. They now require a hold-verb. */
'I hold Q.Med and Q.Arb, and I say so when a party asks',
'Q.Arb (ADRIC / ADRIO), which is what parties look at when they appoint',
'Once appointed, I hold Q.Arb and act as sole arbitrator',
'Q.Arb, and when the parties agree the process runs on that basis',
/* `hold` is a common verb on an arbitration page with a different object;
the repair narrowed `once|when` rather than closing it, and this shipped
past it for one run. The hold-verb must now take the designation. */
'I hold Q.Arb, and when I hold a preliminary conference the parties attend',
'I hold the Q.Med and Q.Arb designations through the ADR Institute of Canada and the ADR Institute of Ontario.',
/* The false positive the first form of `c-med-arb-struck` produced. */
'The ADR Institute of Canada publishes ADRIC Med-Arb Rules, developed by a task force.',
'The ADRIC National Mediation Rules.',
'adopted a new edition effective 1 March 2025',
'Commercial arbitration in Ontario. I do not accept family arbitration.',
'Published as typical, not as a guarantee.',
'Liens, delay and change-order claims, scheduling, subcontract and deficiency disputes.',
],
};
/* --- machinery ----------------------------------------------------------- */
/* `relative()` escapes the root with `../../..` when the optional path argument
points outside the repo (which only a test run does). Show the plain absolute
path in that case — an unreadable path in a failure report is one more thing
between a reader and the match. */
function rel(p) {
const r = relative(ROOT, p);
return r && !r.startsWith('..') ? r : p;
}
function walk(dir) {
const out = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) out.push(...walk(full));
else if (entry.name.endsWith('.html')) out.push(full);
}
return out;
}
function newestMtime(paths) {
let newest = 0;
let which = null;
const visit = (p) => {
const s = statSync(p);
if (s.isDirectory()) {
for (const e of readdirSync(p)) visit(join(p, e));
} else if (s.mtimeMs > newest) {
newest = s.mtimeMs;
which = p;
}
};
for (const p of paths) visit(p);
return { newest, which };
}
/**
* Strip what is not published prose or published data.
*
* `<style>` goes because Astro inlines critical CSS into every page and the
* token names collide with the superlative pattern. Non-JSON-LD `<script>` goes
* because a third-party analytics snippet is somebody else's vocabulary — there
* is none today (D15's Plausible is one line and not yet added) and there will
* be. `ld+json` STAYS: it is claim-bearing.
*
* Attributes are NOT stripped, deliberately. `<title>`, `<meta name=
* "description">`, `og:*` and image `alt` text are published copy and are among
* the places a claim is least likely to be re-read.
*/
function publishedText(html) {
return (
html
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
.replace(
/<script\b(?![^>]*application\/ld\+json)[^>]*>[\s\S]*?<\/script>/gi,
' ',
)
/* Smart punctuation, so `cant` is matched by a pattern written `can't`.
Astro emits whatever the source used and the source uses curly quotes. */
.replace(/[‘’]/g, "'")
.replace(/[“”]/g, '"')
.replace(/&#(?:39|x27);/gi, "'")
.replace(/&amp;/gi, '&')
.replace(/&nbsp;|&#160;/gi, ' ')
);
}
/**
* THE SECOND VIEW, AND IT EXISTS BECAUSE `<strong>` DEFEATED THE GATE.
*
* `publishedText` leaves tags in place, and several patterns use `[^<]` as a
* window boundary so they cannot leap a paragraph. That boundary also stops
* them crossing INLINE markup: `<strong>Q.Arb</strong> — the pathway commenced
* in August 2026` matched nothing, and this site sets `<strong>` in prose on
* `/arbitration/` and `/`. Found by `adversarial-reviewer` 2026-08-30.
*
* So every page is scanned TWICE — once as published, once with inline tags
* collapsed to a single space. Block-level tags collapse to a full stop
* instead, which preserves the "do not leap a paragraph" property that made the
* `[^.<]` window worth having in the first place. Matches are deduplicated by
* the matched text, so a hit visible in both views is reported once.
*/
const BLOCK_TAGS =
/<\/?(?:p|div|section|article|header|footer|nav|main|aside|h[1-6]|li|ul|ol|dl|dt|dd|table|tr|td|th|blockquote|figure|figcaption|br|hr)\b[^>]*>/gi;
/**
* ⚠️ THE BLOCK BOUNDARY IS A SENTINEL (`\uE000`), NOT A FULL STOP, AND THAT IS
* WHAT LETS ONE REGEX SERVE BOTH VIEWS.
*
* First attempt substituted `' . '` and the windows were written `[^<]` — but
* collapsed text contains no `<` at all, so the window was bounded by nothing
* and leapt three block boundaries to join an `<h2>Q.Arb</h2>` to a paragraph
* reading "took effect on 1 March 2025" — approved copy that ships. Measured by
* `adversarial-reviewer`.
*
* Writing the windows `[^.<]` instead would fix that and reintroduce the defect
* the collapsed view exists for: a real sentence period inside one paragraph
* ("Qualified Arbitrator. Newly commenced") must still be crossable.
*
* A sentinel separates the two jobs. `[^<\uE000]` is bounded by `<` in the
* published view and by `\uE000` in the collapsed one; neither character occurs
* in the other view, and a full stop bounds neither. Every window in PATTERNS
* uses that class.
*/
const BLOCK_BOUNDARY = '\uE000'; // Private Use Area: never occurs in real content,
// and unlike \u0000 it is not a control character, which `no-control-regex` bars.
function collapsedText(html) {
return publishedText(html)
.replace(BLOCK_TAGS, BLOCK_BOUNDARY)
.replace(/<[^>]+>/g, ' ')
.replace(/[ \t]+/g, ' ');
}
function contextAt(text, index, length) {
const from = Math.max(0, index - 55);
const to = Math.min(text.length, index + length + 55);
const snip = text.slice(from, to).replace(/\s+/g, ' ');
return `${from > 0 ? '…' : ''}${snip}${to < text.length ? '…' : ''}`;
}
function lineOf(text, index) {
let line = 1;
for (let i = 0; i < index; i++) if (text.charCodeAt(i) === 10) line++;
return line;
}
function scan(text, re) {
const hits = [];
re.lastIndex = 0;
let m;
while ((m = re.exec(text)) !== null) {
hits.push({ match: m[0], index: m.index });
if (m[0].length === 0) re.lastIndex++;
}
return hits;
}
function selfTest() {
const failures = [];
for (const p of PATTERNS) {
for (const positive of FIXTURES.mustMatch[p.id] ?? []) {
if (scan(positive, p.re).length === 0) {
failures.push(
`${p.id}: STOPPED MATCHING its own fixture — ${JSON.stringify(positive)}`,
);
}
}
if (!(p.id in FIXTURES.mustMatch)) {
failures.push(
`${p.id}: has no fixture. Every pattern proves it still fires.`,
);
}
for (const negative of FIXTURES.mustNotMatch) {
/* ⚠️ BOTH VIEWS. This read `publishedText` only, which left the entire
false-positive surface of `collapsedText` untested — and that is
exactly where the loosening happened: the collapsed view leapt three
block boundaries onto approved copy and the instrument check could not
see it. Found by `adversarial-reviewer`. */
const hits = [
...scan(publishedText(negative), p.re),
...scan(collapsedText(negative), p.re),
];
if (hits.length > 0) {
failures.push(
`${p.id}: matched APPROVED copy — ${JSON.stringify(hits[0].match)} in ${JSON.stringify(negative)}`,
);
}
}
}
return failures;
}
/* --- run ----------------------------------------------------------------- */
let failed = false;
console.log(
'check:claims — AGENTS.md §4 Forbidden, enforced on shipped output',
);
console.log(` target: ${rel(DIST)}/**/*.html\n`);
// 1. The instrument, before the measurement.
const selfTestFailures = selfTest();
if (selfTestFailures.length > 0) {
console.error(
'SELF-TEST FAILED — the patterns cannot be trusted, so nothing was swept.\n',
);
for (const f of selfTestFailures) console.error(` ${f}`);
console.error(
'\nA pattern that no longer fires passes a clean sweep forever.',
);
process.exit(2);
}
console.log(
` self-test: ${PATTERNS.length} patterns fire on their fixtures and stay silent on ${FIXTURES.mustNotMatch.length} approved strings`,
);
// 2. The target has to exist and have content in it. An empty sweep is not a
// pass — CLAUDE.md: "a command that did not run is not evidence of absence."
let pages;
try {
pages = walk(DIST);
} catch (err) {
console.error(`\nFAILED: cannot read ${DIST}${err.message}`);
console.error(
'Run `npm run build` first. This checks the built output, not the source.',
);
process.exit(2);
}
if (pages.length === 0) {
console.error(`\nFAILED: no HTML under ${DIST}. Nothing was checked.`);
console.error(
'An empty sweep reads exactly like a clean one. Run `npm run build`.',
);
process.exit(2);
}
// 3. Staleness. A pass against yesterday's dist is the same false negative in
// slower motion.
const newestPage = newestMtime(pages);
const newestSource = newestMtime([
...SOURCE_DIRS.map((d) => join(ROOT, d)),
...SOURCE_FILES.map((f) => join(ROOT, f)),
]);
if (newestSource.newest > newestPage.newest) {
console.error(`\nFAILED: ${rel(DIST)} is older than the source.`);
console.error(` newest source: ${rel(newestSource.which)}`);
console.error(` newest page: ${rel(newestPage.which)}`);
console.error(
'\nThis would have checked a build that does not include your change.',
);
console.error('Run `npm run build`, then this again.');
process.exit(2);
}
console.log(` pages: ${pages.length}\n`);
for (const p of PATTERNS) {
const hits = [];
for (const page of pages) {
const raw = readFileSync(page, 'utf8');
/* ⚠️ DEDUPE BY OCCURRENCE COUNT, NEVER BY TEXT. The first version keyed a
Set on the matched string, so `He is a lawyer.` twice on one page was
reported ONCE and the count said 1 — the check truncating its own
output, which is the one thing CLAUDE.md says a check must never do.
Measured by `adversarial-reviewer` on a two-line fixture.
The published view is authoritative; the collapsed view only ever ADDS
occurrences the tags hid, so a collapsed hit counts only where it exceeds
what the published view already found for that same text. */
const counted = new Map();
const bump = (key) => {
const n = (counted.get(key) ?? 0) + 1;
counted.set(key, n);
return n;
};
const publishedCounts = new Map();
for (const [viewIndex, text] of [
publishedText(raw),
collapsedText(raw),
].entries()) {
for (const hit of scan(text, p.re)) {
const key = hit.match.replace(/\s+/g, ' ').trim().toLowerCase();
if (viewIndex === 0) {
publishedCounts.set(key, (publishedCounts.get(key) ?? 0) + 1);
} else if (bump(key) <= (publishedCounts.get(key) ?? 0)) {
continue;
}
hits.push({
page: rel(page),
line: lineOf(text, hit.index),
match: hit.match,
context: contextAt(text, hit.index, hit.match.length),
});
}
}
}
if (hits.length === 0) {
console.log(` ok ${p.id}`);
continue;
}
failed = true;
console.error(
`\n FAIL ${p.id}${hits.length} match${hits.length === 1 ? '' : 'es'}`,
);
console.error(` rule: ${p.rule}`);
console.error(` incident: ${p.incident}`);
if (p.note) console.error(` note: ${p.note}`);
console.error('');
for (const hit of hits) {
console.error(
` ${hit.page}:${hit.line} ${JSON.stringify(hit.match)}`,
);
console.error(` ${hit.context}`);
}
}
if (failed) {
console.error('\n' + '-'.repeat(72));
console.error(
'A match is not yet a finding — read the context printed above before acting.',
);
console.error(
'This site has had a superlative sweep hit "pleadings" and a case-insensitive',
);
console.error(
'sweep for LSO hit "I aLSO practise". Read what matched, then either fix the',
);
console.error(
'copy or change this file DELIBERATELY, with an AGENTS.md Change Log entry.',
);
console.error('Do not delete a pattern to make a build pass.');
process.exit(1);
}
console.log(
'\nClean. Every pattern ran and every pattern is still firing on its fixture.',
);