Pouya's four rulings of 2026-09-01, applied 2026-09-02.
Q62 — RULED "state the truth", not "remove the access". /legal/privacy/
now says two people can read the intake table, names their role, and adds
the two stronger facts the false sentence had crowded out: the handler
role holds PutItem only, and adr-sml-deploy is implicitDeny on all seven
read and write actions. Wording is subject to Pouya's read-through —
Q63(a), with a TODO(pouya) beside the copy.
The ruling named one sentence; a vocabulary sweep found the falsehood in
three places, and the audit then found two more. Five paragraphs now
answer "who can see it" and change together.
The tripwire stays permanently, per ruling, and grew from two
alternatives to five. Every alternative is one string that reached dist/.
Proven both ways against the pre-correction page rebuilt from bd282aa:
exit 1 with 5 matches at dist/legal/privacy/index.html:54,67,67,68,72;
exit 0 on the corrected page, self-test 12 patterns / 36 approved
strings.
/med-arb/ — the gloss is struck with no replacement, per ruling. The
strike left "the section above" pointing at the ADRIC rule set and "the
agreement" with no antecedent; both fixed. The bare designations line
sitting under ADRIC's quoted competence requirement is also struck, which
goes beyond the ruling and is flagged for Pouya.
R18 — re-stamped, two-tier: (a)(c)(d) re-verified against a source,
(b)(e)(f)(g) held on a cadence judgement. All seven hold, no shipped
sentence changed. R18's trigger had NO cutover checklist item and had
stamped five extracts of seven; both fixed. Candidate limb (h) flagged.
R10 — fired and unsatisfied; left open on instruction.
The evidence behind the new privacy sentence was weaker than the
sentence. Re-measured: 33 of 33 roles simulated (23 of 26 carried inline
policies nobody had read; the two CDK lookup roles can read the table),
four trust policies, the CloudFormation escalation path for all five
users, 0 federated providers, root recorded. Every read path terminates
at the same two people.
Two review rounds, 36 findings. 35 fixed, 1 declined. Five of round 2's
were defects in round 1's own fixes; stopped at two per D19.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
810 lines
40 KiB
JavaScript
810 lines
40 KiB
JavaScript
#!/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 three are the
|
||
REPLACEMENT COPY ITSELF, 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. THEY MUST BE RE-SYNCED WHENEVER THAT COPY CHANGES — round 1's
|
||
set went stale within the hour, when the audit rewrote three of the four
|
||
paragraphs they were copied from. The rest 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. */
|
||
'Two people can. The table sits in an Amazon Web Services account that also runs systems unrelated to this practice, and that account has two administrators — me, and one other person who administers it with me.',
|
||
'No one outside those two people has been granted access to the table, and that is measured rather than assumed: every user and every role in the account was simulated against this table, and the only ones that come back able to read it lead to those same two people.',
|
||
'Two things in the system are narrower than I am, and they are worth stating because they are the part you cannot check for yourself.',
|
||
'No one else is sent it. There is no CRM, no mailing list and no analytics on the submission.',
|
||
'Which means there are three copies, not one. The notification in my own mailbox, which is on Google Workspace — so Google holds a copy of whatever you sent me, and so does anyone who can reach that mailbox.',
|
||
/* The replacement summary, which must not trip the third-surface alternative. */
|
||
'Which means there are three copies, not one. The record in the table, which the two people above can read.',
|
||
'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.',
|
||
'Nobody else has access to my mailbox.',
|
||
'There is no team. Every inquiry is read by me.',
|
||
'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 `can’t` 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(/&/gi, '&')
|
||
.replace(/ | /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.',
|
||
);
|