Applies Pouya's rulings on Q42, Q41(a)(b)(c), Q43 and Q40, then builds `/about/` — six of docs/01's seven items. `/about/` ships WITHOUT a memberships group. R10 is a prohibition on shipping a page that lists memberships before they are re-confirmed; the re-confirmation is a fact only Pouya holds and was not obtained. The first version published all four and disclosed the gap in five places instead; both review agents rejected that. Q44 carries the question. Rulings: - Q42 — ENE, dispute-system design and pre-dispute technical advisory rowed; settlement counsel struck as a partisan role. The strike exposed a hole in the offering test, which now states the prior question it was missing. - Q41(a) — Q37 reaches prose, and prose is held to a higher bar. The sentence is now one constant, ASYMMETRY_LINE, because two hand-typed copies had already diverged inside one session. - Q41(b) — not restored; the implication turned out to be in three places, two of which survived the sweep that closed it. - Q41(c) — verified against the LAT's own Rules and extracted into docs/reference/lat-case-conference.md. Rule 2.4 makes "Pre-Hearing Conference" the Tribunal's own term for a case conference; the Rules contain zero occurrences of `mediat` in 66,593 characters. - Q43 — the timings are service commitments; PROCESS_FRAMING renders adjacent to them, not in a lede above. - Q40 — bundled to step 7 as R15, blocking cutover. Four review passes, 43 findings, nine of them defects in their own predecessors' fixes. The worst was mine: the false universal Q39 struck reached a public page. Also fixed a portrait ladder that upscaled 1.93x at 1024/DPR2 on BOTH pages — the shipped home page included — because its 960 ceiling was derived from the layout range where the image is narrowest. Verified: check/lint/build/audit clean; 0 upscaling across 11 device profiles; 0 overflow and 0 over-wide elements at 13 widths; 0 contrast failures across 127 and 88 painted pairs; 0 print failures against white paper; reveal 0 hidden under reduced-motion and print; zero JavaScript. Lighthouse NOT RUN — tool unavailable until step 7 (R11). HTML validator NOT RUN. Opens Q44 (memberships), Q45 (PDF bio), Q46 (offering-test gating; the glossary standard), Q47 (jobTitle without worksFor). Adds R15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148NztQskLKKApP5SzAA78e
301 KiB
Agent Working File
This file is a living document and a full history tree for this project — not a snapshot. Any agent or person working here (Claude in chat, Claude Code, Claude Cowork, or a human) maintains it by the rules below. Everything else in this file varies by project; these rules do not.
How to maintain this file
-
Document everything. On every change, record what was discussed, decided, changed, or planned — decisions and plans included, not just executed work.
-
Never overwrite or delete history. When something changes, update the relevant Current Truth section in place and append a dated Change Log entry capturing old → new and why.
-
Two parts, kept separate: Current Truth is updated in place and always reflects the present state; the Change Log is append-only, newest entry first, and is never edited retroactively.
-
Stamp facts as
[verified YYYY-MM-DD]or[assumed]. When you re-check a fact, re-stamp it with today's date; a stale date means it needs re-verifying. -
Never prune the Change Log. If the file gets unwieldy, ask before archiving old entries to
AGENTS-history.md. History is not destroyed.
Current Truth
1. What this project is
A ground-up rebuild of adr.smlcompany.ca, the personal-brand website of the
alternative dispute resolution practice of Pouya Lajevardi. [verified 2026-08-25]
The brand's centre of gravity is the practitioner, not the firm and not
SML Company Ltd. Legal background, firm affiliation, and the operating company
are credibility signals referenced in support; they are not the subject.
[verified 2026-08-25 — source: PL_ADR_Personal_Branding_Strategy_Brief.docx]
Positioning in one sentence: a credentialed neutral who is also close to
live litigation and a practising machine-learning / infrastructure engineer — a
combination that is close to absent from Canadian ADR rosters, and which is the
practice's economic moat. [verified 2026-08-25 — source: strategy brief §I, §V]
(Wording corrected 2026-08-26: "a working litigator" asserted conduct of
litigation, which §4 bars — see entry (q).)
Time horizon: the strategy brief plans a 24-month compounding arc. The site
is built to be added to continuously, not shipped once. [verified 2026-08-25]
2. State of the thing being replaced
The site live at adr.smlcompany.ca as of this entry: [verified 2026-08-25]
⚠️ Several of these measurements are contested — see Q34 (opened 2026-08-26). A re-fetch of the deployed URL that day reproduced some of the list exactly and could not reproduce others from the served HTML, because the served document turns out to be a bundler harness whose application lives in nine UUID-named script files that were not fetched. Do not cite any figure below as established, and do not put one in public copy, until Q34 closes. The items marked ⚠️ are the specific ones in doubt. This caveat sits here rather than only in §9 because a reader who arrives at a
[verified]stamp will otherwise never learn it is under review.
- It is a single 2.2 MB HTML file containing an entire React application,
compiled in the browser at runtime by Babel Standalone loaded from unpkg.
Confirmed on re-fetch 2026-08-26: the response is 2,206,032 bytes, of
which 2,210,921 are inline
<script>. - ⚠️ React and ReactDOM are loaded as development builds from a public CDN.
Not reproducible 2026-08-26: the served file contains no reference to
unpkgor to a React development build. They may be inside the unfetched bundles. Q34. - Crawlers see almost nothing. A server-side fetch of the URL returns only
SML Company,DISPUTE RESOLUTION, and the loading stringUnpacking.... Every word of real content is assembled client-side after three CDN round trips.[verified 2026-08-25 — direct fetch] - The deployed
<head>has no<meta name="viewport">, no meta description, no Open Graph or Twitter card tags, no canonical URL, and no favicon.[verified 2026-08-25]— still true of the head a crawler parses. Note for accuracy: the harness carries a second, JSON-escaped<head>inside a<script type="__bundler/template">which does contain a viewport tag. It is assembled by JavaScript, so it reaches a browser and not a crawler, which is the distinction that matters here. robots.txtreturns 403. There is no sitemap.[verified 2026-08-25]- ⚠️ The two logo PNGs are ~1 MB combined and are base64-inlined into the
HTML, so they cannot be cached separately from the page.
[verified 2026-08-25]— but a 2026-08-26 fetch found zerobase64,occurrences anywhere in the served document. Q34. This figure is cited as the reason for a standing convention inCLAUDE.mdanddocs/02, both of which now carry the caveat. A related number, "470 KB PNG", appeared indocs/02with no source anywhere in this repo and has been removed. <title>is stillSML Company · Dispute Resolution— the pre-rebrand placeholder title.[verified 2026-08-25]- There is no version control, no build step, and no test suite. The
publishing mechanism is a Python script (
rebuild-standalone.py) that inlines the JSX components into the single file.[verified 2026-08-25] - The May 2026 content brief was partially applied — the JSX components
under
components-standalone/contain the corrected Pouya-branded copy, but the document<head>and some structural defaults still carry placeholder content.[verified 2026-08-25]
Consequence, stated plainly: for a site whose entire purpose is to be found
by counsel, general counsel, and appointing bodies searching for a neutral, the
current architecture is close to a worst case. This rebuild is justified on
architecture alone, independent of any content or design opinion.
[verified 2026-08-25]
3. Locked decisions
Decided with Pouya on 2026-08-25. Each of these is settled; do not re-open one
without an explicit instruction and a Change Log entry. [verified 2026-08-25]
| # | Decision | Chosen | Rejected alternatives |
|---|---|---|---|
| D1 | Framework | Astro, static output, v7.x. Amended 2026-08-26: the major is now pinned deliberately rather than inherited. The original ^5.0.0 pin was recalled, not checked, and was two majors stale the day it was written. Upgrading costs three config files now and compounds with every page; the v5 advisories were high-severity XSS in the framework rendering a public site, and "we don't use those authoring patterns" is a claim about all future code, not just today's. Starting two majors behind means being three behind at launch |
Next.js; patching the single-file build; hand-written HTML; staying on v5 |
| D2 | Content scope | Full re-architecture — new IA, new pages, all copy rewritten | Copy rewrite on existing structure; audit only; technical layer only |
| D3 | Hosting & CI/CD | Git repo + Gitea Actions → existing S3 + CloudFront. Amended 2026-08-26: self-hosted Gitea, repo adr-sml. Gitea Actions uses GitHub Actions syntax, so the workflow ports nearly as is — but Gitea is not an AWS OIDC provider, so deploys authenticate with a scoped IAM user, rotated quarterly |
GitHub Actions + OIDC; Terraform/CDK IaC; Amplify; manual deploys |
| D4 | Languages | English only | Full EN/FA bilingual; EN + one Farsi page; EN with FA scaffolding |
| D5 | Page structure | Full multi-page, ~20 URLs | Lean six-page; rich home + a few deep pages |
| D6 | Primary audience | All four tiers, served by different surfaces (see §5) | Single-audience focus |
| D7 | Visual design | Keep palette and infinity mark; modernize the execution | Faithful port; strip motion only; fresh design direction |
| D8 | Fees | Full rate card published on a /fees/ page |
Ranges only; on request; gated PDF |
| D9 | Insights | Build it and launch with 3–5 drafted pieces, every word reviewed by Pouya before publication | Launch empty; no Insights section |
| D10 | Intake | Rebuilt intake form + calendar booking for the confidential intake call | Structured questionnaire; form only; email/phone only |
| D11 | Rollout | Build everything, one clean cutover. The current site stays live untouched until replaced | Patch live site first; staging subdomain; take site down |
| D12 | Agent working file | This file, maintained under the constitution above | Ad hoc notes |
| D13 | Licensure claims | The site asserts the JD only. No claim of licensure, call to the bar, or entitlement to practise law. The approved phrasing for the boutique role is "active litigation exposure" or "involvement in litigation and ADR matters" — never "practice" in that context. "Practice" describing Pouya's own ADR practice — "the dispute resolution practice of Pouya Lajevardi" — is correct and stays. Pouya's direction, 2026-08-26. Explicitly interim — see Standing Reminders §12 | Stating LSO licence status; the word "practice" |
| D14 | Fee structure | Single published rate card, all mediation matters at one rate. Confirmed by Pouya 2026-08-26; figures in docs/07-fees.md. No tribunal-secretary line |
Two-tier card (recommended by Claude, declined); ranges; fees on request |
| D15 | Analytics | Plausible. Privacy-first and cookieless: no cookie banner, nothing to consent to, one line in the privacy policy. Amended 2026-08-26 (Q31): the original read "Plausible or Fathom", i.e. undecided, and a guessed 'plausible' had already leaked into src/data/site.ts from it. Decided, not defaulted — Pouya checked rather than assumed and found his own assumption wrong: Fathom is Canadian-owned, which is the intuitive answer here, but it stores non-EU traffic on US servers and isolates in the EU only for EU visitors. Plausible keeps all data in the EU. For a practice whose privacy posture is part of the offer, EU-only beats US-hosted, and ownership is not the axis that matters |
GA4; no analytics at all; Fathom — rejected on data residency, not on quality |
| D18 | Intake email | Two emails per submission — notification to info@smlcompany.ca, and a confirmation receipt to the inquirer. Pouya, 2026-08-26. The receipt is what requires SES production access (Q19) |
Notification only; no email; no form at all |
| D17 | Working method | Pouya architects; Claude Code implements and then adversarially reviews its own work. Six-phase loop encoded in .claude/commands/build.md; two independent review agents in .claude/agents/; extended thinking on by default. Standing agreement — not restated per prompt |
Implement-and-report; single generic reviewer; review only on request |
| D16 | Naming the boutique | Never named. Referred to throughout as a Toronto litigation and ADR boutique. Pouya's decision, 2026-08-26 | Naming the firm |
4. Credential and claim register
This is the most important section in this file. The previous version of this site carried a fictitious founder, invented matter values, fabricated office locations, and a testimonial attributed to a person who does not exist. Whatever regulatory regime does or does not apply, publishing false credentials on a professional's marketing site is the thing this register exists to prevent. That reason is sufficient on its own, and it does not rest on a fact this file has never established.
Note on this paragraph's own history, kept as a caution. It previously read "Pouya is a licensed legal professional", then "a legal professional", then asserted that LSO marketing rules apply — three progressively weaker forms of the same unverified claim, each surviving a sweep meant to remove it. The register was asserting what it could not vouch for, in the section written to stop exactly that.
Governing rule
No claim reaches a public page unless it appears in the Verified column below. If a page needs a fact that is not here, stop and ask Pouya. Do not infer it, do not soften it into something defensible, and do not carry it over from the old site.
Verified — may be published
Sourced from Pouya's own strategy brief of 2026-05-26. Self-reported by the subject, which makes them reliable as to substance, but currency is not confirmed — several are on a moving credentialing track and may have advanced since May.
| Claim | Status |
|---|---|
| Pouya Lajevardi, JD, Bond University | [verified 2026-08-25 — strategy brief §I] |
| Licence status — NOT ESTABLISHED | [unestablished 2026-08-26]. This record does not know whether Pouya is licensed. He instructed that licensure be left out of the site (D13) — an instruction about the site, not a statement of fact either way. Do not assert it, do not deny it, do not infer it from anything else here, and do not treat any regulatory regime as established. R1 tracks the reopening |
DO NOT PUBLISH. Pouya directed on 2026-08-26 that licensure is left out of the site entirely; the JD is asserted, nothing further. See D13 and the Forbidden table below [verified 2026-08-26] |
|
| Director of Firm Operations, Toronto litigation and ADR boutique | [verified 2026-08-25 — strategy brief §I] |
| Active litigation exposure: personal injury, construction, regulatory (POA), insurance (SABS) | [verified 2026-08-26]. Wording corrected — this row previously read "practice", the exact word D13 bars in exactly the context it bars it. The register was quoting the strategy brief verbatim and contradicting its own rule. The underlying fact is unchanged |
| Q.Med designation through ADRIC / ADRIO | [verified 2026-08-25 — strategy brief §I] |
| Has completed multiple sole mediations | [verified 2026-08-25 — strategy brief §I] — count deliberately not published, see below |
| Q.Arb credentialing pathway — commenced August 2026 | [verified 2026-08-26 — Pouya]. Describe as newly commenced, never as held or nearing completion |
| C.Med-Arb stated as long-term designation goal | [verified 2026-08-25 — strategy brief §I, §IV] |
| Kompass Arbitration Certificate Program — completed | [verified 2026-08-25 — strategy brief §I] |
| Stitt Feld Handy negotiation and ADR workshop sequence — completed | [verified 2026-08-25 — strategy brief §I] |
| Practising machine-learning and DevOps / infrastructure engineer | [verified 2026-08-25 — strategy brief §I, §V] |
| Bilingual English and Farsi | [verified 2026-08-25 — strategy brief §I] |
| Iranian-Canadian; cross-cultural fluency with diaspora business communities | [verified 2026-08-25 — strategy brief §I] |
| Operator of SML Company Ltd. alongside the practice | [verified 2026-08-25 — strategy brief §I] |
| SML Company Ltd — incorporated FEDERALLY, under the CBCA | [verified 2026-08-26 — Pouya, Q30]. Two facts were being conflated and one of them was wrong: jurisdiction of incorporation is federal (Canada); place of business is Toronto, Ontario. src/data/site.ts carried 'SML Company Ltd. · Ontario, Canada', which reads as a jurisdiction of incorporation and named the wrong one. No corporation number — none is held and the line does not need one. Caution, and it is the point of this row: "federally incorporated" says nothing about professional licensure, and nothing about where the practice may operate. It must not be read together with the Licence status — NOT ESTABLISHED row into an implication that neither row makes. Not published: on Pouya's direction the footer reads © <year> SML Company Ltd and nothing further — the fact is verified and available, it is simply not on a page |
| Memberships: ADRIC, ADRIO, OBA — Construction & Infrastructure, ADR, and Civil Litigation sections, and the Canadian Tax Foundation | [verified 2026-08-26 — Pouya], for now. CTF added 2026-08-26. Both the OBA sections and the CTF renew yearly; every line here is a fact with a shelf life, tracked as R10. Re-confirm at renewal and before any page listing memberships ships. CTF is a membership, not a practice area — it is the one credential none of the six areas touch, and docs/01-architecture.md records why there is no seventh page at launch and when to revisit (R3) |
Not current. Do not publish [verified 2026-08-26 — Pouya] |
|
Do not publish. Listing the Law Society among memberships implies licensure, which D13 bars. Excluded deliberately, not by oversight [verified 2026-08-26] |
|
| Toronto, Ontario; by appointment | [verified 2026-08-26] |
Contact: info@smlcompany.ca; no public phone number; consultations by scheduled call |
[verified 2026-08-26 — Pouya] |
| Response time: inquiries answered within two business days | [verified 2026-08-26 — Pouya]. A public commitment — it must read identically on /contact/, in the inquirer confirmation email, and in any bio |
LinkedIn: https://www.linkedin.com/in/pouyalajevardi/ |
[verified 2026-08-26 — Pouya] |
| STRUCK 2026-08-26 (Q25). Superseded by D16 — never named. Pouya said it could be named, then reversed; the permission survived the reversal and sat in the Verified table authorising exactly what D16 bars. Refer to it only as a Toronto litigation and ADR boutique |
Offerings — what the practice offers, as distinct from what Pouya holds
Added 2026-08-26 on Pouya's ruling (Q33, Q36). Everything above this point answers one question: is this claim about Pouya true? That is a question about credentials. It turns out the register also has to answer a second, different question: does the practice offer this? — and applying the credential test to an offering produced a wrong answer twice in one session.
What went wrong, because it explains the rule — stated correctly on the second
attempt. co-arbitration available now was flagged across three specs on the
reasoning that §4 records the Q.Arb pathway as merely commenced and therefore
could not support an arbitration offering. That reasoning was wrong at the
root: it silently treated a designation as though it were a licence.
The masthead objection was a different one, and this paragraph originally
misdescribed it. Q33-orig did not say Pouya was not entitled to arbitrate. It
said the line had been promoted to a masthead under his name on every page,
where nothing qualifies it and it reads as a designation strip — an objection
about placement and implication, not entitlement. Answering the entitlement
objection does not answer the placement one, and the first version of this
section quietly substituted the easier question. claims-auditor caught the
substitution. The placement objection is answered by the paired-disclosure
condition below, which is a condition, not a formality.
Pouya's ruling, and the reasoning to keep. His stated basis: ADRIC and ADRIO designations — Q.Med, Q.Arb, C.Med-Arb — are voluntary credentials, not licences, and commercial arbitral appointment in Ontario is not gated behind a designation — scoped and sourced as the box below now sets out, after the unscoped version of that sentence turned out to be false. So the constraint on offering arbitration was never may he act — only will parties appoint someone at this stage of the arc, which is positional and his to answer. He answers it: he accepts arbitration appointments now, and that — his acceptance — is what the rows below record.
⚠️ The legal proposition underneath is the architect's, not this register's, and it is deliberately NOT stamped
[verified].[verified — Pouya]means the subject reported it, which can establish what he offers and cannot establish a proposition of Ontario law. That much is unchanged.What HAS changed: there is now a source in the repo —
docs/reference/ontario-family-arbitration-training.md, the extract Pouya's Q39 ruling named, retrieved 2026-08-27 with its provenance and the method that produced it. R14 is satisfied for the half of the proposition it covers, and it does not cover both halves. Read the next paragraph before relying on either.THE UNIVERSAL IS STRUCK, AND IT WAS FALSE. This paragraph carried "Anyone may be appointed an arbitrator in Ontario. Nothing in law gates the role behind a designation."
claims-auditorproduced a counter-example from inside this repo — the site ships a Shareholder & Family Business practice area, and family arbitration looked like a place where qualification is prescribed — and Pouya checked it and confirmed the counter-example. His words, 2026-08-27: "My assertion was FALSE as a universal." Family arbitrators in Ontario must complete prescribed training: 14 hours on screening for domestic violence and power imbalances, 30 hours of Ontario family law for arbitrators outside the Ontario or another Canadian bar, and 10 hours ongoing per two-year period, five of them on domestic violence or power imbalance. Sourced, per the extract above.The scoped replacement, and exactly whose it is. Commercial arbitration in Ontario requires no licence and no designation. That is Pouya's stated position —
[Pouya's stated basis 2026-08-27], deliberately not[verified]. The extract establishes the family gate directly; it establishes the commercial half only by absence, because a government page about family arbitrators is not authority for what a commercial arbitrator needs. It neither mentions nor excludes commercial arbitration. Do not cite it as if it did, and do not let the scoped form drift back toward a universal because it now has a footnote attached.The reason nothing on the site turns on any of this is the scope exclusion two paragraphs down, not the reasoning above: family arbitration is not offered.
The offering test
For an offering, ask about COMPETENCE. For a credential, ask about PERMISSION. A credential claim needs a row in the Verified table because it asserts a fact about Pouya that is either true or false. An offering needs two things, and they are answered by different people:
- Is the activity gated? — a question for THIS REGISTER, not for the subject. It needs a source that a reviewer can check, and it is answered per activity and per jurisdiction, never as a class. Until it is answered, the offering does not ship.
- Is he competent to do it? — his call, and only his. Not the register's, not an implementer's, not a reviewer's.
Question 1 is not his to answer, and an earlier draft of this test said it was: it ended "That is his call, not the register's, and not an implementer's" attached to the whole preceding question, gating included. That hands a future writer the sentence "he says this one is ungated, and §4 says that is his call" — which is the walk-through the boundary below exists to prevent. Split, on
claims-auditor's finding, 2026-08-26.
What counts as an offering, and the rule for publishing one
An offering may not be published until it has a row in "Offered now" below. Same force as the governing rule for the Verified table. The register created this category; it has to gate it too, or it is a category that authorises everything by saying nothing.
Scope: an offering is a PROCESS the practice conducts — mediation, arbitration, med-arb, early neutral evaluation, dispute-system design, pre-dispute technical advisory. Each needs its own row.
settlement counsel was in this enumeration until 2026-08-27 and is removed
from it, not merely left unrowed: it is not a process this practice conducts
at all. See the struck row in the Offerings table — Q42.
A subject-matter area is NOT an offering. Construction, technology,
energy, insurance, shareholder, cross-cultural are the contexts in which
the processes are conducted, not additional things offered. They are governed by
docs/03's framing rule instead — "Frame as positioning, not as history" — and
the open half of Q35 asks whether a one-word nav label can carry even that.
Do not read this scope note as clearing them; it says only which rule applies.
Added 2026-08-26 on claims-auditor's finding that the section had created a
category without defining either its extent or its gate — so Med-Arb was
sitting in the site-wide footer as an offering with no row, three sentences
below a paragraph saying not to infer exactly that. It has been removed from the
footer pending Q35(a).
THE BOUNDARY, AND IT IS NOT NEGOTIABLE. This test applies only where no licence gates the activity. It is not a general licence-free zone, and it must never be read as one:
- Mediation, and COMMERCIAL arbitration, are ungated. No designation is
required to be appointed. The offering test governs, and Pouya has answered it.
Scoped 2026-08-27 (Q39). This bullet read "Mediation and arbitration are
ungated" — an unqualified statement about arbitration as a class, in the
paragraph headed NOT NEGOTIABLE, which is the worst place in this file for
one. Family arbitration in Ontario carries prescribed training
(
docs/reference/ontario-family-arbitration-training.md), so the class statement was false and the boundary paragraph was the thing breaching its own boundary. Family arbitration is separately not offered — see the Offerings table — so the site never relied on the false half; the record did. - Legal services are gated. Providing legal services and practising law in Ontario are restricted activities. For anything on that side of the line the binding question is permission, the answer is not established (see the Licence status — NOT ESTABLISHED row above), and D13 bars the claim outright. "I offer legal representation — that is competence, not permission" is exactly the misreading this paragraph exists to foreclose.
- The test never converts a credential into an offering. Q.Arb is still commenced August 2026 and must still never read as held. Offering arbitration and holding Q.Arb are two different statements, and the site makes the first while stating the second plainly.
Offered now — may be published
| Offering | Status |
|---|---|
| Mediation — sole mediator | [verified 2026-08-26 — Pouya]. Q.Med held; §4 also verifies multiple completed sole mediations |
| Arbitration — sole arbitrator (commercial) | [verified 2026-08-26 — Pouya, Q33/Q36]. Offered now. Not gated by Q.Arb. Commercial, and the scope is now load-bearing rather than provisional: Q39 closed 2026-08-27 and family arbitration is excluded by Pouya's choice — see the row below |
| Arbitration — party-appointed | [verified 2026-08-26 — Pouya, Q33/Q36]. Offered now |
| Arbitration — co-arbitration | [verified 2026-08-26 — Pouya, Q33/Q36]. Offered now. This closes Q36, which had flagged the claim as asserted in three specs and resting on no §4 row — the row is here |
| Med-Arb — mediation-arbitration | [verified 2026-08-27 — Pouya, Q35]. Offered now. Both halves are live: Q.Med is held, and arbitration appointments are accepted (rows above). Ungated by the same reasoning as arbitration. This closes the "Still unanswered" note that used to sit below this table, and it is why /med-arb/ may stay in the footer sitemap |
NOT OFFERED — a scope exclusion, not a gating problem [verified 2026-08-27 — Pouya, Q39]. Pouya has confirmed he does not do family arbitration. It is therefore out of scope by his choice, and the prescribed-training question never arises for this practice. Recorded because the register reasoned from a false universal for one day and must not do so again — see the box above and docs/reference/ontario-family-arbitration-training.md. Shareholder & Family Business does not touch this: it means commercial disputes among family shareholders — shareholder and partnership disputes, co-founder breakdowns, business succession — and its page must say plainly that family law matters are not accepted |
|
| Early neutral evaluation (ENE) | ⚠️ COMPETENCE ANSWERED; GATING NOT ANSWERED — NOT YET PUBLISHABLE. Question 2: "A neutral assessment of merits sits inside a Q.Med's competence" [Pouya's stated basis 2026-08-27, Q42]. Question 1 — is the activity gated? — is [unestablished]. An earlier version of this row read "Ungated on the same footing as mediation — no designation is required to give a non-binding evaluation" under a [verified — Pouya] stamp. That is a proposition of Ontario law inside a stamp §4 says cannot carry one, and it was the register doing exactly what the box above warns against, one paragraph after warning against it. Struck by claims-auditor on two consecutive passes. ENE is also the offering nearest the gated side of the boundary — a neutral assessment of the merits, delivered to parties — so it is the one that most needs a source rather than an assertion. Q46. Priced hourly in docs/07-fees.md, which is a fee-page question, not a publication licence |
| Dispute-system design | ⚠️ COMPETENCE ANSWERED; GATING NOT ANSWERED — NOT YET PUBLISHABLE. Question 2: "Consulting, no appointment, no neutrality question" [Pouya's stated basis 2026-08-27, Q42]. That sentence answers neutrality, not gating, and an earlier version of this row presented it as though it answered both. Question 1 is [unestablished] and is not his to answer. Q46 |
| Pre-dispute technical advisory | ⚠️ COMPETENCE ANSWERED; GATING NOT ANSWERED — NOT YET PUBLISHABLE. Question 2, in three words: "It is the moat" [Pouya's stated basis 2026-08-27, Q42]. Question 1 is [unestablished]. Q46. Caution that belongs with the row regardless: advisory work for one organisation is the one item in this table that can create a conflict against a later appointment in the same matter. That is practice management rather than a publication gate — but no copy may imply it is free of that tension |
NOT OFFERED, AND IT IS STRUCK RATHER THAN UNROWED [verified 2026-08-27 — Pouya, Q42]. Pouya removed it as his own error in docs/01: "Settlement counsel acts FOR a party in negotiation. That is a partisan role, and putting it on a site that (a) sells neutrality and (b) asserts no licensure under D13 is wrong twice over: it undercuts the brand's central claim and it edges into acting for a client." Note the ordering — the positioning objection comes first. It would still be wrong on a site with no licensure question at all, which is why this row does not read as a D13 consequence. Struck from docs/01 §/practice/, from docs/07-fees.md's hourly list, and from the §4 scope enumeration above. Never priced, never listed, never restored |
|
| Not offered. D14 removed the rate and bars offering it |
How the arbitration page must read (docs/01 §/arbitration/, step 4):
it offers all three today and states plainly that the Q.Arb pathway commenced
August 2026, with C.Med-Arb as the endpoint. Pouya's instruction: being open
about the stage is the differentiator — do not hedge it into vagueness. So
neither half may be dropped. Naming the stage is not a disclaimer bolted onto an
offering; it is the reason a sophisticated reader believes the offering.
Subject-matter areas — the publication gate (Q35, 2026-08-27)
This gate did not exist, and its absence is what let Med-Arb sit in the
site-wide footer as an unrowed offering three sentences below a paragraph
telling readers not to infer exactly that. Pouya's words: "Add the
publication gate you identified was missing." Same force as the two gates
above.
A practice area may be named — in the nav, in the footer, in a card — where BOTH hold:
- He can competently accept an appointment in that subject matter. His call, and only his. A subject-matter area is not a gated activity (the processes conducted in it are mediation and arbitration, both ungated), so the offering test's question 1 is already answered for it and question 2 is what remains.
- The page frames it as positioning, not as claimed history.
docs/03: "Built to facilitate procurement and subcontract disputes on Ontario's megaproject pipeline" — not "extensive experience resolving". A page that claims volume it does not have fails this gate even though the label passes condition 1.Condition 1 is about the label; condition 2 is about the page. A label may ship before its page exists only where the page is already specified to meet condition 2 — which
docs/01does for all six areas,/practice/energy/most explicitly ("Write it as a genuine position, not a claim of existing volume").
The offering test, applied — the Q42 worked example (2026-08-27)
Kept because it is the only place in this file where the test is run on four candidates at once and one of them fails. Pouya's instruction was literally "apply the offering test", and the outcome was 3–1.
| Candidate | Q1 — is the activity gated? (the register's, with a source) | Q2 — competence? (his) | Outcome |
|---|---|---|---|
| Early neutral evaluation | [unestablished] — no source. Q46 |
"sits inside a Q.Med's competence" | Not publishable yet |
| Dispute-system design | [unestablished] — no source. Q46 |
"Consulting, no appointment, no neutrality question" | Not publishable yet |
| Pre-dispute technical advisory | [unestablished] — no source. Q46 |
"It is the moat" | Not publishable yet |
| Settlement counsel | Never reached | — | STRUCK |
THE Q1 COLUMN WAS FILLED IN WITH HIS ANSWERS AND HAS BEEN EMPTIED. The first
version of this table read "No. A non-binding neutral assessment is not a
restricted activity" and "No. Consulting, no appointment, no neutrality
question" — the second of which does not even address gating. §4 is explicit:
"Question 1 is not his to answer… It needs a source that a reviewer can
check, and it is answered per activity and per jurisdiction, never as a class."
No jurisdiction was named for any of the three. claims-auditor caught this
substitution on 2026-08-26 in one place and again on 2026-08-28 in this table —
the same defect, in the paragraph added to explain the defect.
The reason settlement counsel fails is the finding. It did not fail question 1 and it did not fail question 2. It failed a prior question the test does not ask: is this a neutral's role at all? Settlement counsel acts for a party. Run mechanically, the two-question test would have passed it — negotiation on a party's behalf is not obviously gated, and Pouya is competent to negotiate — and it would have put a partisan role on a site whose central claim is neutrality.
So the prior question is: is this a role in which the practice is partisan between the parties to a dispute? If it is, the answer is no before the test starts.
THAT IS THE RULE, AND IT IS STATED THIS WAY BECAUSE THE FIRST VERSION HAD AN
UNGATED EXCEPTION IN IT. It read "the offering test presumes the role is a
neutral's… The one exception in this table is the pre-dispute technical advisory
row, which is also not a neutral role." claims-auditor: a rule and its ad-hoc
exception in the same paragraph, in the section written to prevent "a category
that authorises everything by saying nothing" — and the exception was
self-contradictory, because the dispute-system design row two lines above also
describes a non-neutral role ("Consulting, no appointment, no neutrality
question"). One of the two statements had to be wrong.
Restated as above, there is no exception: dispute-system design and pre-dispute technical advisory both pass, because neither involves a party to be partisan between — one advises an organisation about its future disputes, the other precedes any dispute. Settlement counsel fails, because acting for a party in a negotiation is partisanship between parties by definition. The distinction is the party, not the label.
Applied, 2026-08-27 — Q35 CLOSED. Energy, Grid & Regulatory,
Shareholder & Family Business, and the other four labels pass condition 1
on Pouya's answer; condition 2 is each page's job at build step 5. Med-Arb
now has an Offerings row of its own and stays in the footer sitemap. Nothing in
the nav or footer is unrowed or ungated any more.
Forbidden — must not appear on the site
Scope, clarified 2026-08-26. This table governs published pages — anything
a visitor to adr.smlcompany.ca can read, JSON-LD included. It is not a word
filter over the repository's own documentation.
Internal docs are bound by a related rule: they must not assert as fact anything this register has not verified. Reasoning built on an unverified premise propagates into copy. Licensure is the live example — the Verified table above does not establish licence status either way; its two licensure rows are verified directives not to publish, not a verified status. So no file in this repo may call Pouya "licensed", or soften it to "a legal professional", internal or not. State the reason for a rule rather than a credential the register cannot vouch for.
| Never publish | Why |
|---|---|
| Any count of matters closed, hours mediated, or years in ADR practice | Practice is new. Small true numbers do not persuade a sophisticated GC and invite scrutiny. Save for one-to-one appointment proposals where context travels with the figure. [verified 2026-08-25 — content brief, "Rule of thumb on numbers"] |
| Settlement rates, resolution percentages, median time to award | No underlying data exists |
| Aggregate value resolved; any dollar figure attached to past matters | No underlying data exists |
| Named or describable past matters | Confidentiality, and none are publishable |
| Testimonials, endorsements, quotes from counterparties | None exist. The prior site's testimonial was fabricated |
| "Since 2009", "sixteen years", London / New York offices, Co. № 07452218 | Artefacts of the placeholder template. All false |
| The name "S. M. Lawrence" | Fictitious founder from the template |
| Guarantees of outcome, or superlatives ("best", "leading", "top-rated") | Unverifiable as written, and they read as insecure to the audience this site is for. Marketing rules for regulated professions also treat them as suspect. (Reason restated 2026-08-26: this row previously read "LSO marketing rules", which asserts the regulatory status the row above marks NOT ESTABLISHED. It was the last surviving instance, flagged in entries (o) and (q) and answered in Q26 as already fixed — it was not. Restated by Claude Code; the rule itself is unchanged and the restatement needs Pouya's ratification.) |
| The word "lawyer" used of Pouya; "called to the bar"; "licensed"; "my law practice"; "my litigation practice"; any post-nominal implying a licence | D13. The site asserts the JD and nothing further |
| Any phrasing that implies entitlement to practise law without saying so — "acts for clients", "represents parties", "my clients", "legal advice" | Same rule. Implication is the risk, not just the word. Describe the role factually instead: Director of Firm Operations at a Toronto litigation and ADR boutique, and the matter types worked on |
| Q.Arb described as held, imminent, or "nearly complete" | It commenced August 2026 |
The substitution principle
Wherever the design wants a "how much / how many" statistic, substitute a
longer-arc credential — something already substantial and true at launch that
does not grow by closing files. The approved stat set is Q.Med /
JD + ML / EN · FA, with Q.Arb as a fourth slot where one exists.
[verified 2026-08-25 — content brief]
5. Audience model
All four tiers matter, but they are served by different surfaces rather than by
diluting every page into a compromise. [verified 2026-08-25 — decision D6]
| Tier | Reads | Wants | Primary surface |
|---|---|---|---|
| In-house / general counsel | Home, practice pages | Credentials, subject-matter fluency, procedure, predictable cost | Home + /practice/* |
| Referring lawyers and litigation boutiques | Practice pages, fees, process | Availability, rules familiarity, rate card, confidence you won't embarrass them | /fees/, /process/, /mediation/ |
| Appointing bodies and ADR institutions | About, credentials | A verifiable credential record and evidence of depth | /about/ |
| Self-represented parties and SMEs | Plain-language layer | What mediation actually is, what it costs, what happens | /for-parties/ |
Realistically, referring counsel are the largest source of early
appointments. Where two audiences conflict on a page, resolve toward counsel.
[assumed]
6. Architecture
Full sitemap, URL map, and per-page content outline: docs/01-architecture.md.
Summary: ~20 static pages. Home; About; three process pages (Mediation, Arbitration, Med-Arb); six practice-area pages; Process; Fees; For Parties; Insights index and articles; Contact; Privacy; Terms.
Deliberate omission: there is no Indigenous engagement / IBA practice page at
launch, though the strategy brief rates that niche as strategically the most
valuable. Publishing a practice page for it before the multi-year relationship
work described in brief §III.4 has been done would read as overreach to exactly
the audience it targets. Revisit at month 12–18. [verified 2026-08-25 — decision recorded here, not yet discussed with Pouya]
7. Environment and stack
| Thing | Value |
|---|---|
| Framework | Astro 7.2.9, output: 'static' [verified 2026-08-27 — npm view astro version, D1 as amended]. @astrojs/mdx 7.0.8, @astrojs/sitemap 3.7.3, sharp 0.35.4 — all three at latest. Bumped from 7.2.7 at the step 1 → step 2 phase boundary under R11: 7.2.8 published 2026-08-26 and 7.2.9 on 2026-08-27, i.e. two patches appeared inside 48 hours, which is the argument for checking at boundaries rather than on failure. engines unchanged at node >=22.12.0, npm >=9.6.5 [verified 2026-08-27 — npm view astro@7.2.9 engines] |
| Lint toolchain | ESLint 10.9.1, @eslint/js 10.0.1, globals 17.11.0, eslint-plugin-astro 3.1.0, eslint-plugin-jsx-a11y 6.10.2, typescript-eslint 8.68.0, typescript 6.0.3 — every pin at the registry's latest except typescript [verified 2026-08-27 — npm view, all 14 pins]. @eslint/js and globals are declared explicitly; before 2026-08-26 eslint.config.js imported them and they resolved only by npm hoisting accident. Accessibility linting is on — flat/jsx-a11y-recommended, 36 rules [verified 2026-08-26 — 7 rules fired on a deliberately inaccessible .astro file]. eslint-plugin-jsx-a11y@6.10.2 declares a stale eslint ^3..^9 peer range; a one-line overrides entry in package.json resolves it. ⚠️ eslint-plugin-astro@3.1.0 declares node ^22.22.3 || ^24.16.0 || >=26.3.0, which excludes Pouya's Node 25.6.0 — npm install prints EBADENGINE there. Dev-time only, and nvm use (Node 22 LTS, per .nvmrc) clears it. typescript — the hold, and it was too wide by a whole major until 2026-08-27. latest is 7.0.2 and it is unusable here: typescript-eslint@8.68.0 peers typescript >=4.8.4 <6.1.0 and @astrojs/check@0.9.10 peers ^5.0.0 || ^6.0.0, so taking 7 breaks both the linter and astro check [verified 2026-08-27 — npm view <pkg> peerDependencies]. But this row previously read "held at 5.x", and 6.0.3 is a stable release both peers accept — so the pin sat a full major behind for no reason the record could name, in a row whose whole job is to name the reason. Now at 6.0.3, the newest version compatible with both peers; npx tsc --version reports 6.0.3 and the full gate is green [verified 2026-08-27]. The 6.x listing is mostly -dev and -beta tags; 6.0.2 and 6.0.3 are the only stable 6.x releases, which is why npm view typescript version (7.0.2) is not the number to pin against here. Re-check at the next phase boundary: the hold ends when typescript-eslint widens its peer range past <6.1.0 |
| Lighthouse CI | Not installed, and that is now stated wherever the check is claimed. @lhci/cli was the sole source of all 10 npm audit findings (7 high, via lighthouse → puppeteer-core → extract-zip), 0.15.1 is latest so there was no clean upgrade, and it cannot run at all — no pages, no lighthouserc. Removed 2026-08-26; npm audit now reports 0 vulnerabilities [verified 2026-08-26 — npm audit]. Removing it left a documented control that no longer exists — docs/04, CLAUDE.md and /build Phase 5 all required a run that had silently become impossible, which is Q22's defect in a different costume. Fixed 2026-08-26: the unavailability is stated in docs/04-seo-spec.md (budget table, the callout under Performance, and the post-launch checklist), CLAUDE.md (performance budget and the definition of done), .claude/commands/build.md Phase 5, docs/06-deployment.md (PR checks and the cutover checklist), and .claude/agents/adversarial-reviewer.md §4 — which is also told not to raise the absence as a finding. Re-add at build step 7 with a pin verified that day and a check for a patched release; do not assume 0.15.1 is still the ceiling (R11) |
| Node | 22 LTS line, .nvmrc holds 22 so it tracks the latest 22.x rather than freezing at a floor. package.json declares **engines.node = `^22.13.0 |
| Styling | Plain CSS with custom properties. No Tailwind, no CSS-in-JS [verified 2026-08-25] |
| Known HTML-conformance error | Astro's <Picture> + densities emits a duplicate-density srcset, and it is upstream of this repo. The pattern is url, url 1x, url 2x, url 3x — the first candidate carries no descriptor and therefore defaults to 1x, so two candidates claim the same density, which puts the document in error per the HTML spec. Behaviour is benign and selection is correct at DPR 1, 2 and 3 [verified 2026-08-27 — measured in headless Chrome]. It affects 4 of the 12 srcset attributes on / — the <source> elements of every <Picture> using densities, i.e. the three InfinityMark instances. The hero portrait uses widths + sizes and emits clean w descriptors. Consequence for the record: do not claim "HTML validator clean" for any page carrying a mark, and do not re-open this as a defect in this repo — it is <Picture>'s output, not ours. It would go if InfinityMark moved to widths + sizes, which would mean every call site supplying a sizes string for a fixed-size decorative image; not worth it for a conformance nit. Recorded on adversarial-reviewer's request, 2026-08-27; first raised as step-1 review finding #8 |
| Deploy ordering | / must not be deployed before /about/ exists, and the reason is machine-readable rather than visible. The home page's Person JSON-LD sets url and @id to https://adr.smlcompany.ca/about/, which is a 404 until build step 3. The @id resolves inside the page's own @graph, so provider is fine; Person.url is the exposure, and it is the one part of the page a crawler acts on rather than renders. Every internal link is in the same position, which is inherent to a nineteen-page build order — but D11 already governs this: "Build everything, one clean cutover", and nothing deploys until step 11. Recorded because npm run deploy is a single command from this machine and the guard is a decision rather than a mechanism. Raised by adversarial-reviewer, 2026-08-27 |
| Client JS | Zero. Not "minimal" — none. dist/index.html contains 1 <script> tag and it is type="application/ld+json"; non-JSON-LD scripts 0, and dist/_astro/ contains no JS bundle [verified 2026-08-27 — built, counted, and re-confirmed with script execution disabled at the CDP level: 444 DOM nodes, 6,578 characters of visible text, 5,665 px of content, i.e. the identical page]. (This row cited dist/type-scale/index.html until 2026-08-27; that page was the step-1 proof sheet and step 2 deleted it, so the row named a file that no longer exists. / is now the page to count.) The reveal was an inline IntersectionObserver in <head> until 2026-08-26; it was removed because docs/05-backend-spec.md specifies script-src 'self' with no unsafe-inline, so the only script on the site was the one thing the site's own CSP would refuse to run. animation-timeline: view() in global.css replaces it. Both scroll-driven animations must use animation-* LONGHANDS — Lightning CSS folds the animation shorthand together with animation-timeline into an invalid declaration and discards it, which works in npm run dev and is dead in npm run build. It happened twice in one session: once in the header condense, then again in the fix written for it. /build Phase 5 now greps dist for it. Islands remain available where a feature genuinely cannot be CSS or progressive HTML — none has been needed |
| Content | Astro Content Layer API collections, MDX for Insights. Config lives at src/content.config.ts; collections declare a loader, not a type; z imports from astro/zod. Astro 5 introduced the Content Layer API and this location; Astro 6 removed the legacy src/content/config.ts fallback — moving the file back now raises LegacyContentConfigError [verified 2026-08-26 — reproduced by moving it back] |
| Fonts | Instrument Serif + Geist + Geist Mono, self-hosted, font-display: swap. Files present as of 2026-08-26 — six .woff2 cuts, 123,804 bytes total, in public/fonts/ (du -sh says 136K; that is disk blocks, not transfer, and this row is read in a performance context); provenance, licences (all SIL OFL 1.1) and the reason they are committed rather than pulled at build are in docs/reference/fonts-provenance.md. Before this they were declared in global.css and absent from disk, so every face silently fell back to Georgia and the system sans [verified 2026-08-26 — built and listed]. Instrument Serif 400 latin (21,032 B) and Geist latin (29,400 B) are preloaded in BaseLayout.astro — about 50 kB at first paint; nothing else is. Every font URL carries ?v=1, because the deploy script serves /fonts/* immutable for a year: replacing a cut means bumping that query in global.css and in the preload together [verified 2026-08-26 — byte counts] |
| AWS account | 327082975128 [verified 2026-08-26 — inventory] |
| Region | ca-central-1 throughout — hosting, Lambda, DynamoDB [verified 2026-08-26] |
| S3 bucket | adr-smlcompany-site — versioning Enabled, so rollback works [verified 2026-08-26] |
| CloudFront | E1OK7G98KNKUTA, alias adr.smlcompany.ca, origin adr-smlcompany-site.s3.ca-central-1.amazonaws.com, Deployed [verified 2026-08-26] |
| ACM certificate | arn:aws:acm:us-east-1:327082975128:certificate/2b6d5bdf-6790-430c-9b82-c00ab66e6d87 — ISSUED [verified 2026-08-26] |
| Intake API | adr-intake-api, HTTP API 4tl0m5igkj, endpoint https://4tl0m5igkj.execute-api.ca-central-1.amazonaws.com [verified 2026-08-26] |
| Intake Lambda | adr-intake-handler, nodejs24.x [verified 2026-08-26] |
| Intake table | adr-intake-submissions (DynamoDB, ca-central-1) [verified 2026-08-26] |
| SES identities | Domain smlcompany.ca verified for sending [verified 2026-08-26]; addresses info@, intake@, adr@ |
| SES account | Production access GRANTED — out of the sandbox in ca-central-1, confirmed by AWS in writing and effective immediately [verified 2026-08-26 — Q19 closed]. Mail now reaches unverified recipients, so the inquirer confirmation in D18 works |
| Mail hosting | Google Workspace — MX 1 smtp.google.com; google._domainkey present, so Google DKIM is configured [verified 2026-08-26 — DNS query] |
| SPF | PRESENT. v=spf1 include:_spf.google.com include:amazonses.com ~all on @, added by Pouya and independently verified [verified 2026-08-26 — DNS query, Q20] |
| DMARC | PRESENT. v=DMARC1; p=none; rua=mailto:info@smlcompany.ca; fo=1 on _dmarc, added by Pouya and independently verified [verified 2026-08-26 — DNS query, Q20]. Still at p=none; tighten to quarantine only after reports come back clean |
| Required DNS (Namecheap) | TXT @ → v=spf1 include:_spf.google.com include:amazonses.com ~all · TXT _dmarc → v=DMARC1; p=none; rua=mailto:info@smlcompany.ca; fo=1 [assumed] — start at p=none, tighten after monitoring |
| SES DKIM — the set that actually resolves | f5puwearz5ek47aiuoskk57ya6ir66ju, jdue2r22cxrk27laxlgnjcpby2fq4vxu, kznn3cklvwh4gs342rtf5euqymlger53 — all three answer NOERROR at <token>._domainkey.smlcompany.ca [verified 2026-08-26 — DNS] |
| SES DKIM — the set that is BROKEN | 3zsnvsjg3ddi6hjyndjy6jgjoitvhydl, jejgp7na3wdpprsanamct4uxxgw2tyqw, xpiwyftpodmgpnzfof3ee7t7fzmlmhnh — listed in the SES console, present in Namecheap, but NXDOMAIN. They were entered with the full name in Namecheap's Host field, so the zone doubled the domain: <token>._domainkey.smlcompany.ca.smlcompany.ca resolves, the correct name does not [verified 2026-08-26 — DNS] |
SES identity — ca-central-1 (the one that matters) |
Verified: true, DkimStatus: SUCCESS, tokens kznn… / jdue… / f5pu… — the set that resolves. Healthy. Change nothing [verified 2026-08-26] |
SES identity — us-east-1 (unused) |
Verified: false, DkimStatus: PENDING, tokens 3zsn… / jejgp… / xpiwy… — the doubled-domain set. A stray identity in a region this project does not use [verified 2026-08-26] |
| ⚠️ SES sandbox is per-region | Recorded as a standing caution, not an open task — production access was granted in ca-central-1 (Q19). The trap: the AWS console defaults to us-east-1, and granting access there leaves ca-central-1 in the sandbox with the intake email still failing. Applies again to any future SES request [verified 2026-08-26] |
| SES custom MAIL FROM | Not configured — SES reports "No MAIL FROM records found". This is why SES's SPF is unaligned and DMARC rests on DKIM. Optional future improvement, not required [verified 2026-08-26] |
| SES monitoring — SNS topic | ses-alerts, ca-central-1. Email subscription to info@smlcompany.ca is PENDING CONFIRMATION, not confirmed [verified 2026-08-26]. Until the subscription is confirmed the alarms below fire into nothing — confirm the link in the subscription email |
| SES monitoring — alarms | SES-BounceRate-High (bounce rate ≥ 0.03) and SES-ComplaintRate-High (complaint rate ≥ 0.001), both ca-central-1, treat-missing-data: notBreaching [verified 2026-08-26] |
| SES bounce/complaint handling | Email feedback forwarding — SES's default, on unless disabled. Deliberately not an SNS feedback topic: at under 100 messages a month there is nothing to consume a programmatic feed. Revisit when code needs to act on a bounce [verified 2026-08-26] |
| DMARC alignment | SES satisfies DMARC via DKIM, not SPF — without a custom MAIL FROM domain its envelope sender is at amazonses.com and is unaligned. The SPF record's real job is authenticating Google Workspace mail [verified 2026-08-26] |
| Do not delete | Two ACM validation CNAMEs → jkddzztszm.acm-validations.aws. They renew the adr.smlcompany.ca certificate; deleting them breaks HTTPS silently at the next renewal [verified 2026-08-26] |
| Root domain | @ carries a Namecheap URL Redirect Record to sml-coming-soon.ca. TXT records coexist with it — adding SPF does not disturb it [verified 2026-08-26] |
| DNS confirmed | adr.smlcompany.ca CNAME → d26v23dhgsp2ta.cloudfront.net [verified 2026-08-26] |
| TLS | ACM certificate [verified 2026-08-25 — AWS-Hosting-Guide.md] |
| DNS | Namecheap, not Route 53 [verified 2026-08-25 — AWS-Hosting-Guide.md] |
| Intake backend | API Gateway (HTTP API) → Lambda → DynamoDB, notifications via SES [verified 2026-08-25 — AWS-Hosting-Guide.md] |
| Repository | adr-sml, self-hosted Gitea. Local clone at /Users/pouya/Dev/Websites/adr-sml [verified 2026-08-26] |
| CI/CD | Gitea Actions, .gitea/workflows/deploy.yml. Instance git.larsnolden.com, version 1.27.2 [verified 2026-08-26 — /api/v1/version] — well above the ~1.21 floor for the vars context. The GitHub OIDC workflow is kept as docs/reference/github-actions-oidc.yml.example, outside .github/workflows/ so Gitea cannot fall back to it. No OIDC available — Gitea is not an AWS OIDC provider |
| Deploy credential — NOT PROVISIONED | aws iam get-user --user-name adr-sml-deploy returns NoSuchEntity [verified 2026-08-26 — Q22]. The scoped IAM user does not exist, no access key has been issued, and the Gitea secrets are unset. No file may describe this credential as existing. Creation commands are with Pouya; this is a to-do, not an open question |
| How deploys actually happen today | Locally, via npm run deploy (scripts/deploy-local.sh) — same guard, same three sync passes, same cache headers, same invalidation as the workflow. Gitea Actions needs [actions] ENABLED in app.ini and a registered act_runner, both of which need the instance's second administrator (Q23). At this scale the pipeline changes only how a deploy is triggered, not what it does [verified 2026-08-26] |
| Interim auth — do not repeat in CI | Pouya has been authenticating as user/pouya, the broadly-permissioned personal user the Q10 inventory captured. Acceptable for interactive work at a keyboard; never as a CI credential — see §10 [verified 2026-08-26] |
| Analytics | Plausible — cookieless, no personal data, no consent banner, all data held in the EU [verified 2026-08-26 — D15 as amended, Q31]. src/data/site.ts sets ANALYTICS.provider. Not yet installed: no script is on any page, and /legal/privacy/ (step 10) is where the processor gets named |
8. Design system
Full token set, type scale, motion rules, and contrast results:
docs/02-design-system.md.
Palette and infinity mark carry over unchanged. Execution is modernized:
fluid type scale, self-hosted fonts, an 8 px spacing scale, motion gated behind
prefers-reduced-motion, and one hard accessibility constraint discovered by
measurement:
Gold
#c9a876on cream#faf7f2measures 2.10:1 contrast. That fails WCAG AA for body text (4.5:1) and for large text (3:1). Gold is a decorative and on-dark colour only. On maroon it measures 5.84:1 and on ink 8.00:1, both of which pass.[verified 2026-08-25 — computed]
9. Open questions — blocking
Nothing below can be invented. Each needs an answer from Pouya.
| # | Question | Blocks |
|---|---|---|
| ANSWERED 2026-08-26. Licensure is left out entirely; the site asserts the JD only. See D13 | — | |
| ANSWERED 2026-08-26. Q.Arb commenced August 2026 | — | |
ANSWERED 2026-08-26. Email info@smlcompany.ca. No public phone — "By scheduled call". Location: Toronto · Ontario · By appointment |
— | |
ANSWERED 2026-08-26. Rate card confirmed by Pouya — see D14 and docs/07-fees.md |
— | |
| ANSWERED 2026-08-26. Self-hosted Gitea with Gitea Actions | — | |
| ANSWERED 2026-08-26. Non-mediation hourly $500. Prep bundled: 2 h in the half day, 3 h in the full day, stated on the page. Overtime $500/h | — | |
| ANSWERED 2026-08-26. Struck. The §4 row permitting the boutique to be named is removed; D16 governs alone | — | |
| ANSWERED 2026-08-26. §4 now carries an explicit Licence status — NOT ESTABLISHED row, and its opening no longer asserts that LSO rules apply. The register's rationale stands on the fabricated-credentials history alone | — | |
ANSWERED 2026-08-26. AWS-Hosting-Guide.md copied into docs/reference/ — it is the only record of how the hand-built infrastructure was created. Scanned for credentials before copying: no access keys, no secrets, no account ID |
— | |
ANSWERED 2026-08-26 — it does not exist. aws iam get-user --user-name adr-sml-deploy returns NoSuchEntity. Recorded in §7 as NOT PROVISIONED; creation commands are with Pouya, so it is now a to-do, not a question. Consequence while it is outstanding: deploys run locally (npm run deploy), and the quarterly rotation obligation in D3 still has no subject |
— | |
| Q5 | Booking tool — parked 2026-08-26 at Pouya's request. Build /contact/ with the form only and a clean slot for the embed. Now tracked as standing reminder R6 |
/contact/ — non-blocking |
ANSWERED 2026-08-26. Supplied and committed: src/assets/pouya-lajevardi.jpg (1600×1600 master) and src/assets/og-portrait.jpg (1200×630 link-preview crop) |
— | |
| ANSWERED 2026-08-26. Pouya reverted to generic. The boutique is never named; refer to it as a Toronto litigation and ADR boutique throughout | — | |
ANSWERED 2026-08-26. ADRIC, ADRIO, OBA sections, and the Canadian Tax Foundation (CTF added 2026-08-26; this row omitted it until claims-auditor found the omission on 2026-08-27 — a second, stale copy of the memberships fact in a place nobody re-reads, which is the failure mode §7's single-source rule exists to stop. §4 is the source; this row is a pointer). Not OCNI. Not LSO (see §4) |
— | |
ANSWERED 2026-08-26. Gitea, repo adr-sml, clone at /Users/pouya/Dev/Websites/adr-sml |
— | |
| ANSWERED 2026-08-26. Full inventory captured; values in §7 | — | |
ANSWERED 2026-08-26. smlcompany.ca is verified for sending — true. The None in the earlier listing was a query artefact, not a fault |
— | |
ANSWERED 2026-08-26. Production access granted in ca-central-1, confirmed by AWS in writing, effective immediately. The per-region trap was real and was avoided — it was requested in ca-central-1, not the console's default us-east-1. Nothing now blocks /contact/ |
— | |
ANSWERED 2026-08-26. Both records added by Pouya and independently verified: SPF v=spf1 include:_spf.google.com include:amazonses.com ~all on @, DMARC v=DMARC1; p=none; rua=mailto:info@smlcompany.ca; fo=1 on _dmarc. Both existing @ TXT records, Google DKIM, MX, and the adr CNAME all confirmed intact |
— | |
ANSWERED 2026-08-26. ca-central-1 is healthy (SUCCESS) on the resolving token set. The broken records belong to a stray us-east-1 identity this project does not use. Nothing to fix. The three dead CNAMEs are inert; leaving them is the low-risk choice |
— | |
| ANSWERED 2026-08-26. Privacy-first, cookieless — Plausible or Fathom. No GA4, no consent banner | — | |
ANSWERED 2026-08-26. https://www.linkedin.com/in/pouyalajevardi/ |
— | |
ANSWERED 2026-08-26 — two business days. A public commitment, so it must read identically everywhere it appears: src/data/site.ts, /contact/, the inquirer confirmation email, and any bio |
— | |
ANSWERED 2026-08-26 — OBA Construction & Infrastructure, ADR, and Civil Litigation [verified 2026-08-26 — Pouya], for now: section membership renews yearly, so this is a fact with a shelf life. Tracked as R10 |
— | |
ANSWERED 2026-08-26 by implementation. Guard widened on both counts: the two secrets are checked (emptiness only, never echoed), and INTAKE_ENDPOINT is promoted to job-level env: and guarded. Every vars.* and secrets.* the workflow consumes is now covered except BOOKING_URL, empty by decision (R6) |
— | |
ANSWERED 2026-08-26 — federally, under the CBCA, and it does not appear in the footer. Recorded in §4 with the caution that it implies nothing about licensure or about where the practice may operate. The footer reads © <year> SML Company Ltd and stops there (Pouya's direction, given twice). src/data/site.ts un-nulled, TODO(pouya) removed. Note the spelling: §4 writes SML Company Ltd. with a terminal period; the rendered string has none, because that is how Pouya specified it |
— | |
| ANSWERED 2026-08-26 — Plausible, and decided rather than defaulted. Pouya checked and found his own assumption wrong: Fathom is Canadian-owned but stores non-EU traffic on US servers, isolating in the EU only for EU visitors; Plausible holds everything in the EU. D15 amended with that reasoning. The value that was guessed and nulled is now the value that was chosen — which is not the same thing, and the difference is why it was nulled | — | |
ANSWERED 2026-08-27, all four items, and the answer added a gate that was missing. (a) Med-Arb — "he holds Q.Med and now accepts arbitration appointments, so both halves are live. Keep it in the nav. Removing it unilaterally orphaned the page — restoring it was right." It now has its own row in §4 Offered now, and stays in the footer sitemap (docs/01 keeps it out of the primary nav deliberately; that is unchanged). (b) Energy, Grid & Regulatory, Shareholder & Family Business and the other four — "subject-matter labels, already governed by docs/03's 'positioning, not history'. They pass test 1; test 2 is the page's job." (c) THE GATE. Pouya: "Add the publication gate you identified was missing: a practice area may be named where he can competently accept an appointment in that subject matter, and where the page frames it as positioning without claiming history. That hole is why Med-Arb sat in the footer under a paragraph telling readers not to infer it." Written into §4 → Subject-matter areas — the publication gate. Nothing in the nav or footer is now unrowed or ungated. Narrowed framing follows |
— | |
NARROWED 2026-08-26 — two of the four items are resolved; two are not. Arbitration is answered (Q33) and is now a §4 Offerings row. What remains: (a) Med-Arb — ungated by the same reasoning, so it is purely a competence call, and Pouya named mediation and the three arbitration forms without naming it. Do not infer it. (b) Energy, Grid & Regulatory and Shareholder & Family Business — subject-matter areas rather than gated activities, so the offering test asks only whether he is competent to mediate or arbitrate in them, which is his answer to give. docs/03 already fixes how the pages must read once answered — "Frame as positioning, not as history" — so this is not a blocker on writing them, only on claiming capability in a one-word nav label before the page exists. Original framing follows |
Med-Arb (step 4); the practice pages (step 5) | |
May the header nav and footer sitemap name a service or practice area the register does not establish as a present capability, before the page that qualifies it exists? This is the architecture-versus-register question that claims-auditor has now raised twice, and it is not one an implementer should settle. The nav and footer ship on all nineteen pages and today carry Arbitration, Med-Arb, Energy, Grid & Regulatory and Shareholder & Family Business — none of which trace to a §4 row. Every one of them is also locked architecture: D5 and §6 fix the sitemap, and docs/01 specifies each page. The specs already anticipate the tension and answer it at page level — docs/03: "Frame as positioning, not as history"; docs/01 on energy: "Write it as a genuine position, not a claim of existing volume" — so the question is precisely whether a one-word nav label, which has no room to frame anything, inherits that licence. Three ways out: accept the labels as taxonomy and rely on the pages to qualify (status quo), rename toward capability-neutral wording, or hold items out of the nav until their page ships. Claude Code declined to act unilaterally on either audit pass; recorded so the judgement is visible rather than missed |
The header and footer on every page; sharpest at step 5 | |
ANSWERED 2026-08-26 — yes, and so are sole-arbitrator and party-appointed appointments. Now a row in §4 Offerings rather than an unsourced assertion in three specs. docs/01, docs/03 and docs/07-fees.md no longer need to cite an open question against it. Original text follows |
— | |
Is co-arbitration work available now? Asserted in three specs — docs/01 §/arbitration/, docs/03 §Voice as model copy, and docs/07-fees.md, which instructs "The page must say so" — and it is in no §4 row. §4 records the Q.Arb pathway as commenced August 2026 and nothing about what is available in the meantime. The sentence is offered under the heading "Name the limits", so the unverified half travels inside advice about honesty, which is how it survived. All three files now cite this question; none has been deleted, because the shape of the sentence is right and only its second clause is unsourced. Raised by claims-auditor, 2026-08-26 |
/arbitration/ (step 4) and /fees/ (step 9) |
|
ANSWERED 2026-08-27 — the label changes. JD + ML is now labelled "Legal training and engineering practice", not "Law and engineering". Pouya's reasoning, and it is the finding rather than the fix: "The parallel was doing the implying — a degree and a practice under one noun. The asymmetry is the honest part." A degree is not a practice, and a label that renders them as two instances of one thing invites the reader to supply the missing symmetry — which for "Law" is precisely the licensure implication D13 bars. The new label is longer and less tidy, deliberately. docs/03's credential-row table is amended, and the TODO(pouya) is gone from src/data/site.ts. Original question follows |
— | |
Is "Law and engineering" the right label under JD + ML? §4's substitution principle supplies the three credential-row values (Q.Med / JD + ML / EN · FA); the labels come from docs/03 and are not themselves registered. claims-auditor reads this one as a D13 implication risk: "engineering" is shorthand for a verified practising claim, so the parallel invites "Law" to be read the same way, and D13 bars implication as hard as assertion. Left as written — it is approved copy and changing approved copy is not an implementer's call — with a TODO(pouya) on it in src/data/site.ts. Nothing renders it yet. Raised by claims-auditor, 2026-08-26 |
The credential row on / (step 2) and /about/ (step 3) |
|
ANSWERED 2026-08-26 — the question rested on a false premise. It assumed the constraint on describing arbitration was about entitlement. ADR designations are voluntary credentials, not licences, and commercial arbitral appointment in Ontario is not gated behind a designation — so the constraint was always positional, never legal, and it was Pouya's to answer. (This row said "anyone may be appointed an arbitrator in Ontario" until 2026-08-27. Pouya checked it and it is false as a universal — family arbitration is gated; see Q39. Corrected in place, because §9 is Current Truth. The Change Log entries that quote the original stand unedited, which is where the history lives.) He accepts arbitration appointments now. Mediation · Arbitration · Toronto is restored to the masthead one-liner, and docs/04's specified home <title> stands as written. See §4 Offerings, added for exactly this class of statement. The original question text follows for the record |
— | |
How is arbitration described in site-wide copy before Q.Arb is conferred? Not a wording question — a positioning one, and it recurs on every page. §4 records the Q.Arb pathway as commenced August 2026, "never as held or nearing completion". SITE.tagline reads Mediation · Arbitration · Toronto, and docs/01 specifies that exact string as the Home hero eyebrow — where the hero copy beneath it can qualify it. Build step 1 promoted it to a masthead line under Pouya's name on every page, where nothing qualifies it and it reads as a designation strip; claims-auditor flagged it and it has been removed from the header. But the question it exposed is still open and lands at build step 2: does the hero eyebrow keep Arbitration as specified, qualify it, or hold it until designation? /arbitration/ (step 4) is separately specced to state the position plainly, which is the honest model — the issue is the one-line forms that have no room to. Second pass, same day: the removal from the header did not sweep the proof sheet, where the same string was hand-typed as a type specimen and rendered ten times at up to 96px. Both defects fixed — the specimen is now a neutral pangram — and the lesson is the sweep rule again: a removal is a grep, not a memory. The nav item Arbitration also survives, deliberately; that is Q35. Raised by claims-auditor, 2026-08-26 |
The Home hero (step 2); docs/04's specified home <title>; any bio or one-line descriptor |
|
| Q34 | Do §2's measurements of the previous build still hold, and were they measured against the deployed file? Two of claims-auditor's findings were unverified old-site assertions published in copy, which sent Claude Code to measure the live URL on 2026-08-26. What came back does not straightforwardly match §2. Reproduced: the response is a single 2,206,032-byte file, and <title> is still SML Company · Dispute Resolution. Could not be reproduced from the served HTML: there are zero base64, occurrences anywhere in it — §2 records "two logo PNGs ~1 MB, base64-inlined" — and no reference to unpkg or a React development build. Why this is a question and not a correction: the served document is a bundler harness. 2,210,921 bytes of it are inline <script>, the real <head> sits JSON-escaped inside a <script type="__bundler/template">, and the application is loaded from nine UUID-named script files that were not fetched. The logos and the CDN references may well be in those bundles. So §2 may be right, may be stale, or may have been measured against the local source rather than the deployment — this record cannot tell which. Why it matters beyond tidiness: docs/02 and CLAUDE.md both cite the base64-logo figure as the reason for a standing convention, and §12 R8 exists because unverified premises propagate. Nobody should quote these numbers in public copy until the question closes. Raised by Claude Code, 2026-08-26 |
Nothing structural. It governs whether §2's figures may be cited as fact |
ANSWERED 2026-08-27 — and the answer is that the register's premise was FALSE. Pouya checked it rather than defending it. His words: "My assertion was FALSE as a universal." (a) Is it gated? YES. Family arbitrators in Ontario must complete prescribed training — 14 hours on screening for domestic violence and power imbalances, 30 hours of Ontario family law for arbitrators outside the Ontario or another Canadian bar, and 10 hours ongoing per two-year period. Sourced, and the source is now in the repo per R14: docs/reference/ontario-family-arbitration-training.md, retrieved 2026-08-27 with provenance. (b) Does the offering extend to it? NO — and that is what actually disposes of the question. Pouya does not do family arbitration, so it is a scope exclusion, not a gating problem, and the prescribed-training requirement never bears on this practice. Shareholder & Family Business means commercial disputes among family shareholders — shareholder and partnership disputes, co-founder breakdowns, business succession — and its page must state plainly that family law matters are not accepted. One sentence, not a section: Pouya's instruction is that it earns its place only by saving a wasted intake call. (c) One honest limit on the source. The extract establishes the family gate directly and the commercial half only by absence — it neither mentions nor excludes commercial arbitration. §4 therefore carries "commercial arbitration in Ontario requires no licence and no designation" as Pouya's stated position, unstamped, not as a sourced fact. Do not let the footnote promote it. Also swept: the false universal survived in three live places outside the paragraph that was corrected — §4's NOT-NEGOTIABLE boundary bullet, §9's Q33 closure row, and a comment in SiteHeader.astro. All three fixed; the grep and its output are pasted in Change Log entry (w), §Sweeps. (adversarial-reviewer flagged this citation while it still pointed at nothing: the entry was written at Phase 6, after review, so for the duration of the review a §9 row asserted in the present tense that evidence "is in the Change Log" before it was. Fair catch. The row now names the entry, and the reviewer independently re-ran the sweep and confirmed it was genuinely complete.) Original question follows |
— | |
Does the arbitration offering extend to FAMILY arbitration, and is that activity gated? Raised by claims-auditor 2026-08-26 as a counter-example from inside this repo, and it is a good one. §4 Offerings rests on the proposition that arbitral appointment in Ontario is not gated behind a designation. The site ships a Shareholder & Family Business practice area — and family arbitration in Ontario is understood to be one of the places where arbitrator qualification is prescribed by statute and regulation. If so, the offering test's own boundary applies: where an activity is gated, the question is permission, and permission is not established. This record does not resolve it — Claude Code will not assert a proposition of Ontario law, and neither may a self-report. What is needed: (a) does the practice-area page contemplate family-law arbitration at all, or only shareholder, partnership and family-business disputes, which is a different thing; and (b) if it does, the gating question needs a checkable source in the repo, per R14. Interim: §4's arbitration rows are scoped to commercial arbitration and the universal has been removed from the reasoning. Nothing ships that turns on it |
The Shareholder & Family Business page (step 5); /arbitration/ (step 4) |
|
RULED 2026-08-27 — and deliberately BUNDLED rather than shipped as a second interim. Two kinds of card, not one. Pouya: "A portrait is the right OG image for / and /about/ — a face is the strongest social preview for a personal brand. It is the wrong one for nineteen pages, where a typed card carrying the page title would do the work. But do not build the generator now and do not leave 'portrait everywhere' as an untracked interim. Ship it at step 7 alongside Insights, which needs per-article cards anyway — one build, one dependency, one review." So the portrait is the decided card for / and /about/, not a placeholder; the generated typed card covers every other page and every article, built once at step 7 against a freshly checked pin (R11). Until then all pages share the portrait, and that interim blocks cutover, not build step 3 — recorded on the cutover checklist in docs/06 and tracked as R15 with its removal trigger, on his instruction: "Record the trigger so it cannot quietly become permanent." docs/04 amended. |
Nothing until step 7. Then the generator, and cutover | |
ANSWERED 2026-08-27 — all three, and two of the three interim wordings were wrong in a way the interim had not identified. (a) Q37's reasoning DOES extend to prose, and prose is held to a HIGHER bar, not a looser one. Pouya: "The implication test applies everywhere, not just to labels. Prose has more room, so it is easier to satisfy: state the asymmetry explicitly rather than relying on a parallel construction to carry it." Deleting the parallel — which is all the interim did — was half the fix; a reader can still supply the missing symmetry from silence. Both pages now name both halves, from one constant — ASYMMETRY_LINE in src/data/site.ts, which is where a claim-bearing sentence belongs (ROLE's reasoning: "these are the two where the wording IS the compliance"). It was typed into / and then into /about/, and the two copies had diverged inside the same session — a comma on one, full stops on the other — which is why it is a constant and why this row cites it rather than quoting a variant. Calling the legal half training is what makes the licence implication impossible rather than merely absent. docs/01 §/ item 3 and docs/03 §Home both carry a warning not to lift their own phrase "law and engineering" into copy. (b) NOT restored, and the editorial reason outranks the compliance one. Pouya: "That is an unverifiable empirical claim about other practitioners… It is also weaker copy: assert his capability, not the field's incapability." His replacement is used verbatim — "disputes that turn on the contract, the code, and the engineering documents" — and it replaces the comparative in docs/03's core positioning statement, so the approved-copy defence that had protected it is gone. The interim ("the documents rather than the pleadings") is also gone: it said nothing about other neutrals but still worked by contrast. (c) Verified, and the interim was ambiguous in the one word that mattered. docs/reference/lat-case-conference.md [verified 2026-08-28]: LAT Rule 2.4 makes "Pre-Hearing Conference" the Tribunal's own term for a case conference; Rule 14.3 puts a Member in the chair and then disqualifies them from the hearing panel; Rule 14.6 makes attendance mandatory; and the Rules contain zero occurrences of mediat or arbitrat in 66,593 characters. A privately retained neutral cannot be appointed to it. The affirmative basis is on the Tribunal's own AABS page: "you may want to consider negotiation or mediation services… including before filing at the LAT-AABS, and continuing… after a claim has been filed." The interim read "private mediation of matters before the LAT" — where before reads as pending at as readily as prior to. Published: "private mediation alongside a LAT application, before filing or after." No roster claim, because there is no row for one |
— | |
ANSWERED 2026-08-27 — 3 offered, 1 STRUCK, and the one that failed is the finding. Pouya applied the offering test himself: early neutral evaluation ("A neutral assessment of merits sits inside a Q.Med's competence"), dispute-system design ("Consulting, no appointment, no neutrality question") and pre-dispute technical advisory ("It is the moat") each gained a §4 Offerings row and may now ship on /practice/. Settlement counsel is struck, as his own error in docs/01: "Settlement counsel acts FOR a party in negotiation. That is a partisan role, and putting it on a site that (a) sells neutrality and (b) asserts no licensure under D13 is wrong twice over: it undercuts the brand's central claim and it edges into acting for a client." The positioning objection comes first — it would be wrong on a site with no licensure question at all, so it is not a D13 consequence. What it exposed about the test: settlement counsel fails neither question 1 nor question 2. Run mechanically the test would have PASSED it. It fails a prior question the test does not ask — is this a neutral's role at all? — so §4 now records that the offering test presumes a neutral role, with the Q42 worked example kept as the 3–1 table. Swept by command: docs/01 §/practice/, docs/07-fees.md (three services at the hourly rate, not four), FEES.hourly's comment, and /'s areas-more comment. AGENTS.md lines 293 and 3298 are the §4 scope enumeration (fixed) and Change Log entry (d) (history, not edited) |
— | |
ANSWERED 2026-08-27 — and the ruling reversed the reasoning that had blocked it. The interim shipped the five timings as bare numbers because docs/03 §Process required them "real" rather than illustrative, which was read as barring the word "typical". Pouya: the timings are service commitments, the same class as Q27's response time — "not facts about Pouya, so they need framing, not a Verified row. Present them as the TYPICAL shape of an engagement, explicitly not a guarantee: mediation timing depends on party and counsel availability, which he does not control. Published as typical, they are honest and useful; published as commitments, the first matter that slips makes the page false." So no §4 row, and the numbers are unchanged — softening them was never the fix. What ships is PROCESS_FRAMING in src/data/site.ts: "This is the typical shape of an engagement, not a commitment. Timing depends on party and counsel availability, which I do not control." Not optional, and placed adjacent to the numbers rather than in a section lede above them — a reader who scans the strip and skips the lede has read a commitment. docs/03 §Process amended to record the override; docs/01 §/process/ requires the same rendering at step 6 |
— | |
| Q44 | R10 discharge — are all four memberships current TODAY, and in which month does each renew? §4 verifies ADRIC, ADRIO, the three OBA sections (Construction & Infrastructure, ADR, Civil Litigation) and the Canadian Tax Foundation [verified 2026-08-26 — Pouya], for now. Only Claude Code can raise this; only Pouya can close it — a two-day-old stamp is not a renewal receipt. What §4 actually says about renewal, because an earlier version of this row said more: the OBA sections and the CTF renew yearly. §4 records nothing about ADRIC's or ADRIO's renewal period, and this row asserted "All renew yearly" — a widened fact that had already propagated to schema.ts, docs/06 and /about/'s own comment. Fixed in all four. /about/ DOES NOT PUBLISH THE MEMBERSHIPS. An earlier version of this row said it did, and so did §12 R10, docs/06 and schema.ts — four documents recording a page state that had been reversed and not swept, on the one credential class that has already lapsed once (OCNI). R10 is written as a prohibition, not a disclosure duty, so the group is withheld and CREDENTIAL_GROUPS carries a TODO(pouya) with this question. Designations, Education and Certifications ship. Blocks the memberships block, not step 3. Four yes/no answers and four renewal months closes it — and the months are what stop this recurring, because they turn R10 from a reminder into a date. Raised by Claude Code 2026-08-28; scope corrected the same day by claims-auditor |
The Memberships group on /about/; memberOf in the Person JSON-LD; cutover |
| Q45 | The one-page PDF bio — generated, authored, or dropped? docs/01 §/about/ item 7 lists it (brief §VIII: an asset for circulation with appointment proposals) and it does not exist; /about/ therefore ships without a link to it, because a link to a missing file on the page an appointing body reads is worse than the absence. Why it is a question and not a task: it is a credential document, so every line traces to §4 exactly as a web page does — and it is circulated detached from the site, where no reviewer ever sees it again and no build can re-check it. That is the one artefact class this project's whole review apparatus cannot reach. Two decisions are his: (a) generated at build (a dependency and an npm audit surface, against R11) or authored once as a designed artefact; (b) whether it carries anything the site does not — a matter list, a fee summary, referees — each of which is a §4 question of its own. Raised by Claude Code, 2026-08-28 |
Nothing on the site. It is a circulation asset, wanted before the first appointment proposal |
| Q46 | Two questions the offering test cannot close by itself, and one about the glossary. (a) Is early neutral evaluation gated in Ontario, and are dispute-system design and pre-dispute technical advisory? Pouya answered question 2 (competence) for all three on 2026-08-27 and those answers stand. Question 1 is the register's and it is [unestablished] — §4: "It needs a source that a reviewer can check, and it is answered per activity and per jurisdiction, never as a class." No source exists in docs/reference/ for any of the three (R14). It matters most for ENE, which is the offering nearest §4's NOT-NEGOTIABLE boundary: a neutral assessment of the merits, delivered to parties, sits closest to "providing legal services", where the binding question is permission and the answer is not established. Consequence today: none on the site — the three appear only in comments — but the rows authorise /practice/'s "also offered" strip at step 5 and docs/07-fees.md prices them, so it blocks step 5, not step 3. Twice-flagged: claims-auditor found the register answering its own question 1 with the subject's words on 2026-08-26 and again on 2026-08-28, the second time inside the paragraph added to explain the first. (b) May §11 Glossary be published as the source for DEFINITIONAL expansions — what Q.Med, Q.Arb, C.Med-Arb, POA, SABS, ADRIC and ADRIO stand for? This is not a claim about Pouya; "he holds it" is the claim and that has a row. /about/ currently publishes all four classes on this basis, and the alternative is to strip "Provincial Offences Act", "the ADR Institute of Canada and the ADR Institute of Ontario" and recognizedBy from the JSON-LD as well — which serves no reader and reduces no risk. Sourcing them externally was tried and failed: adric.ca/designations/ redirects to /designations-cee/ and serves zero occurrences of Q.Med, Qualified Mediator or Chartered Mediator in 114,985 bytes. This is the one place this session went against a review finding, on a later finding that the standard was being applied inconsistently. Raised by Claude Code 2026-08-28 |
(a) /practice/ and its strip (step 5), /fees/ (step 9). (b) /about/'s arc and the Person JSON-LD, live now |
| Q47 | Person.jobTitle is published without worksFor, and the two halves of one §4 row are separated in machine-readable form. §4's row is "Director of Firm Operations, Toronto litigation and ADR boutique" — one row, one fact, two halves. Visible copy always pairs them (/about/: "Director of Firm Operations at a Toronto litigation and ADR boutique"). The JSON-LD emits jobTitle: 'Director of Firm Operations' with worksFor deliberately omitted, on a Person node whose url is this ADR practice's /about/ — so a consumer may attach the title to this entity, which is not what the row says. This is a structural bind, not an oversight: docs/04 instructs the omission and D16 bars naming the employer, so there is no value worksFor may take. The options are (i) drop jobTitle from the graph and let the visible copy carry the role, (ii) keep it and accept the implication, or (iii) get a §4 row for the half-claim. Flagged twice by claims-auditor, which declined to choose. Raised by Claude Code 2026-08-28 |
Nothing visible. The Person node on / and /about/, live now |
| Q38 | A true vector master for the infinity mark. The mark is a shaded ribbon — variable-width band, maroon flowing into champagne, twisting in three dimensions and passing over itself at the crossing. That is gradient-mesh artwork, and there is no honest way to express it as the flat vector paths docs/02 assumes. An SVG is held — src/assets/brand/sml-logo-source.svg — and it renders faithfully: rasterised at 8333 px it reproduces the master exactly, at the same 1.566:1 [verified 2026-08-26 — rendered and measured]. The characterisation has now moved twice and Pouya has settled it. This row first called the file "a raster in a vector wrapper"; a later draft withdrew that as unfair. The withdrawal went too far, and Pouya's ruling of 2026-08-27 restores the substance: "It renders faithfully because it IS the raster. Your first characterisation was accurate and the walk-back went too far." Both things are true at once and the row must hold both — the file is a faithful rendering and it is faithful because seven base64 PNGs are carried inside it, which is why fidelity was never the question. The Canva SVG does not close this question. Pouya: "Keep it committed, keep the AVIF render path. R13 stays open for a true vector master." What rules it out is payload and composition: 257,278 bytes against 3,063 B for the AVIF a Retina browser takes in the header — 84× — plus seven embedded base64 PNGs and a 1,225-stop gradient mesh, so inlining it would breach CLAUDE.md's rule against base64-inlining images. Restated 2026-08-27, because the single number stopped covering the case: build step 2 added a 232 px call site (the home page's approach section, rendering at 225.5 px) beside the existing 64 px one, and at that size a DPR-2 device takes 14,555 B and DPR-3 22,639 B — a ratio of ~11×, not 84×. adversarial-reviewer caught the three copies of the old figure going stale together, which is the SES-DKIM duplication in a new place. Both ladders are in docs/reference/brand-assets.md; quote the size with the call site, never on its own. Also worth knowing before someone reads it as a regression: the PNG fallback at 232 px runs 38,094 / 91,618 / 150,215 B. Nothing that negotiates content types takes it — a DPR-3 device with neither AVIF nor WebP does not exist in practice — and the AVIF above is what real clients fetch. Accepted deliberately rather than capped, because capping it would blunt the mark on the high-DPI desktops that are the only place the 696 px variant is used at all. What is wanted is a master that is faithful and light. Pouya is commissioning a proper vector master. Until it lands, InfinityMark.astro renders an optimised raster and that is a documented, temporary exception to docs/02's inline-SVG rule (R13 keeps it from becoming permanent). When the master arrives: replace the component's <Picture> with inline SVG, regenerate the favicons from it, and delete the exception from docs/02 and this row. Raised by Claude Code 2026-08-26; scoped by Pouya the same day |
Nothing — the mark renders correctly. This is fidelity and payload, not function |
ANSWERED 2026-08-26 — and the answer was that the reconstruction was WRONG. Pouya rendered the committed path against the master (SML Company Just Logo Transparent.png, 3000×3000) and rejected it on three counts. Two reproduce from the path alone: (a) TANGENT, NOT CROSSING — all four cubic branches meet the origin at exactly 90°, so the loops are mutually tangent on a vertical line and at stroke-width 28 render as two kissing circles, the one thing an infinity mark must not be [verified 2026-08-26 — tangent vectors computed per segment, independently reproduced]. Both lobes are strictly confined to their own half-plane (x is monotone on every segment), so the signed crossing number is 0 — they cannot cross anywhere, not merely at the node. Worse at the size actually shipped: the two strokes stay fused into one mass wherever the centreline separation y²/192 is under 28, i.e. across 61% of the mark's height — at 2rem that is a 16.8px blob out of a 27.4px-tall mark. No stroke-width, viewBox or scale change can produce a crossing from this centreline; (b) WRONG PROPORTION — the master's ink bounding box is 2668 × 1704 = 1.5657:1 [verified 2026-08-26 — measured], and (c) the master is a shaded ribbon where the trace was two flat uniform strokes. 1.23:1 — RECONCILED, and it was a real measurement, not a slip. It is the bounding box of the path's coordinates rather than of the curve. The control points sit at y = ±160 while the curve only reaches ±120 — the standard 3/4 pull-in of a cubic — so a coordinate-hull box is 400 × 320 = 1.2500, and with stroke-width 28 applied on all four sides it is 428 × 348 = 1.2299. Pouya's figure to four significant figures, from one method applied consistently [verified 2026-08-26 — derived]. It is a trap rather than a slip: because x is monotone on every segment, the control points give the correct width (±200) and a 33% inflated height, so the obvious sanity check — "does the width look right?" — passes. Any tool that reads a bbox from parsed coordinates lands here; getBBox() would have returned 400 × 240. And the direction is inverted from how it read: measured from the curve, the traced path is 6.4% wider and flatter than the master, not squatter. Consequence worth keeping: the declared viewBox 440:280 = 1.5714 is within 0.35% of the master's 1.566, so re-tuning the layout to the true ratio was ~0.18px of work at the shipped 2rem — and re-tuning to 1.23 would have actively broken it — at any given width 1.2299 allocates 1.5657/1.2299 = 27% more height than the mark occupies, so the header would have been rebuilt around a box a quarter too tall. The ratio was never sufficient grounds on its own; (a) tangency is, and it is exact. The traced path is deleted, not kept as a fallback, on Pouya's instruction: a wrong mark that renders is worse than a missing one, because it stops looking wrong. Now closed by Q38 for the vector master |
— | |
| (superseded — the original wording of this question is preserved in entry (v)) | src/components/InfinityMark.astro is built from geometry lifted verbatim from the deployed site's own loading-thumbnail SVG — the element it labels smlMark, fetched from https://adr.smlcompany.ca/ on 2026-08-26. Path, both stroke widths (28 / 6) and the 0.7 inner opacity are the source's; the only change is folding a translate(60 0) into the coordinates. So it is SML's own artwork rather than a redrawing — but a loading placeholder is not necessarily the canonical file, and D7 says the mark carries over unchanged. If a master SVG or AI/EPS exists, supply it and the component gets replaced. Raised by Claude Code, 2026-08-26 |
|
| Q23 | Is a Gitea act_runner registered, and are Actions enabled in app.ini? The version half is closed: the instance reports 1.27.2 [verified 2026-08-26 — /api/v1/version], so the vars context is supported and the first-step guard is belt-and-braces rather than load-bearing. What remains is not a fact to look up but a dependency on a person: git.larsnolden.com is jointly administered, and both enabling Actions and registering a runner need the second administrator. Until then npm run deploy is the path, and it is documented rather than treated as a workaround |
How deploys are triggered — not what they do |
10. Risks
| Risk | Severity | Mitigation |
|---|---|---|
| A fabricated or unverifiable claim reaches a public page | High — professional-conduct exposure | §4 register; every claim traced to a source before it ships |
| Copy silently carried over from the placeholder template | High | Nothing is ported verbatim. All copy written fresh against docs/03-content-spec.md |
| Insights section launches and then goes stale | Medium | D9 commits to 3–5 pieces at launch and a monthly cadence. A dead blog is worse than no blog |
| Personal data in the intake pipeline without a retention policy | Medium — PIPEDA | docs/05-backend-spec.md sets retention, and /legal/privacy/ states it |
| Cutover breaks the live site (D11 is a single-shot deploy) | Medium | Full pre-cutover checklist in docs/06-deployment.md; CloudFront can be rolled back to the prior origin path |
| Twenty pages of thin copy rank worse than six good ones | Medium | Each page must justify itself with substantive content. Ship fewer pages rather than padded ones |
user/pouya is a broadly-permissioned personal IAM user, and it is what has been authenticating to this account. Q22 confirms the scoped deploy user does not exist yet |
High | Fine at an interactive keyboard; never in CI, never in a Gitea secret, never in scripts/deploy-local.sh. That script refuses to run if sts get-caller-identity returns user/pouya. Create adr-sml-deploy before the first automated deploy [verified 2026-08-26] |
| The deploy secret will live on jointly-administered infrastructure. git.larsnolden.com has a second administrator. An instance admin can generally reach repository secrets, or register a runner that receives them — that is inherent to how Actions runners are fed credentials, not a flaw in this setup | High | This does not change the plan; it changes what the scoped IAM policy is for. The policy is no longer hygiene — it is the actual control standing between a shared Gitea instance and an AWS account holding another business's client-database backups. Four actions, one bucket, one distribution. Never widen it, and treat any request to as a security decision, not a convenience one [verified 2026-08-26] |
Deploy-credential blast radius. AWS account 327082975128 is not a single-project account. It also holds meshkinilaw.ca and its preview site, demesne.media, orynenergy.ca, lajirugs.ca, and mlp-clientdb-prod-backups-327082975128 (recorded in an earlier entry with the account suffix; the short form mlp-clientdb-prod-backups is used elsewhere in the docs) — which by its name holds production client-database backups. Only the name was ever observed; the contents were not inspected and the owner is not established |
High | A static deploy key for a personal website must never be able to reach a client database. The scoped IAM policy in docs/06-deployment.md grants four actions on one bucket and one distribution and nothing else — that narrowness is now load-bearing, not hygiene. Never widen it. Never reuse the user/pouya credentials in CI [verified 2026-08-26 — inventory] |
Q19 closed: production access granted in ca-central-1, confirmed in writing. Residual, tracked in §7: the ses-alerts SNS email subscription is pending confirmation, so the bounce and complaint alarms currently notify nobody [verified 2026-08-26] |
||
smlcompany.ca |
Both records added by Pouya and independently verified (Q20). SPF authenticates Google Workspace mail; SES satisfies DMARC through DKIM alignment on the three resolving CNAMEs. DMARC sits at p=none — the residual task is to review reports and tighten to quarantine, which is monitoring, not a risk [verified 2026-08-26 — DNS query] |
11. Glossary
| Term | Meaning |
|---|---|
| ADR | Alternative dispute resolution |
| ADRIC | ADR Institute of Canada — national credentialing body |
| ADRIO | ADR Institute of Ontario — provincial affiliate |
| Q.Med | Qualified Mediator — ADRIC/ADRIO designation |
| Q.Arb | Qualified Arbitrator — ADRIC/ADRIO designation |
| C.Med-Arb | Chartered Mediator-Arbitrator — senior hybrid designation; the long-term goal |
| Med-Arb | Hybrid process: mediation that converts to binding arbitration if unresolved |
| SABS | Statutory Accident Benefits Schedule — Ontario auto insurance benefits |
| LAT | Licence Appeal Tribunal — hears Ontario SABS disputes |
| POA | Provincial Offences Act |
| ENE | Early neutral evaluation |
| LSO | Law Society of Ontario |
| OBA | Ontario Bar Association |
| IESO | Independent Electricity System Operator |
| OEB | Ontario Energy Board |
| SMR | Small modular reactor |
| PIPEDA | Personal Information Protection and Electronic Documents Act |
12. Standing reminders
Surface these to Pouya at the start of any substantial session. They are open questions he has deliberately parked, not settled matters. The point of this section is that a parked decision does not quietly become permanent by never being raised again.
| # | Reminder | Raised | Why it must keep coming back |
|---|---|---|---|
| R1 | RAISED AGAIN 2026-08-28, and /about/ is why the stakes moved a second time. /about/ is "the page an appointing body or opposing counsel reads before agreeing to an appointment" (docs/01), it is the site's credential spine, and it now carries the full designation list, the education line, the certifications, the memberships and the boutique role — with no licence status stated either way. On / that reads as brevity. On a dedicated credentials page a sophisticated reader notices the omission, and D13's framing is doing more work than it was designed for. Q41(a)'s ruling is now load-bearing on the same page: the copy says the legal half is "training I hold", which is honest and is also the closest the site comes to addressing the question. Nothing here is a request to change D13 — it is the surfacing R1 exists to compel. Previous text follows. Licensure — RAISED 2026-08-27 and acknowledged by Pouya; still open, and the stakes are higher than when it was written. His words: "The interim licensure framing is now carried by shipped pages rather than specs, which raises the stakes. Keep surfacing it." So it is surfaced, and it will be surfaced again next session. The opening sentence below is now false and is kept only because §12 rows are not rewritten silently: src/pages/ exists, / ships at build step 2, and the framing is on a public page rather than in a document. Original text follows. Licensure. No site exists yet — src/pages/ does not exist. As specified, the copy will assert the JD only and describe the boutique role as active litigation exposure, never practice. Pouya flagged that as an interim position and asked to be reminded to change it. §4 now records licence status as [unestablished], which is the honest state, not a resolution |
2026-08-26 | If he is licensed and in good standing, it is the first credential appointing bodies and opposing counsel look for, and its absence from a detailed credentials page is conspicuous. If licensure is in progress, the copy should be rewritten the moment it completes. Either way this is a temporary framing that will otherwise ossify |
| R2 | Matter counts stay off the site until they are independently credible. Revisit once there is a number a sophisticated GC would find persuasive on its own | 2026-08-26 | §4 forbids them now. That rule has an expiry date nobody has set |
| R3 | The month 12–18 practice-area review — now two candidates, not one. (a) Indigenous engagement, deliberately omitted at launch (§6). (b) Tax-adjacent disputes, added 2026-08-26 on the Canadian Tax Foundation membership: it is the one credential none of the six areas touch, and valuation, earn-out, indemnity and shareholder disputes that turn on a tax position are ADR territory. Reasoning for both is in docs/01-architecture.md. One review, two candidates |
2026-08-26 | The brief rates the Indigenous niche the most valuable single one, and omission there is a timing call rather than a permanent one. The tax case is the same shape and fails for the same reason today: a practice page is a claim of present capability, and a membership is a credential, not a caseload. Both stop being true at some point, and nothing will tell you when |
| R4 | Insights cadence. D9 commits to monthly. A blog that stops is worse than one that never started | 2026-08-26 | The section's whole value is compounding |
| R5 | Fee review at 12 months. Published rates are sticky; the right moment to move them is deliberate, not reactive | 2026-08-26 | D14 is priced for where the practice is going, not where it is |
| R6 | Booking tool. Parked by Pouya on 2026-08-26; /contact/ ships with the intake form and a reserved slot for an embed |
2026-08-26 | He asked to be reminded. D10 committed to booking because it removes the back-and-forth that loses appointments — the form alone is a partial answer |
| R9 | The SES alarms notify nobody until the ses-alerts email subscription is confirmed. SES-BounceRate-High and SES-ComplaintRate-High are configured and live; the SNS email subscription to info@smlcompany.ca is pending confirmation, and an unconfirmed subscription drops every message |
2026-08-26 | A monitoring control that exists but does not deliver is worse than none, because it reads as covered. At this volume five bounces can cross the ~5% suspension threshold. Tracked in §7 and on the cutover checklist, but a one-click task nobody owns is exactly what §12 is for |
| R10 | RAISED 2026-08-28 AND NOT DISCHARGED — AND THE PROHIBITION HELD. Pouya's instruction for this session was "R10 first: re-confirm the memberships, which now includes CTF". The re-confirmation is a fact only he holds, so it is now Q44, and /about/ ships without a memberships group — this row is written as a prohibition on shipping such a page, and documenting a prohibition is not discharging it. An earlier version of this row said /about/ had shipped with all four; that was true for part of one session and was reversed on review. Three other documents recorded the same reversed state and have been corrected. Four yes/no answers and four renewal months close it; the months are the part that matters, because they turn this row from a reminder into a date. Original text follows. Annually-renewing memberships. §4 verifies the OBA sections (Construction & Infrastructure, ADR, Civil Litigation) and the Canadian Tax Foundation as of 2026-08-26 — for now. Both renew yearly. Widened 2026-08-26 when CTF was added; it was OBA-only before |
2026-08-26 | A credential that lapses quietly is the failure mode §4 exists to prevent, and OCNI already did exactly this (§4 records it as "not current, do not publish"). Re-confirm at each renewal, and before any page listing memberships ships — /about/ at build step 3 is the first one that will |
| R11 | Re-check dependency currency at every phase boundary in the build order (docs/01-architecture.md §Build order, 11 steps). Run npm view <pkg> version across every pin in package.json and compare; do not wait for something to break. Verified does not mean latest — record the reason for any deliberate hold in §7. Carries one specific re-add trigger: at step 7, put @lhci/cli back. Check for a patched release — do not assume 0.15.1 is still the ceiling just because it was on 2026-08-26 — pin it against npm view that day, write a lighthouserc with the budgets from docs/04-seo-spec.md, and delete the UNAVAILABLE notices this repo now carries in six places (§7 lists them). If the advisories are still unfixed, that is a decision to take deliberately, not a reason to leave the gap unstated |
2026-08-26 | astro: "^5.0.0" was recalled rather than checked and was two majors stale the day it was written, which meant a framework carrying high-severity XSS advisories. Between phases is cheap; after a phase of pages is written is not. The build order has ten more boundaries |
| R12 | compressHTML: true is a deliberate deviation from the Astro 7 default ('jsx'). Measured 2026-08-26: in an .astro template an inline pair split across two lines renders as <em>a</em><strong>b</strong> under the default — the space is silently deleted. MDX prose is unaffected |
2026-08-26 | It is a deviation, and undocumented deviations become folklore. Revisit with a measurement, not a preference — and re-measure after any Astro major, since the behaviour could change again |
| R13 | The infinity mark ships as a RASTER, and that is temporary. RAISED 2026-08-27; Pouya ruled the committed SVG does NOT close it — "Keep it committed, keep the AVIF render path. Your own measurement is the reason: 257 KB wrapping seven embedded base64 PNGs. It renders faithfully because it IS the raster." So the exception stands and the reminder stays live. InfinityMark.astro renders an optimised AVIF/WebP from src/assets/brand/sml-infinity-mark.png — a deliberate, documented exception to docs/02's "inline SVG, never a PNG", because the mark is gradient-mesh artwork and no true vector master exists yet (Q38). Removal trigger: the commissioned vector master lands. Then replace the <Picture> with inline SVG, regenerate favicon.ico and apple-touch-icon.png from it, and delete the exception from docs/02, from the component, and from Q38 |
2026-08-26 | Pouya flagged this himself when he made the ruling: an interim raster is exactly the kind of temporary measure that becomes permanent by never being raised. It costs ~8 KB and works, which is precisely why nobody will notice it again. There is no build error to prompt anyone — only this row |
| R14 | Anything a spec makes a claim about must be reachable from the repository. If the artefact lives only in Drive, in a console, or on someone's disk, no reviewer can check the claim and the claim is unverifiable by construction — see the rule now in CLAUDE.md |
2026-08-26 | This has now cost twice. Q24 was AWS-Hosting-Guide.md, the only record of how the infrastructure was hand-built, living outside the repo. Q32 was the infinity mark: Claude Code traced it from the old site's loading placeholder, wrote a scrupulous provenance comment recording exactly that doubt — and two adversarial review passes still could not catch it, because the artwork they would have had to compare against was not in the repo. The doubt was correctly stated and structurally uncheckable. That is the failure mode: not a missing warning, a missing artefact |
| R15 | Every page shares ONE Open Graph image — the portrait — and that is an interim for seventeen of the nineteen. RULED, DEFERRED, AND TRACKED HERE ON POUYA'S INSTRUCTION: "Record the trigger so it cannot quietly become permanent." Q40, 2026-08-27. The portrait is the decided card for / and /about/ — a face is the strongest social preview for a personal brand. It is the wrong card for the other seventeen, where a typed card carrying the page title does the work. Removal trigger: build step 7, where Insights needs per-article cards anyway — one generator (satori or astro-og-canvas, pinned against npm view that day per R11), one dependency, one review. Then delete the interim note from docs/04 and the cutover line from docs/06. Blocks cutover; does not block steps 3–6 |
2026-08-27 | Nobody on this project will ever see the defect. A link preview is rendered by LinkedIn, Slack and Teams for a reader who is not us, and docs/04 requires a unique title and description per page — nineteen unique pages that all preview identically is the failure, and it is invisible from inside the repo. It costs nothing and works, which is exactly the R13 shape |
RATIFIED / SUPERSEDED 2026-08-26. (a) Cache-policy table matching the pipeline — accepted; documenting what the pipeline does beats documenting an intention. (b) s3:AbortMultipartUpload omitted — accepted, reasoning corrected: the lifecycle rule does not exist and is therefore not the cover; the actual cover is that aws s3 sync only goes multipart above 8 MB and the largest asset is a 357 KB portrait. Recorded in docs/06-deployment.md with a revisit trigger. (c) The aws s3 ls pre-flight — superseded by the variable guard now running as the workflow's first step |
2026-08-26 | — | |
PROMOTED TO A RULE 2026-08-26. A reminder was too weak for a pattern that survived three entries. A sweep is a command, not a claim now sits in CLAUDE.md under Conventions, in /build Phase 6, and in /wrap step 3: any claim that a change was applied across files must cite the command and be written only after reading its output |
2026-08-26 | — |
Change Log
2026-08-28 (x) — Build step 3: /about/ ships without its memberships. Q40–Q43 close; four review passes found nine defects in their own predecessors' fixes
Task, in Pouya's words: rulings on Q42, Q41(a)(b)(c), Q43 and
Q40, then "step 3, /about/ — and R10 first: re-confirm the memberships,
which now includes CTF."
Headline, because it is the one thing to read if nothing else: /about/
ships without a memberships group. R10 is written as a prohibition on
shipping a page that lists memberships before they are re-confirmed; the
re-confirmation is a fact only Pouya holds; it was not obtained. The first
version of the page published all four and disclosed the gap in five places
instead — both review agents rejected that, and they were right. Q44 carries
the exact question.
Standing reminders surfaced (§12)
- R1 — licensure. Raised again, and
/about/is why the stakes moved. It is the site's credential spine, it now carries designations, education, certifications and the boutique role, and it states no licence status either way. On/that reads as brevity; on a dedicated credentials page a sophisticated reader notices. Q41(a)'s ruling is now load-bearing on the same page — the copy says the legal half is "training I hold", which is the closest the site comes to addressing the question. Nothing here asks to change D13. - R10 — raised and NOT discharged. See the headline. Now Q44.
- R13 — the mark is still a raster. Unchanged; no vector master has landed.
- R11 — phase-boundary currency check RUN, not recalled. Every pin in
package.jsonagainstnpm view: 13 of 14 exact. The one hold istypescript ^6.0.3against7.0.2, and the reason was re-verified rather than carried forward —@astrojs/checkpeers^5.0.0 || ^6.0.0andtypescript-eslintpeers>=4.8.4 <6.1.0. Both bar 7.npm audit: 0 vulnerabilities.[verified 2026-08-28] - R15 — NEW. The single portrait OG image across nineteen pages, per Pouya's Q40 ruling, with build step 7 as the removal trigger.
Rulings applied
Q42 — three offerings rowed, settlement counsel struck. ENE, dispute-system
design and pre-dispute technical advisory gained rows; settlement counsel was
struck as Pouya's own error in docs/01, on the ground he named first —
"it undercuts the brand's central claim" — with the D13 half as aggravation
rather than reason. Swept by command; output below.
And the strike exposed a hole in the offering test. Settlement counsel fails neither question 1 nor question 2; run mechanically the test would have passed it. It fails a prior question the test does not ask. §4 now states that prior question — is this a role in which the practice is partisan between the parties to a dispute? — with the Q42 worked example kept as the 3–1 table.
Q41(a) — Q37 reaches prose, and prose is held to a HIGHER bar. Deleting the
parallel was half the fix; a reader supplies the missing symmetry from silence.
The sentence that names which half is which is now one constant,
ASYMMETRY_LINE, because it had been typed into two pages and the two copies had
already diverged inside the same session — a comma on one, full stops on the
other.
Q41(b) — not restored, and the comparative turned out to be in three places,
not one. Pouya's replacement wording is used verbatim in docs/03's core
positioning statement. Then the second audit found the implication had been
relocated, not removed: a new instance written into the /about/ biography,
plus two pre-existing instances on / that had survived the sweep that closed
Q41(b) the day before — "usually arrives as a separate expert report" and
"not something a party has to commission and wait for". All three gone.
Q41(c) — verified against the Tribunal's own materials, and the interim was
ambiguous in the one word that mattered. New extract:
docs/reference/lat-case-conference.md. Rule 2.4 makes "Pre-Hearing
Conference" the LAT's own term for a case conference; Rule 14.3 puts a
Member in the chair and disqualifies them from the hearing panel; Rule 14.6
makes attendance mandatory; the Rules contain zero occurrences of mediat or
arbitrat in 66,593 characters. The affirmative basis is the Tribunal's own AABS
page: "you may want to consider negotiation or mediation services… including
before filing at the LAT-AABS, and continuing… after a claim has been filed."
Q43 — the timings are service commitments; framing, not a row. Numbers
unchanged. PROCESS_FRAMING renders adjacent to the strip, not in a lede above
it, because a reader who scans the strip and skips the lede has read a
commitment. docs/03 §Process amended to record that its "real timing"
requirement had been over-read as barring the word "typical".
Q40 — bundled to step 7, not shipped as a second interim. Portrait is the
decided card for / and /about/; the generated typed card covers the rest,
built once with Insights. Tracked as R15, blocking cutover, not step 3.
/about/ — what shipped
Six of docs/01's seven items. Item 6 (Speaking) omitted on the spec's own
instruction. Item 7's PDF not shipped — Q45. Item 3 ships Designations,
Education and Certifications; Memberships withheld — Q44. Sections ordered
4-before-3 deliberately: the arc is the part a reader has a question about.
Four review passes, and what they cost
Two agents, twice. 28 findings, then 15 more. Nine of the second round's
findings were defects in the first round's fixes — which is the measurement
/build Phase 4 exists on.
Three that must never have shipped, all mine:
- The false universal Q39 struck, on a public page. The arc's Q.Arb body read "arbitral appointments are not gated behind it, which is why I accept them now" — unscoped, first person, publishing a proposition of Ontario law §4 holds only in scoped form and deliberately does not stamp. Q39 swept three instances on 2026-08-27; this was the fourth and the first outside a comment. Found independently by both agents.
- A public warranty of currency over an undischarged R10 — "Memberships are renewed annually and are listed as current." Also widened §4, which records yearly renewal for the OBA sections and the CTF only and says nothing about ADRIC or ADRIO. The widened form had propagated to four files.
- A licensure denial — "Nothing above asserts a licence to practise law, in
either direction" — on the page §4 marks
[unestablished], where the rule is do not assert it, do not deny it. No spec asked for it.
And the same sentence was wrong in both directions, which is the finding worth keeping. Audit 1 flagged "I do not give legal advice" — "do not" describes an election, and an election implies the entitlement. So it became "I cannot give legal advice". Audit 2 flagged that — "cannot" is a denial. Both readings are correct and they point in opposite directions, because both sentences make a claim about capacity. The third version does not: it states the role and its consequence for the reader.
Defects in my own fixes, all found by measuring the fix rather than reading it:
:where(.prose) > p + pis additive with a flexgap—/'s approach paragraphs went 24px → 48px. My comment said "Verified: with:where()the flex container's gap governs and this contributes nothing." False, and measured false minutes after being written::where()lowers specificity, which only matters when two rules set the same property.- The 1440w portrait rung removed a 1.07× upscale and imposed +27,273 B on DPR-3 phones (48,799 vs 21,526). Added 1080; that sample is now 27,594 B and exact.
- Gating the italic preload behind a new prop and never setting it on
/, which uses the face in a 96px headline. Pill's type fix, twice:interface Props {}is rejected by eslint and means "any non-nullish value" anyway;Record<string, never>passes eslint and rejectsclassand also rejectschildren, breaking two real call sites while the probe page went green on exactly what it tested..designation-part { white-space: nowrap }fixed an orphaned separator and took/about/to 108px of overflow at 320px at a 200% default font size.- The print override, scoped by class, missed two elements and then a third:
.approach-metaphor,.btn-gold, and — after the token rewrite — the hero.btn-primaryat 1.07:1 against white paper, which sits on cream inside no inverse section at all.
The portrait defect was the expensive one, and it was on the shipped home page
too. Below 66rem the hero is one column, so the portrait is the full content
width — 592 to 928 CSS px — while sizes declared 52–60vw and the ladder
stopped at 960w. Measured 1.40× upscale at 768/DPR2 and 1.93× at 1024/DPR2,
identical on both pages, surviving a cleared cache. /'s own comment derived the
960 ceiling from the ≥66rem layout: the range where the image is widest was
never in the arithmetic.
Declined, with reasons:
- "I will tell you whether I am the right neutral for it" flagged as an unrowed
service commitment. It is a statement about a conversation, not a metric a
slipped date could falsify, and it ships identically on
/. - Empty-collection guards on
CREDENTIAL_GROUPSandlitigationAreas. Both areas consttuples; neither is reachable without a source edit. Person.hasCredentialcurrency (Q.Med asHeld, present tense, on a[verified 2026-08-25]row under a "currency is not confirmed" preamble). The row exists, so it traces. Noted for Pouya rather than resolved.- The
ContactBandCTA invites matter details on pages carrying no no-retainer notice.docs/03scopesNO_RETAINER_NOTICEto/contact/, which is where the CTA goes. Worth a ruling before the band reaches nineteen pages.
Reversed a review finding once, deliberately — the designation-name
expansions. Audit 1 struck them as §11-Glossary-only; audit 2 then found the page
also publishes "Provincial Offences Act", "the ADR Institute of Canada and the
ADR Institute of Ontario" and recognizedBy, all on the same footing —
"one standard or the other." Standard chosen: §11 is the source for
definitional expansions; §4 remains the only source for claims about Pouya.
Sourcing externally was tried and failed — adric.ca/designations/ redirects and
serves zero occurrences of Q.Med, Qualified Mediator or Chartered Mediator in 114,985 bytes. Q46(b) asks Pouya to ratify the standard; if he
declines, all four classes come out together.
The third review round, and a process defect of my own
The code re-review returned 8 more findings, and it opened with a criticism
of how I ran the loop that is correct and is recorded here rather than softened:
I edited the tree while it was measuring. It anchored to SHA-256, re-verified,
and reported that four of the defects it had measured "were repaired under me
mid-review" — so its certification covers a snapshot, not the tree, and the
repairs it watched me make were reviewed by nobody. /build Phase 4 says to
re-review material fixes; it does not say to hold the tree still while a review
is in flight, and it should. Next session: snapshot, review, then fix.
The four it watched being repaired were all real and all measured: the lost
italic preload on /, the 24→48px prose regression, three elements the print
block missed (rasterised from printToPDF at 100 dpi: 2.49:1 and 3.96:1 against
white), and dead page-scoped CSS whose compiled selectors carried the old page
cids while the rendered elements carried ContactBand's.
Two of the eight were defects in fixes from this same session:
/keptfetchpriority="high"while/about/withheld it on the identical measurement. Portrait visible px: 0 at 320, 0 at 360, 0 at 390, with LCP measured asP.hero-lede— so the attribute promoted 27–49 KB of image the reader cannot see above the face that paints the actual LCP element, on the axis the ≥95 budget is measured on. Removed.loading="eager"stays; the portrait is the LCP element from 768px up.- The 1080 rung I added closed the 390px gap and missed the two largest current iPhones. 428@3 and 430@3 need 1140/1146 and took 1440 — 48,799 B, against 27,594 for the device the rung was tuned for: +21,205 B, 13% of page weight. A 1200 rung closes it at 1.05x. Third iteration of this ladder.
And one of my stated reasons was simply wrong about ARIA. I removed
role="list" from the arc <ol> on the ground that it "re-announces an ordered
list as an unordered one". It does not — both <ul> and <ol> map to the
list role, so on an <ol> it is a no-op for ordering. What it is for is the
WebKit heuristic that strips list semantics from a list with
list-style-type: none, which .arc sets. Restored, and .arc's hand-written
padding: 0; list-style: none deleted because global.css's ol[role='list']
reset already supplies both. Not verified: whether WebKit's heuristic covers
<ol>. No Safari instrument here, so the role stays on the precautionary side.
Also fixed: ContactBand shipped eyebrow?, cta? and a named heading slot
with zero call sites overriding any of them — the pattern this repo has
already deleted twice, in Eyebrow and SectionHeading, with the reasons written
into the source. All three gone; the empty Props guard stays, because that is
what makes <ContactBand class="x" /> a build error. .section-head moved to
global.css (byte-identical in two pages, seventeen to come). sizes corrected
in the 66rem–80rem band, where 42vw/38vw described neither track — measured
36.0% and 32.5% — costing up to 1.45x over-fetch at 1200/DPR 1.
Two residual over-fetches left deliberately and recorded in the source: 320@2 at
1.18x (no rung between 480 and 640, and 480 would be an upscale) and 1056@2 at
1.26x, where 380.2 x 2 = 760.4 misses the 760 rung by four tenths of a pixel.
Declaring 35vw to duck under it would make sizes less truthful across the band
for a 0.05% upscale. The declaration stays honest.
docs/02's reflow table is corrected (65 → 63 px, re-measured) and now
carries an /about/ row — the first page measured against that table rather
than establishing it. It records that .designation-part { white-space: nowrap }
was itself introduced as a fix, for an orphaned separator, and created a
108px reflow regression at 320px.
Register defects the audits found in §4 itself
claims-auditor caught the register answering its own question 1 with the
subject's words — twice, the second time inside the paragraph added to explain
the first. The three new Offerings rows carried gating conclusions
("no designation is required to give a non-binding evaluation") under a
[verified — Pouya] stamp, which §4's own box says cannot carry a proposition of
law. Q1 is now [unestablished] on all three, the worked example's Q1 column is
emptied, and Q46(a) carries it. Nothing publishes on it today; it blocks
/practice/ at step 5.
Also: four documents recorded that /about/ publishes memberships after the
page had been changed not to — §9 Q44, §12 R10, docs/06 and schema.ts. And
the widened "all renew yearly" survived in three of them plus
.claude/agents/claims-auditor.md itself, which the auditor found in its own
brief. Third stale claim located inside that file.
Verified — measured, not asserted
npm run check 0 errors / 0 warnings / 0 hints
npm run lint eslint clean; prettier clean
npm run build 2 pages, no errors
npm audit 0 vulnerabilities
grep -rE 'animation:[^;}]*(scroll\(\)|view\(\))' dist --include='*.css' → no match
- Portrait ladder: 0 upscaling — across 24 synthetic samples and then across 11 real device profiles on both pages (was 1.40× / 1.93×). Worst oversize 1.26×, at one viewport, recorded in the source with the reason.
.section-head: 48 px on all seven wrappers after the rule moved toglobal.css— re-measured, because moving a rule that exists to work around the parent/child scope trap is exactly where that trap recurs.- Overflow 0 and 0 over-wide elements at 13 widths on both pages — elements measured, not only the document.
- Contrast: 0 failures of 127/127/128 painted pairs on
/and 86/86/88 on/about/, at 390/768/1280. - Print, against white paper: 0 failures of 89 and 48 visible text elements under print-media emulation (was 1.07:1 on the arc, the contact band and both CTAs).
- 200% default font size:
/about/63 / 23 / 0 px overflow at 320/360/390, against/'s 63 / 23 / 3. The 320/360 residual is the headerA.brand, whichdocs/02already accepts. - Reveal: 0 of 10 and 0 of 4 hidden under
prefers-reduced-motion: reduceand under print. - One
<h1>; outlineh1 → h2 → h3with no skips; 30 tabbable elements of 37 in the DOM (the 7 are closed<details>contents, correctly untabbable and correctly ringed when open). - Bio: 409 words by my count, 411 by
adversarial-reviewer's (spec 400–600). The two differ on whether a standalone em-dash is a token; both are inside the range and neither is presented as the number. It was 385 at first review, which is the point of measuring it — the source comment then claimed the count had been verified against "the verify step of this session's Change Log entry", a document that did not yet exist. - Zero
<script>other thanapplication/ld+json; no.jsindist. - Lighthouse: NOT RUN — tool unavailable (R11, step 7).
- HTML validator: NOT RUN. The duplicate-
1xsrcset conformance error recorded at step 2 stands; no "validator clean" claim is made.
Sweeps — the command, then the output
$ grep -rni "settlement counsel" . --include='*.md' --include='*.ts' --include='*.astro'
AGENTS.md:293 (§4 scope enumeration — REMOVED from the list, pointer added)
AGENTS.md:567 (§9 Q42 — now ANSWERED, records the strike)
AGENTS.md:3298 (Change Log entry (d) — history, not edited)
docs/07-fees.md:128 → three services, not four
docs/01-architecture.md:235 → struck from the strip, with Pouya's reasoning
src/data/site.ts:236 → FEES.hourly comment
src/pages/index.astro:423 → areas-more comment
Every live instance struck or converted to an explicit prohibition. A
whitespace- and blockquote-insensitive sweep was needed to find them all: a
plain grep for take on faith returned four hits and missed docs/03
entirely, because the phrase wrapped across a newline behind a > marker — the
positioning statement, i.e. the one that mattered.
$ (normalised sweep for "all renew yearly" / "/about/ publishes")
→ every remaining match is a QUOTATION INSIDE A CORRECTION, read individually.
$ (rendered-text-only forbidden sweep, printed with context)
→ 4 matches, all read: "a licence question" (software licence, x2),
"I do not act for a party" (the protective negation), "their own legal advice"
(advising the reader to get advice). The `aLSO` / `pLEADINGs` family.
$ grep -rn "law degree on one side" --include='*.astro' --include='*.ts' --include='*.md' .
docs/03-content-spec.md (spec quotation, canonical form)
src/data/site.ts (the constant)
→ one authoring location.
Instrument errors caught before acting — six this session
CLAUDE.md's rule earned its place again. None of these reached a report:
- A
<title>probe using a regex that cannot match a<title>element → reported "MISSING" on both pages. - A link checker counting
?v=1query strings as missing files. /at 390/DPR1 reading as 2.81× oversized — an HTTP-cache artefact. Cleared: 0.90, correct.- "7 focusables with no focus ring" — closed
<details>contents, correctly untabbable, correctly ringed when open. - A
<picture>regex that grabbed the header's infinity mark instead of the portrait, reporting the AVIF ladder as 0 bytes. /about/'s print button reading as gold because print media was emulated after navigation on one page and before it on the other.
And one non-error worth recording: adric.ca/designations/ really does serve
zero designation names — but only checking the raw bytes established that,
because 4,840 characters extracted from 114,985 is the signature of a
JS-rendered shell, not of an absence.
Corrections to comments that asserted the opposite of the measurement
Recorded because the confident ones were the false ones:
- "The
<picture>WRAPPER carries no cid, so it needs:global()" — false. The emitted markup is<picture data-astro-cid-ta2fbyqs="true">, and InfinityMark's barepictureselector compiles topicture[data-astro-cid-usztftas]and works.:global()dropped on both pages. - "The LCP element on this page is the
<h1>— two words of 96px serif" — false twice. LCP at 1280 isIMG.portrait-img(229,679 vs the h1's 51,484), and--text-5xlcomputes to 76px, not 96. - "above the fold at every width" — false. The portrait is entirely below
the fold at 320 and 360, the two widths
docs/02names. - "
--text-metaon cream measures 3.07:1" — false, in two files. It is 5.47:1; 3.07 is the ratio on ink, which is whattokens.csssays. - "the components have had their
classprops deleted so passing one is a build error" — false forPill, which had noPropsinterface at all, so its call sites were unchecked.CLAUDE.mdnamesPillas the next place the parent-scope defect will happen; the guard documented as protecting it was absent on exactly it. - A comment citing "the word-count assertion in the verify step of this session's Change Log entry" — which did not exist yet. The same shape §9's Q39 row already records as a fair catch.
Files
New: src/pages/about.astro, src/components/ContactBand.astro,
docs/reference/lat-case-conference.md. Changed: AGENTS.md (§4, §9, §12),
CLAUDE.md-adjacent agent brief, docs/01, docs/03, docs/04, docs/06,
docs/07, src/data/{site,schema}.ts, src/layouts/BaseLayout.astro,
src/components/Pill.astro, src/styles/global.css, src/pages/index.astro.
Open for Pouya
Q44 memberships (blocks the group + memberOf + cutover) · Q45 the PDF
bio · Q46 (a) ENE / DSD / advisory gating, (b) §11 as a source for
definitional expansions · Q47 jobTitle without worksFor · plus
Q23, Q34, Q38. Live reminders: R1, R10, R13, R15.
Next: step 4 — /mediation/, /arbitration/, /med-arb/. /arbitration/
is where §4's paired-disclosure condition is hardest: it offers all three forms
and must state the Q.Arb stage plainly, "and neither half may be dropped."
2026-08-27 (w) — Build step 2: / ships. Q35, Q37 and Q39 close, and Q39's answer was that this register had been reasoning from a falsehood
Pouya ruled on all five parked items in one message. Four are implemented here; R1 is acknowledged and stays live by design. The most important of them did not confirm this file — it corrected it.
Q39 — ANSWERED, and my assertion was FALSE as a universal
His words: "My assertion was FALSE as a universal; correct it, then close the
question simply." He checked rather than defended, against
ontario.ca/page/training-family-arbitrators.
Family arbitration in Ontario is gated. Prescribed training: 14 hours on
screening for domestic violence and power imbalances, 30 hours of Ontario family
law for arbitrators outside the Ontario or another Canadian bar, and 10 hours
ongoing per two-year period with five on domestic violence or power imbalance.
claims-auditor produced this counter-example from inside the repo on
2026-08-26 and it was right.
The source is now IN THE REPO — docs/reference/ontario-family-arbitration- training.md, retrieved 2026-08-27 with its provenance and method. That closes
R14 for the half of the proposition it covers, and §4 now says which half that
is, because the distinction matters more than the citation:
- The extract establishes the family gate directly.
- It establishes the commercial half only by absence — it neither mentions nor excludes commercial arbitration. A government page about family arbitrators is not authority for what a commercial arbitrator needs.
So "commercial arbitration in Ontario requires no licence and no designation" is recorded as Pouya's stated position, deliberately unstamped, with the citation attached to the family half only. A footnote must not be allowed to promote the scoped form back into a universal.
What actually disposes of the question is the scope exclusion, not the law.
Pouya does not do family arbitration. §4 Offerings carries it as NOT
OFFERED — a scope exclusion, not a gating problem, so the prescribed-training
requirement never bears on this practice at all. Shareholder & Family Business
means commercial disputes among family shareholders; its page must say plainly
that family law matters are not accepted — one sentence, not a section, on
his instruction that it earns its place only by saving a wasted intake call.
Recorded in docs/01 §/practice/shareholder/ for build step 5.
The false universal had survived in three more places, and the grep found them
Correcting the paragraph that carried it was not the fix. grep -rn "Anyone may be appointed" plus a paraphrase sweep for No designation is required:
$ grep -rn "anyone may be appointed\|Anyone may be\|No designation is required" \
--include='*.md' --include='*.astro' --include='*.ts' .
AGENTS.md:238 <- my new text, quoting what was struck (correct)
AGENTS.md:313 <- §4 BOUNDARY bullet: "Mediation and arbitration are ungated" FIXED
AGENTS.md:548 <- §9 Q33 closure row, bolded as an assertion FIXED
AGENTS.md:774 <- Change Log entry (v), append-only history (correct)
src/components/SiteHeader.astro:141 <- a live comment in shipped source FIXED
The §4 instance is the one worth dwelling on: it was in the paragraph headed THE BOUNDARY, AND IT IS NOT NEGOTIABLE, which is the worst place in this file for an unscoped claim about a class of activity. The boundary paragraph was breaching its own boundary.
Q35 — ANSWERED, all four items, and the answer supplied a gate that was missing
- Med-Arb — "he holds Q.Med and now accepts arbitration appointments, so
both halves are live. Keep it in the nav. Removing it unilaterally orphaned
the page — restoring it was right." Now its own §4 Offerings row. It stays in
the footer sitemap;
docs/01keeps it out of the primary nav deliberately and that is unchanged. - The six subject-matter labels — "subject-matter labels, already governed
by
docs/03's 'positioning, not history'. They pass test 1; test 2 is the page's job." - THE GATE — "Add the publication gate you identified was missing... That hole is why Med-Arb sat in the footer under a paragraph telling readers not to infer it." §4 gains Subject-matter areas — the publication gate: a practice area may be named where (1) he can competently accept an appointment in that subject matter, and (2) the page frames it as positioning without claiming history. Condition 1 is about the label; condition 2 is about the page. Nothing in the nav or footer is unrowed or ungated any more.
Q37 — ANSWERED. The label changes, and the reasoning is the finding
JD + ML is labelled "Legal training and engineering practice", not "Law and
engineering". Pouya: "The parallel was doing the implying — a degree and a
practice under one noun. The asymmetry is the honest part." A JD is a degree;
engineering is a verified practice. Rendering them as two instances of one thing
invited the reader to supply the symmetry, and for "Law" the missing half is a
licence. It is longer and deliberately lopsided; CredentialRow carries a
comment telling the next reader not to tidy it back into a parallel.
Q38 / R13 — the committed SVG does not close it, and my walk-back went too far
Pouya: "Keep it committed, keep the AVIF render path. Your own measurement is
the reason: 257 KB wrapping seven embedded base64 PNGs. It renders faithfully
because it IS the raster. Your first characterisation was accurate and the
walk-back went too far." Q38 and docs/02 now hold both halves at once — the
fidelity is real, and it is bought with embedded raster, which is exactly why
fidelity was never the question. R13 stays open.
Q32 — Pouya ratifies the reconciliation, and identifies that his own step 3 would have broken the layout
"My 1.23:1 was measured from the path's coordinate hull rather than the curve, and step 3 of my Q32 ruling would have broken the layout had you followed it. Your reconciliation was right. The tangency was the only sound ground of the three." Recorded because a ruling that turns out to be wrong in one of its three grounds is exactly the thing this file exists to keep visible.
Build step 2 — / ships. Zero JavaScript, and six components
Eight sections specified in docs/01 §/; seven are built. Hero ·
credential row · the approach · two processes · six practice areas · process
preview · contact band. New components, all from docs/02's list: Eyebrow,
SectionHeading, Pill, CredentialRow, PracticeCard, ProcessStep, plus
src/data/schema.ts for the JSON-LD.
Section 7, Latest insights, is NOT built, and that is the only spec item this
page does not deliver. src/content/insights/ is empty; the collection, the
ArticleCard component and the drafted slate all arrive together at step 7
(docs/01 §Build order, D9). Rendering it now means shipping a component's
scoped CSS to every visitor for an empty block, plus a props surface with no
call site — already an open finding against InfinityMark. SiteHeader gates
the Insights nav item on the same collection, so the page and the nav appear
together. The omission is commented in the page source, not just here.
Deviations from spec, each deliberate and each recorded in place:
- The credential row has FOUR slots, not
docs/01's three. §4 Offerings' paired-disclosure condition is the higher authority and requires the stage of the arc stated wherever arbitration is offered;/says arbitration appointments in its second sentence, so the stage belongs on this page and not only in the footer.docs/03already authorised a fourth slot and now records that on/it is required. - No booking link in the contact band, which
docs/01item 8 asks for. Booking is parked (R6) andCONTACT.bookingUrlisnull. Stated, not silently dropped. - The masthead tagline is suppressed on
/. It is the same string as the hero eyebrow, so at ≥76 rem the page opened with the same six words twice — step-1 review finding #10, left open to "decide at step 2". The hero keeps it (docs/01puts it there and gives it copy underneath to qualify it); the masthead drops it, because the masthead is the placement Q33-orig objected to. This only ever removes a claim from one page. - The step-1 proof sheet at
/type-scale/is deleted, as its own comment,InfinityMark's comment andastro.config.mjs's sitemap filter all said it would be. Five live references to it survived the deletion and were found bygrep, including §7's Client JS row, which citeddist/type-scale/index.htmlas the file proving the site ships no JavaScript.
R11 at the phase boundary — two pins were stale, one hold was too wide
Run before writing any code, which is what R11 asks. astro 7.2.7 → 7.2.9
(two patches inside 48 hours). typescript ^5.9.3 → ^6.0.3 — a full major
behind and installable. §7 had recorded the TypeScript hold as "held at 5.x",
which was too wide by a whole major: 7.0.2 is genuinely blocked
(typescript-eslint@8.68.0 peers >=4.8.4 <6.1.0, @astrojs/check@0.9.10
peers ^5 || ^6), but 6.0.3 is stable and both peers accept it. The row now
names the version, the blocker, and the condition that ends the hold. Committed
separately as 8a2f513 so step 2's verification is not confounded by a
toolchain change.
What the claims audit found — ten defects, and every one was implication or scope
claims-auditor returned FAIL. Not one finding was a fabrication of the kind
the old site carried; all ten were implication, scope, or a fact restated
loosely — which is exactly where D13 says the risk lives. All ten are fixed or
escalated. The four highest-value ones:
-
I mediate and arbitrate commercial disputes. §4 verifies that he accepts arbitral appointments, and separately verifies "multiple completed sole mediations" — there is no counterpart row for a completed arbitration. Present-indicative "arbitrate" beside "mediate" invites the reader to supply a track record for both. Now offering-shaped: "I mediate commercial disputes from Toronto, and I accept arbitration appointments." The clean form was already on the same page, in the arbitration card. -
The JSON-LD asserted arbitration twice and stated the stage nowhere. The visible page satisfied §4's paired-disclosure condition with the fourth credential slot; the machine-readable graph did not. Both node descriptions now carry "the Q.Arb pathway commenced August 2026", and
hasCredentialstays Q.Med-only — the stage belongs in prose, never in a field that means holds. A crawler-only claim is still a claim, and this is the first time the register has had to say so. -
at one published rate with preparation time includedmisdescribed money. Checked againstdocs/07-fees.md: D14's card sets two day rates ($2,000 half, $4,000 full), so "one published rate" reads as one price for both. Anddocs/07says in terms that the bundled prep is a capped allowance which "must be stated on the page — 'including 2 hours of preparation'... Do not quietly fold it into the hours figure." A home card is the wrong place to state it properly and stating it improperly is worse than silence, so the fee claim is gone from/in both places it appeared. "Published" was also forward-looking:/fees/ships at step 9. -
Law and engineering are not blended here— Q37's struck parallel, relocated from the credential label into body copy one day after Pouya struck it, and strengthened by attributing both halves to him personally. The section now reads "what the documents say, and what the engineering says": asymmetric, which was Pouya's whole point. The argumentdocs/01anddocs/03specify is unchanged. Whether Q37 formally reaches prose is his call — Q41(a).
Also fixed: "what else is offered" asserted four processes with no §4 row
(Q42); "LAT pre-hearing mediation" was a docs/01 search intent read
as a service (Q41c); "facts most neutrals take on faith" is an
unverifiable comparative about third parties, replaced with "the documents
rather than the pleadings" (Q41b); the editorial "with real timing" is
gone while the timings themselves stand as specified (Q43); and the Q.Arb
noun is now §4's own — pathway, not designation.
And the auditor found a stale copy of a fact in §9 itself. Q8 still listed
memberships as "ADRIC, ADRIO, OBA sections" — the Canadian Tax Foundation
was missing, added to §4 on 2026-08-26. A second copy of a fact in a place
nobody re-reads is the failure mode §7's single-source rule exists to stop, and
this is the same shape as the stale enumeration that was found inside
claims-auditor's own brief. Fixed, and rewritten as a pointer to §4 rather
than a second list. It would have bitten at step 3, the first page to publish
memberships.
Seven defects I found in my own work before the reviewers reported
Kept because the pattern in them is the point, not the count. Full list in the verification section; the three worth reading:
-
<Picture widths>declared the untouched 1600 px master as the<img src>fallback — 254,626 bytes, for a 476 px slot. Passingwidth/heightalongsidewidthspins it to the 960 variant (78,665 B) and the 1600 px file stops being generated. The build log hid it completely: it printed "before: 349kB" for every variant either way. Found by reading the emittedsrcand that file's real dimensions. -
Naming a prop
assilently turned off prop type-checking for a whole component.astro checkreported it only asts(6196) 'Props' is declared but never used, which reads like lint noise. It is not:<Eyebrow dot as="h9" bogusProp={1} />compiled with 0 errors, while the same probe against the four other new components producedts(2322)on all four. Renaming the single identifier totagrestored it. Do not silence ats(6196)withAstro.props as Props— that hides the warning and leaves every call site unchecked. -
96 px of cream sandwiched between the maroon contact band and the ink footer. Found by looking at a full-page screenshot, not by reading CSS.
And one alarm of my own that was wrong, in the direction of panic
Measuring the marks, img.naturalWidth came back 64 at every DPR — which
looks exactly like the density ladder is not being generated at all, i.e. a
shipped step-1 defect on the header mark of every page. It is not. For an
image chosen from a srcset with an x descriptor, naturalWidth is
density-corrected: a 192 px file selected at 3x correctly reports 64.
Ground truth from disk: 64×41, 128×82, 192×123, in all three formats.
The real defect underneath was smaller and mine: the home page added a 225.5
px call site to a component whose ladder was sized for 50 px and 56 px, so it
upscaled 3.52× at DPR 3. InfinityMark now takes width and loading; the
approach mark passes width={232} (→ 232/464/696, covering 676 device px) and
loading="lazy", since it sits a screen and a half down. All three instances
now measure ≤1.0× upscale at DPR 1, 2 and 3.
That is the fifth time on this project that my own instrument misread, and
the pattern across all five is the same: a number that looks like a finding, from
a probe nobody checked. naturalWidth joins timeout, the coordinate-hull
bbox, the worst-deviating-instance filter, and the document-level overflow check.
CLAUDE.md gains the general rule.
What the code review found — and finding 1 is the same defect for the FOURTH time
adversarial-reviewer returned fourteen findings, three blocking. It also
noted that the working tree moved under it mid-review, because the claims fixes
above were being applied while it ran, and it re-verified each finding against
the current tree so it could say which were still live. That is the right
behaviour and it is worth recording as the reason the two reviewers should not
be run against a moving target next time — brief them, then stop editing.
1. BLOCKING. class="section-head" on <SectionHeading> never matched, and
three section headings collided with their content.
This is the defect CLAUDE.md already records twice under "A parent cannot
style a child component's root element", which then recurred a third time with
<Picture> inside InfinityMark — and I wrote a fresh warning about it into
Eyebrow and Pill in this very diff and then did it again in the page.
The emitted evidence, side by side:
dist/_astro/index.*.css .section-head[data-astro-cid-lcdefpme]{margin-block-end:var(--space-7)}
dist/index.html <div class="heading-block section-head" data-astro-cid-ypavld2q>
Measured before the fix: margin-block-end: 0px and a 0 px gap to the next
block on all three call sites — 48 px of intended separation gone. Not merely
tight: .display sets line-height: 0.98, so the glyphs overflow the line box
and "Two processes." sat over the top edge of the Mediation and Arbitration
cards. astro check reported 0 errors and eslint was clean, which is
exactly what CLAUDE.md says this failure looks like: no error, no warning, and
the CSS reads correctly in the source.
I had looked at a full-page screenshot of this section and passed over it. The gap looked plausible because a serif's descender space reads as air. A screenshot is not a substitute for a computed-style assertion, and the reverse is also true — three of my own findings today came only from looking.
Fixed by wrapping each in a page-owned <div class="section-head">. Measured
after: 48 px margin, 48 px gap, all three. And the prop is gone —
SectionHeading no longer accepts class, so passing one is now a build
error rather than a silent no-op. Same deletion applied to CredentialRow,
Pill, ProcessStep, PracticeCard and Eyebrow, all of which declared a
class prop with no call site: five more invitations to the same mistake.
2. BLOCKING, already fixed in flight — the mark was upscaled 3.5× on the home
page. Covered above. The reviewer added the follow-up that mattered: the fix
invalidated a figure cited as load-bearing reasoning in three places.
docs/02 §Components and §9 Q38 both argued "257,278 ÷ 3,063 = 84×" against
the committed SVG, and none was updated when a second, larger ladder appeared —
the home page's mark takes 14,555 B at DPR 2, so the ratio there is ~11×.
That is the SES-DKIM duplication in a new costume: the copy nobody re-reads goes
stale. All three now carry both ladders and the instruction to quote the size
with the call site, never on its own.
3. BLOCKING, partly fixed in flight — 234 px of horizontal content loss at a
large default font size. The three minmax(Nrem, 1fr) grids were mine to fix
and were fixed; the reviewer then re-measured and found 83 px still going,
from two elements this diff introduced:
.featuremeasured 411 px in a 342 px container —padding: var(--space-7)is 192 px a side at root 32 px, and.feature-title's min-content did not shrink becauseoverflow-wrap: break-wordpermits a break at layout time but does not reduce min-content size. Now a clamped padding plusoverflow-wrap: anywhere..contact-action { flex: none }is0 0 auto, so the button could not shrink below its 425 px max-content. Now0 1 autowithmin-inline-size: 0.
4. The credential row was never "two up on a phone", and its comment said it
was. repeat(auto-fit, minmax(11rem, 1fr)) resolved to a single 342 px
track at 390 px with all four items stacked and the band ~430 px tall — with
Q.Arb / Commenced August 2026, which §4's paired-disclosure condition puts on
this page, at the bottom of it. The arithmetic is not subtle: two 176 px tracks
plus a 24 px gap need 376 px and the container is 342. A measured-sounding
comment that was false, which is this project's own named failure mode, written
by me on the same day I added the rule about it to CLAUDE.md.
Now explicit: repeat(2, minmax(0, 1fr)), becoming four at 56 rem where four
genuinely fit. Measured after: 2 rows at 320 / 390 / 640 / 768 (band height
296 / 280 / 272 / 260 px, down from ~430), 1 row from 896 px.
5. A §9 row cited a grep "in the Change Log" before the entry existed. Q39
said so in the present tense while the newest heading was still (v) — because
/build writes the entry at Phase 6, after review. Fair catch, and the reviewer
independently re-ran the sweep and confirmed it was genuinely complete. Q39 now
names the entry.
7. PROCESS was hardcoded in the page, against the reason written in
site.ts for centralising the practice blurbs. "/ and /practice/ both
render them and two copies of a claim-bearing sentence is one copy that will
eventually be wrong." PROCESS is the same shape of content, /process/
renders the same five steps at step 6, and the staged step-3 body carried the
fee claim the claims audit had just found to be wrong. It existed in one
place today and would have existed in two. Moved to src/data/site.ts.
8. The section-7 omission was recorded only in a comment in the file that
deviates. docs/01 was edited in this same diff and not amended. Compare
SiteHeader's sticky-threshold deviation, which was written back into docs/02
— the project's own precedent. docs/01 §/ item 7 now records it.
9. Two of FOUR above-the-fold faces were not preloaded, and the comment said
"the two faces used above the fold". Network probe on a cold cache: /
requests four. The serif italic sets <em class="it">the room</em> inside
the <h1>, and a swap there moves the last line of a 96 px headline — now
preloaded. Geist Mono sets the eyebrow, which is the first visible text, and
is deliberately not: preloading it puts 95,688 B of font on the critical path
instead of 72,560 B, for one short line of 12 px reflow. A trade, stated as one,
revisited against Lighthouse at step 7. The reviewer was straight about its own
limit here: throttled to slow 4G + 4× CPU it measured LCP 896 ms, CLS 0.000
and could not reproduce a shift.
10. Dead API surface. Eyebrow's tag prop had zero call sites, so its
<span> branch was unreachable and the file carried two near-identical
templates for it — deleted, keeping the as-naming discovery. PORTRAIT_ALT in
schema.ts was exported and referenced nowhere — deleted. SectionHeading
exposed both a title prop and a heading slot for one job — the prop is gone.
Five class?: string props — gone, per finding 1.
11–14, all consider, all recorded rather than churned. The
duplicate-density srcset and the /about/-404-in-JSON-LD note are now §7
rows so neither is re-discovered as a defect in this repo. The portrait's 1:1
declaration under a 4:5 crop stays, with its reasoning already in the source and
CLS measured at 0. The named-landmark inconsistency is fixed by removing
aria-labelledby from the four content sections that have visible <h2>s and
keeping aria-label only on the credential band, which has no heading — the rule
being name a region only where it has no heading of its own. That takes the
region list from eleven entries to six.
And one of its findings was wrong, which it said itself. The reviewer
reported seeing two portrait fetches at 390 px / DPR 3, flagged it
low-confidence, and named the likely artefact — setDeviceMetricsOverride
applied around navigation. Checked: one fetch at 390/DPR 1, 2 and 3 and at
1280/DPR 2 and 3, cache cleared each time. It was the artefact. A reviewer that
states its own uncertainty is doing the job; the answer was to run the probe, not
to dismiss it.
What it checked and found clean, so the gap is auditable: 26 real Tab
keydowns dispatched, all 26 focused elements matched :focus-visible with a
visible ring, skip link first; 72/72 hit-test points across eight cards
resolved to the card's link; heading outline dumped, 23 headings, no skips; every
computed contrast pair recomputed from scratch including .approach-metaphor at
11.09:1; zero client:* directives and zero non-JSON-LD <script> tags;
41 KB total transfer cold-cache at 1280/DPR 2 with zero JS bytes; zero
data: and zero base64, in the HTML and CSS; no third-party host; and both
minifier traps re-verified in the emitted CSS.
Verification — run, not asserted
Harness: a zero-dependency CDP driver (Node 22+ has a global WebSocket)
against --headless=new Chrome, serving dist/ over python3 -m http.server.
Every figure below came from a command whose output was read.
| Gate | Result |
|---|---|
npx tsc --version |
6.0.3 |
npm run check |
0 errors, 0 warnings, 0 hints (19 files) |
npm run lint |
clean — ESLint + Prettier |
npm run build |
complete, 1 page |
npm audit |
0 vulnerabilities |
| Lighthouse | NOT RUN — tool unavailable. @lhci/cli removed 2026-08-26; R11's re-add trigger is step 7. Stated, not omitted |
Zero JavaScript, in the emitted page. dist/index.html carries 1
<script> and it is type="application/ld+json"; non-JSON-LD scripts 0;
dist/_astro/*.js no matches. With script execution disabled at the protocol
level: 444 DOM nodes, 6,578 characters of visible text, 5,665 px of content —
the identical page.
Phase 5, the minifier check. grep -o 'animation:[^;}]*' dist/_astro/*.css
returns only animation:none!important twice, which is the reduced-motion and
print override. No animation shorthand beside animation-timeline. Both
longhand blocks survived: animation-timeline:scroll() with
animation-name:header-lift, and animation-timeline:view() with
animation-name:reveal-in.
Layout, 14 widths (320 → 1920). Document overflow 0 at every one, with
zero elements past the viewport. And the elements were measured, not only the
page — this is the correction entry (v) owed: every rendered infinity mark
came back at aspect 1.5654–1.5657 against the master's 1.5657, at all 14
widths. Header 189 px ≤414, 141 px 640–1024, 81 px sticky from
1056, matching --header-h.
Accessibility. One <h1>; heading levels never skipped (outline dumped and
read). Landmarks 1/2/1/1. Focus order == DOM order across 44 focusables.
Every <img> has alt and explicit width/height; 0 missing either; 0 empty
links. Contrast: 32 distinct foreground/background/size triples computed from
what the browser actually painted, at 390 / 768 / 1280 px — 0 failures. Print
media: 0 revealed elements at opacity < 1 (10 targets). Reduced motion: 0 hidden.
Touch targets, and the instrument mattered here. The eight cards report
26–39 px-tall <a> boxes and are not a finding: hit-testing nine points per
card at three widths put 9/9 inside the link on all 24 cards — the whole card
is the target via ::after { inset: 0 }. Two standalone paragraph links were
real at 18 px tall and are fixed. One inline link (164 × 21) is deliberately left
under WCAG 2.5.8's sentence exception.
Reflow, and one case that is not clean. Page zoom passes: 1280 at 200% is the
640 column and at 400% is the 320 column, both 0 overflow, so WCAG 1.4.4 and
1.4.10 are covered. With the reader's default font size at 200% (root 32 px —
not page zoom) / started at 234 px of overflow at 390 and came down in
three measured steps to 3 px at 390 and 65 px at 320: the three
minmax(Nrem, 1fr) grids guarded with min(), then .credentials made
explicit, then .feature's padding clamped with overflow-wrap: anywhere on its
title and .contact-action allowed to shrink. overflow-wrap: break-word
permits a break at layout time but does not reduce min-content size —
anywhere does, and that was the whole of one fix. The residual is the header's
deliberate flex-wrap: nowrap above 66 rem (602 px at 1280) plus
white-space: nowrap on the brand name and the headline's 104 px floor; undoing
either re-opens the measured step-1 header decision, so they stand. Beyond what
docs/02's floor requires, since page zoom is clean — a robustness margin, not a
failure. The table is in docs/02.
Payload. HTML 27,334 B · CSS 26,572 B (linked, over the 4 kB inline
threshold) · hero portrait AVIF 5.6 / 7.3 / 11.1 / 14.8 / 21.5 kB across five
widths, <img src> fallback now the 960 variant at 78,665 B · mark AVIF
1,720 / 3,063 / 4,843 B at 64 px and 6,017 / 14,555 / 22,639 B at 232 px
· fonts ~50 kB preloaded. JS 0 B against a 100 kB budget.
Not verified, and named as such. Lighthouse (above). The HTML validator was
not run, and the page carries a known conformance error: 4 of 12 srcset
attributes declare two candidates at 1x, because Astro's densities emits
url, url 1x, url 2x, url 3x and a descriptor-less candidate is implicitly 1x.
Harmless in browsers; step-1 review finding #8, still open. Do not claim
"validator clean". (And my first check of it reported "ok" — it compared
undefined against the string '1x'.)
Sweeps — the commands, not the claims (R8)
$ grep -rn "anyone may be appointed\|Anyone may be\|No designation is required" \
--include='*.md' --include='*.astro' --include='*.ts' .
-> 5 hits: 2 correct (my new text quoting the struck line; entry (v) history),
3 FIXED (§4 boundary bullet, §9 Q33 row, SiteHeader comment)
$ grep -rn "Med-arb\|med-Arb" --include='*.astro' --include='*.ts' --include='*.md' .
-> 1 hit, FIXED (index.astro:313 -> Med-Arb, the form §4 and docs/01 use)
$ grep -rn "Law and engineering" --include='*.md' --include='*.ts' --include='*.astro' .
-> label FIXED in site.ts + docs/03; the PROSE instance on / is reworded and
the two spec instances (docs/01:136, docs/03:150) are Q41(a) for Pouya
$ grep -rn "type-scale\|proof sheet" ... | grep -v dist
-> 5 live references to the deleted page, all FIXED (§7 Client JS row,
robots.txt, docs/05, brand-assets.md, InfinityMark.astro). Change Log
entries (u) and the struck Q33-orig row keep theirs — append-only history
$ grep -cEi 'OCNI|Law Society|LSO|testimonial|Since 20|London|New York' dist/index.html
-> 1, and it is a FALSE POSITIVE: "I aLSO practise". Case-sensitive: 0
$ grep -cEi 'leading|premier|top-rated|best|proven|guarantee|extensive' dist/index.html
-> 2, both FALSE POSITIVES: "the pLEADINGs" twice. No superlative on the page
$ grep -cE 'LegalService|worksFor|priceRange|aggregateRating' dist/index.html
-> 0
Superseded
Entry (v)'s "⏸ RESUME HERE" section is spent. All five items it listed as
awaiting a ruling — Q35, Q39, Q37, Q38/R13, R1 — were ruled on 2026-08-27 and are
handled above. Its six non-blocking review findings: #7 fixed (--text-2xs,
--space-05, --focus-offset added), #8 still open and now measured (4 of 12
srcsets; see Verification), #9 unchanged (--header-h has 0.81 px of
headroom), #10 fixed (the masthead tagline is suppressed on /), #11
unchanged (~3.16 MB of brand binaries), #12 fixed, and by deletion rather than by
use — InfinityMark's class prop is removed. It had no call site and
class:list put it on the <img>, so a parent writing
<InfinityMark class="foo" /> would have got a rule compiled against the
parent's cid that never matches: the exact defect CLAUDE.md records twice on
this project, offered as an API. label stays with its reason written down (all
three current call sites are decorative; the next one may not be), and width
and loading are new and used. Entry (v) is not edited; this is where the
state lives now.
2026-08-26 (v) — The mark was wrong; §4 gains an Offerings category; and two of my own tools lied to me
Who: Pouya ruled on the mark (Q32), on arbitration (Q33, Q36), and on the
asset convention. Claude Code implemented; adversarial-reviewer and
claims-auditor ran on the result. The claims audit returned FAIL with 13
findings, and the sharpest of them were against text written an hour earlier in
this same entry's work. Most of what follows is those findings.
Q32 — the infinity mark was a wrong shape, and it had shipped
Pouya rendered the committed path against the master and rejected it. Two of his three grounds reproduce from the path alone and are now verified here:
- TANGENT, NOT CROSSING. All four cubic branches meet the origin at exactly
90°, so the loops are mutually tangent on a vertical line rather than crossing.
At stroke-width 28 that renders as two kissing circles — the one thing an
infinity mark must not be
[verified 2026-08-26 — tangent vector computed per segment]. This alone disqualifies the shape. - WRONG PROPORTION. The master's ink bounding box is 2668 × 1704 =
1.5657:1
[verified 2026-08-26 — measured]. - FLAT. Two uniform strokes standing in for a shaded ribbon: a band of variable width twisting in three dimensions, maroon into champagne, passing over itself at the crossing.
The 1.23:1 figure is reconciled, and it was a real measurement. It is the bounding box of the path's coordinates, not of the curve: control points sit at y = ±160 where the curve reaches only ±120, so the coordinate hull is 400 × 320, and with stroke-width 28 on all four sides 428 × 348 = 1.2299. Pouya's number exactly. It is a trap rather than a slip — x is monotone on every segment, so the control points give the right width and a 33% inflated height, and the usual "does the width look right?" check passes.
Two consequences worth keeping. The direction was inverted: measured from the
curve, the traced path is 6.4% wider than the master, not squatter. And the
declared viewBox 440:280 = 1.5714 sits within 0.35% of 1.566 — so step 3 of
the ruling, re-tune the layout to the true aspect, was 0.18px of work at the
shipped 2rem, and re-tuning to 1.23 would have broken it: for any
given width, 1.2299 allocates 1.5657/1.2299 = 27% more height than the mark
actually occupies, so the header would have been rebuilt around a box a quarter
too tall. Where 1.566 does matter is step 2: a tight crop to ink is
exactly the measurement where 1.667 and 1.566 differ visibly, which is why the
render source is cropped to the master's ink box and pinned at 667 / 426 rather
than inheriting 11:7. The ratio was never sufficient grounds on its own.
Tangency was, and it is exact — both lobes are confined to their own
half-plane, so the signed crossing number is 0, and at 2rem the strokes fuse into
a single mass across 61% of the mark's height.
The traced path is deleted, not kept as a fallback, on Pouya's instruction:
a wrong mark that renders is worse than a missing one, because it stops looking
wrong. public/favicon.svg deleted with it; the favicon chain is regenerated
from the real artwork.
Why two review passes could not catch it — and the rule that follows
InfinityMark.astro carried a scrupulous provenance comment stating that the
path was traced from the old site's loading placeholder and that a
placeholder is not necessarily canonical. It was correct, it was prominent, and
it did not help, because the artwork it would have to be checked against was
not in the repository. The doubt was stated and structurally uncheckable.
New rule in CLAUDE.md, and R14: anything a spec makes a claim about must
be reachable from the repository. If the artefact lives only in Drive, in a
console, or on a laptop, the claim is unverifiable by construction, not
merely unverified. Second occurrence — Q24 was AWS-Hosting-Guide.md, the only
record of how the infrastructure was built, living outside the repo.
Two process failures of my own, both in the same shape
- I reported the brand assets unreachable. They never were. Four commands of
the form
timeout 60 ls "$DRIVE"returned empty output; I read that as an empty directory and told Pouya the ruling could not be executed.timeoutis not installed on macOS — the commands had never run. The Drive was fully readable the whole time and the masters were found within a minute of using a tool that exists. - I reported malformed HTML —
<picture="true">andstyle="block-size:2rem"="true"— and rewrote a component around it. It was my own regex, stripping an attribute's name and leaving its value. The markup was always valid. The rewrite was reverted.
Both are the same failure as a sweep is a command, not a claim: an assertion
resting on output that was never produced, or was produced by my own filter.
New rule in CLAUDE.md: a command that did not run is not evidence of
absence — check the tool exists, read exit status, not just stdout.
What the mark is now
src/assets/brand/ holds the master (3000 × 3000, alpha), the tight crop that is
the render source (2668 × 1704, so the file's aspect ratio is the mark's),
the full lockup, and the SVG Pouya added. InfinityMark.astro renders it through
Astro's <Picture>; a browser downloads 3,063 bytes of AVIF on a Retina device.
Correction inside this entry. The SVG was first described here and in three
files as "a raster in a vector wrapper". That was unfair: rasterised at 8333 px
it reproduces the master exactly, at the same 1.566:1 [verified 2026-08-26].
What rules it out is payload and composition — 257,278 bytes against 9,468,
plus seven embedded base64 PNGs that would breach CLAUDE.md's no-base64 rule.
Corrected in the component, docs/02, Q38 and docs/reference/brand-assets.md.
width={320} on <Picture> is load-bearing. Without it Astro emits the
untouched 2668 px master as the <img src> fallback — 1,146,406 bytes — sitting
in dist looking like an optimisation had happened.
Documented, temporary exception to docs/02's "inline SVG, never a PNG",
tracked as Q38 (a vector master that is faithful and light) with R13 so
it cannot become permanent by neglect — Pouya's own warning when he made the
ruling.
Q33 and Q36 — arbitration, and a new §4 category
Both answered: Pouya accepts arbitration appointments now — sole,
party-appointed and co-arbitration. His reasoning: ADR designations are
voluntary credentials, not licences, so the constraint was always
positional, never legal. Mediation · Arbitration · Toronto is restored to
the masthead; docs/04's home <title> stands as specified.
§4 gains an Offerings subsection — a second category beside the credential
register, with the test: competence for an offering, permission for a
credential. Three specs that asserted the converse were swept
(docs/01, docs/03, docs/07-fees.md) — and that sweep had been claimed in
the Q36 closure before it was run, which claims-auditor caught. docs/03's
model sentence was wrong in both halves and in opposite directions, which is
why it had survived two audits.
What the claims audit found in the new section, and how it was resolved
Four structural defects in text written the same hour:
- The masthead justification answered the wrong objection. Q33-orig had objected to placement — a line under his name on every page "where nothing qualifies it" — not to entitlement. The section refuted an entitlement objection nobody raised. Corrected, and the substitution is recorded rather than tidied away.
- §4's own paired-disclosure condition was unmet by the shipped build. The
section says the site "makes the first while stating the second plainly" and
"neither half may be dropped" — yet the masthead shipped on every page while
the Q.Arb stage shipped on none, and
CREDENTIALS.inProgressexisted insite.tsrendered nowhere. The footer designation strip now readsQ.Med (ADRIC / ADRIO) · Q.Arb — commenced August 2026on every page. - The offering test handed the gating question to the subject. Its closing sentence, "that is his call, not the register's", attached to the whole preceding question including whether the activity is gated — a clean walk-through for a future writer. Split in two: gating is this register's question and needs a checkable source; competence is his and only his.
- The category had no gate and no defined extent. No rule said an offering
needs a row before it may be published, and
Med-Arbwas sitting in the site-wide footer as an offering three sentences below a paragraph saying not to infer exactly that. Both fixed: the publication rule is stated, scope is defined (an offering is a PROCESS; a subject-matter area is not), and Med-Arb is removed from the footer pending Q35(a).
And the finding that matters most: four unsourced propositions of Ontario
law. "Anyone may be appointed an arbitrator in Ontario. Nothing in law gates
the role behind a designation." [verified — Pouya] means the subject reported
it, which can establish what he offers and cannot establish a proposition of
law. The auditor produced a counter-example from inside the repo: the site
ships a Shareholder & Family Business practice area, and family
arbitration in Ontario is understood to be one of the places where arbitrator
qualification is prescribed. §4 now carries the proposition as the
architect's stated basis, deliberately unstamped, scoped to commercial
arbitration, with the universal removed — and Q39 opened. This record will
not assert Ontario law, and neither will a self-report.
Questions and reminders
Closed: Q32 (the reconstruction was wrong), Q33, Q36.
Narrowed: Q35 — Arbitration resolved; Med-Arb and the two
subject-matter labels remain, now under the scope note that says which rule
governs them.
Opened: Q38 (vector master), Q39 (family arbitration and gating).
§12: R13 (the raster is temporary), R14 (assets must be in-repo).
Verification — run, not asserted
npm run check 0/0/0 · npm run lint clean · npm run build 0 errors ·
npm audit 0 vulnerabilities.
Measured in headless Chrome at 320/375/640/768/1000/1023/1024/1100/1199/1200/1201/1279/1280/1281/1440/1600, with a seventh nav item injected for the step-7 state:
- 0 overflow, 0 focus-order inversions, 0 tap targets under 44 × 44 at every
width; closed
<details>panels contribute 0 hidden tab stops - Header a constant 81px at every sticky width — 32 padding + 48 reserved
brand block + the 1px border I had forgotten, which is why
--header-his measured rather than added up - Restoring the tagline broke the 64rem fit (84px past the content box at 1024 with seven items) and was re-tuned: tagline gated at 75rem, nav gap moved to 80rem so two things do not grow at one breakpoint — 4px of spill at exactly 1200px caught that
- 0
<script>tags, 0.jsfiles; both scroll-driven animations attach live - Print: all four revealed cards present · 760 words with no JavaScript
⏸ RESUME HERE — session ended mid-flight, 2026-08-26
Pouya went mobile. The tree is committed and green; nothing is half-applied. Pick up at the numbered list below.
State: npm run check 0/0/0 · npm run lint clean · npm run build 0
errors · npm audit 0 vulnerabilities. Both adversarial-reviewer blocking
findings on the mark are fixed and re-measured. Build step 1 is complete; step 2
is / (docs/01 §Build order).
Waiting on Pouya — nothing proceeds on these without a ruling:
- Q35 — may the nav and footer name
Med-Arb,Energy, Grid & RegulatoryandShareholder & Family Business? Both halves go to him together;Med-Arbwas removed from the footer for a few hours and restored, because acting on one half unilaterally was wrong and orphaned the page. - Q39 — family arbitration. §4's arbitration rows are scoped to commercial pending an answer. This one has a legal dimension the register cannot resolve from a self-report.
- Q37 — is
JD + ML → "Law and engineering"the right label? Answer before step 2 renders the credential row. - Q38 / R13 — the commissioned vector master. Until it lands the mark ships as a raster under a documented exception.
- R1 — the licensure framing is interim and now carried by shipped pages.
Unresolved review findings, all non-blocking, none shipped-defect:
adversarial-reviewer#7:gap: 2pxandfont-size: 0.6875reminSiteHeaderare magic numbers; add--text-2xs/--space-05or record the deviation.- #8: Astro's
<Picture>+densitiesemits a duplicate1xdescriptor insrcset— an HTML conformance error, harmless in browsers. Do not claim "validator clean" on a page carrying it. - #9:
--header-hhas 0.81 px of headroom at ≥76 rem. A font-metric change pushes the brand past its 48 px reservation with no build error. - #10: the masthead tagline duplicates the home hero eyebrow, so
/will open with the same words twice. Decide at step 2. - #11: ~3.16 MB of brand binaries; the 1.1 MB derived crop is reproducible from the committed master and could go if Astro could crop at build time.
- #12:
InfinityMark'slabelandclassprops have no call site.
One correction owed to the record. Entry (v)'s verification section says "0 overflow at every width". That was true and misleading — the page-level check passed because the brand block absorbed the deficit by crushing the logo. The harness now asserts the rendered aspect ratio of every mark instance, not just document overflow. Measure the elements, not only the page.
Declined and deferred
- The remaining Q35 labels — locked architecture; not an implementer's call.
- A verification workflow was run and half of it was wasted, which is worth recording rather than hiding. It was launched while the artwork was believed unreachable, and briefed accordingly; the premise died four minutes later when the assets were found. Its design strand — three proposals for coping without the artwork — was answered by reality and discarded. Its geometry strand did not depend on that premise at all, and it is what reconciled 1.23:1 above, proved the zero crossing number, and computed the fused-waist figure. The lesson is not "don't delegate" but "scope a brief to what cannot change under it": a premise about the environment goes stale, a question about the contents of a committed file does not.
Technology, AI & Dataand the other three practice labels, raised as offering claims with no rows: answered structurally rather than individually — subject-matter areas are not offerings, so the Offerings table is not the rule that governs them.docs/03's framing rule and Q35 are.docs/reference/brand-assets.mdreproducing R14 — the auditor was right that every measurement derived from a master outside the repo. Fixed, not declined: the uncropped master is now committed, so the crop is re-derivable in-repo, and every figure carries a stamp.
2026-08-26 (u) — Build step 1. Zero JavaScript, and both review passes found defects in the first pass's fixes
Who: Pouya ruled on CTF, Q30, Q31 and the Lighthouse gap, and set the
direction for step 1. Claude Code implemented under /build;
adversarial-reviewer and claims-auditor each ran twice, and the second
pass is where most of this entry comes from.
What Pouya decided
- Canadian Tax Foundation membership added to §4 and to
CREDENTIALS.memberships,[verified 2026-08-26 — Pouya], for now — it renews yearly, so R10 was widened from "OBA section membership" to "annually-renewing memberships" covering both. - Not a seventh practice area at launch. CTF is a credential none of the six
areas touch and tax-adjacent disputes are ADR territory, but there is no track
record to point at. Recorded in
docs/01beside the Indigenous-engagement omission, and R3 widened from one candidate to two — one month 12–18 review, two questions. - Q31 CLOSED — Plausible, decided rather than defaulted. Pouya checked and found his own assumption wrong: Fathom is Canadian-owned but stores non-EU traffic on US servers, isolating in the EU only for EU visitors; Plausible holds everything in the EU. D15 amended from "Plausible or Fathom" to Plausible, with that reasoning and with Fathom recorded as rejected on data residency rather than on quality.
- Q30 CLOSED — SML Company Ltd is incorporated FEDERALLY, under the CBCA.
Two facts were being conflated and one was wrong:
site.tscarried'SML Company Ltd. · Ontario, Canada', which reads as a jurisdiction of incorporation and named the wrong one. Jurisdiction of incorporation is federal; place of business is Toronto, Ontario; they are different facts. Neither is published — the footer reads© <year> SML Company Ltdand stops, on Pouya's direction given twice. No corporation number: none is held and none is needed. §4 carries the caution that "federally incorporated" says nothing about licensure and nothing about where the practice may operate, and must not be read together with the Licence status — NOT ESTABLISHED row into an implication neither makes. - The Lighthouse gap, ratified and closed as a documented-control problem.
Removing
@lhci/cliwas right — seven high-severity advisories for a tool that could not run — but it leftdocs/04,CLAUDE.mdand/buildPhase 5 all requiring a check that had silently become impossible. That is Q22's defect in a different costume. The unavailability is now stated in six places, listed in §7, andadversarial-revieweris told not to raise the absence as a finding. R11 carries the re-add trigger for step 7, including "check for a patched release; do not assume0.15.1is still the ceiling".
What shipped
src/layouts/BaseLayout.astro, src/components/{SEO,SiteHeader,SiteFooter,Button,InfinityMark}.astro,
public/fonts/ (six cuts), public/favicon.{svg,ico}, public/apple-touch-icon.png,
and a temporary proof sheet at /type-scale/ that step 2 deletes.
The fonts were never on disk. global.css declared six @font-face rules
pointing at /fonts/*.woff2 and public/fonts/ did not exist, so every face had
been silently falling back to Georgia and the system sans. The design system had
never once rendered as specified. Six cuts committed, 123,804 bytes (not the
136 K du -sh reports — that is disk blocks, and §7 is read in a performance
context), provenance and SIL OFL licences in docs/reference/fonts-provenance.md.
Every URL carries ?v=1 because the deploy script serves /fonts/* immutable
for a year and no invalidation reaches a browser cache.
The infinity mark was recoverable, and is not a redrawing. The deployed site
serves its own loading-thumbnail SVG labelled smlMark; InfinityMark.astro is
that path verbatim, with the source's translate(60 0) folded into the
coordinates. Stroke widths (28/6) and the 0.7 inner opacity are the source's.
Geometry verified to fit its viewBox with 6 units of margin, so it cannot clip at
any size. Q32 opened — a loading placeholder is not necessarily the canonical
file, and D7 says the mark carries over unchanged.
The proof sheet computes rather than restates. It reads tokens.css at build
time and runs the WCAG luminance formula over it. All eleven ratios reproduce
docs/02's measured table exactly — 16.81, 11.75, 12.29, 8.95, 5.47, 3.11, 2.10
on cream; 16.81, 11.09, 8.00, 3.07 on ink. It cannot drift from the tokens
because it is derived from them.
Insights is gated, not hardcoded. docs/01 keeps the section out of primary
nav until two pieces are live, so SiteHeader reads the collection count at
build time. It does not render today and appears by itself at step 7.
Decisions taken during implementation
- ZERO JavaScript, not "minimal". The reveal was an inline
IntersectionObserverin<head>.docs/05specifiesscript-src 'self'with nounsafe-inline— so the only script on the site was the one thing the site's own CSP would refuse to execute, and a per-build hash drifts from the policy pinning it. Replaced withanimation-timeline: view()behind@supports. 0<script>tags and 0.jsfiles indist/.docs/02§Motion amended: theIntersectionObserverroute is now ruled out, not merely second choice.docs/05gained the converse note — inlinestyle=attributes are now load-bearing, so hashingstyle-srcwould collapse the infinity mark. - Header sticky from 64rem, not
docs/02's "sticky". Measured: the one-row header must hold brand + seven nav items + CTA, which it does at 1024px with 32px clearance and not below. Below 64rem the nav takes its own row and the header is 137px (tablet) / 185px (phone) — more of a small viewport than a sticky header is worth.docs/02amended with the measured numbers. - "Condenses on scroll" is now a rule and a shadow, not a size change. A
position: stickyheader stays in normal flow, so shrinking its padding shortens its layout box at the top of the document and lifts every page below it — a scroll-linked layout shift on every page, against the CLS < 0.05 budget. --section-ycorrected.6vw + 2remreaches its own upper bound only at a 2133px viewport, so the "160px desktop" half ofdocs/02was never delivered (measured 108.8px at 1280, 128px at 1600). Now9vw + 1rem— 160px at 1600px.- The measure is opt-in. A global
p { max-inline-size: 68ch }capped every paragraph in every card and footer, forced components to opt back out, and made.prosea class with no effect. Moved onto.prose. robots.txtdisallows nothing.Disallow+noindexon the same path cancel out: a crawler forbidden to fetch a URL never reads thenoindex. The legal pages are footer-linked from every page, so the pair would likely have produced bare-URL listings with the suppressing directive unread.docs/04amended.
What the reviews found — including in each other's fixes
Round 1 — code (5 blocking, 7 should-fix). All fixed and re-measured:
the animation shorthand silently collapsed by Lightning CSS (dead in every
production build, alive in dev); a seven-item nav that fit at no width; parent
styles that never reach a child component's root in Astro; gold text shipped at
1.76:1; 27 touch targets under 44px; revealed content printing blank; a skip
link landing behind the sticky header; 72px of overflow at 320px masked by
body { overflow-x: hidden }.
Round 1 — claims (FAIL, 8). The masthead tagline Mediation · Arbitration · Toronto — never specified for the header, and unqualified Arbitration beneath
a name reads as a held capability against §4's Q.Arb row. Removed; Q33 opened
for the form the question takes at step 2. A four-stage service timeline and two
unverified assertions about the previous build, both removed from a page that
exists to demonstrate CSS.
Round 2 found defects in round 1's fixes — four of them.
- The minifier bug was written back into its own fix. Having just diagnosed
that
animation:besideanimation-timelinegets folded into an invalid declaration, the replacement reveal used the same shorthand. Caught only because the print test still failed. Both are longhand-only;/buildPhase 5 now grepsdistfor it andCLAUDE.mdcarries the rule. - The claims fixes were silently lost. The patch applying all four aborted on a later assertion before writing the file, having already reported four successes. The service timeline was still in the source and still in the build.
- The colour-alone fix used the banned pairing. Nav state was given a gold underline so colour would not carry meaning alone — gold on cream is 2.10:1, below WCAG 1.4.11's 3:1 for a state indicator, so it carried nothing and the state reverted to colour alone. Now maroon at 12.29:1, 2px, dotted for section and solid for current page.
--header-hwas stamped[measured]before anything was measured. It said 4.75rem. Actual is 77px at every sticky width, six items and seven.- Plus: a retracted "470 KB PNG" figure surviving in a file written the same
day; two "measured" comments giving 34px and 14px for the same measurement;
docs/02amended to a 60rem threshold the code no longer used; flexorderputting focus order out of step with visual order between 640 and 1023px.
Round 2 — claims (FAIL, 10). The most serious was inside the review
apparatus: .claude/agents/claims-auditor.md said "Memberships. ADRIC, ADRIO,
OBA sections only" — contradicting a §4 that had gained CTF that morning.
It would have flagged a verified membership as unverified and would not have
noticed CTF being dropped. That file has now hosted a stale claim twice, so
the list was not updated — it was removed, replaced by an instruction to read
the §4 row at audit time. Also fixed: two specs instructing a public phone onto
pages against a §4 row that records none (Q3, answered four entries ago); a
robots.txt reproduced inline in docs/04 that had already drifted; Q.Arb
described as "in progress" where §4 pins "commenced August 2026"; and headline
option 3 struck as asserting party, counsel and neutral.
ROLE added to site.ts — there was no constant for "Director of Firm
Operations" or "active litigation exposure", the two highest-risk strings on
the site, and both were headed for hand-typing at step 3 and in the Person
JSON-LD. npm run check then caught a temporal-dead-zone error in that fix.
Declined, with reasons
- The nav and footer naming
Arbitration,Med-Arb,EnergyandShareholder— raised on both claims passes. Every one is locked architecture (D5, §6,docs/01), and §4 registers claims about Pouya rather than enumerating service lines. Removing them is not an implementer's call. Opened as Q35 so the judgement is visible rather than missed. - Inlining critical CSS.
docs/04names it, but the bundle is 22.5 KB against a 4 KBinlineStylesheets: 'auto'threshold; inlining it into all nineteen pages costs more than the one round trip it saves and destroys cross-page caching. Revisit at step 11 against real Lighthouse numbers. - Simplifying
PRACTICE_SLUGS/PRACTICE_AREAS. A fair simplicity finding, but it touches the content schema a prior review round hardened, and rewriting it as a rider on step 1 is exactly how defects 1 and 2 above happened. Deferred to its own change before step 7. - The sitemap 404.
<link rel="sitemap">removed as inert (it is not a registered link relation and no crawler consumes it); therobots.txtSitemap:line resolves at step 2. Step 1 is not deployed — no deploy credential exists (Q22) and deploys are manual. - Second-person copy on the proof sheet.
docs/03bars it on counsel-facing pages; an internal proof sheet is not one, and the page is deleted at step 2.
Verification — run, not asserted
npm run check 0/0/0 · npm run lint clean · npm run build 0 errors ·
npm audit 0 vulnerabilities · R11 dependency sweep: 13 of 14 pins at
registry latest, the only drift being the deliberate typescript hold.
Measured in headless Chrome across 320/375/640/768/900/1000/1023/1024/1100/1200/1280/1440/1600, with a seventh nav item injected to test the step-7 state that does not exist yet:
- Header 77px, one row, sticky at every width ≥1024 with six items and seven; 137px static below; 0 overflow at every width including 320px
- 0 tap targets under 44×44 at any width
- 0 focus-order inversions (
checkVisibility-filtered); closed<details>panels contribute 0 hidden tab stops - State indicators 12.29:1;
--section-yreaches exactly 160px at 1600px scroll-padding-topcomputes to 93px = 77 + 16, matching--header-h- 0
<script>tags, 0.jsfiles; both scroll-driven animations attach live (header-lift/scroll(),reveal-in/view()) - Printed to PDF: all four revealed cards present (they were absent before the print override)
- 755 words of body text from
curlwith no JavaScript — the old site returns three
Sweeps — the commands, not the claims (R8)
$ grep -rE 'animation:[^;}]*(scroll\(\)|view\(\))' dist --include='*.css'
(no output)
$ grep -rn "OBA sections only" . --exclude-dir={node_modules,.git,dist,.astro}
.claude/agents/claims-auditor.md:58:enumerate "ADRIC, ADRIO, OBA sections only"; the Canadian Tax Foundation was
(the sole hit is the note recording its removal)
$ grep -c '<script' dist/type-scale/index.html -> 0
$ find dist -name '*.js' | wc -l -> 0
$ grep -c "Mediation · Arbitration · Toronto" dist/type-scale/index.html -> 0
The 470 KB sweep is the one worth recording as a near-miss: after retracting
the figure from docs/02, grep -rn "470 KB" found it still asserted in
src/components/InfinityMark.astro, a file written the same day. Without the
grep this entry would have claimed a completed retraction. Three previous entries
made exactly that mistake.
Questions
Closed: Q30 (federal, CBCA), Q31 (Plausible).
Opened: Q32 (canonical vector for the infinity mark) · Q33 (how
arbitration is described in one-line site-wide copy before Q.Arb) · Q34
(whether §2's measurements of the previous build still hold — a re-fetch found
zero base64, occurrences, but the real application is in nine unfetched
bundles, so this is a question and not a correction; §2 now carries the caveat
inline, and CLAUDE.md, docs/02 and adversarial-reviewer.md all cite it
where they quote the figure) · Q35 (may the nav name a service the register
does not establish) · Q36 (is co-arbitration work available now — asserted in
three specs, in no §4 row) · Q37 (is "Law and engineering" the right label
under JD + ML).
§12: R3 widened to two candidates, R10 widened to all annually-renewing
memberships, R11 given the @lhci/cli re-add trigger. R1 remains live and was
surfaced — nothing in this change describes Pouya as licensed or as a legal
professional, and the header tagline that triggered Q33 is verified absent from
dist.
Commit shape
Split in two on adversarial-reviewer's own recommendation: instructions that
narrow a reviewer's scope should not travel in the same commit as the work that
reviewer is checking. The .claude/ changes go in their own chore: commit so
the narrowing reads as a decision rather than as a line in a feature diff.
2026-08-26 (t) — Astro 5 → 7. The review found a defect in the fix, twice
Who: Pouya approved the upgrade and set the version-pin rule. Claude Code
implemented under /build; adversarial-reviewer and claims-auditor both ran
and both failed the first attempt.
This entry resolves the escalation left open at the end of entry (s). That entry recorded the upgrade as proposed, not done; it is done now.
D1 amended — the major is pinned, not inherited
Old → new: "Astro, static output" → "Astro, static output, v7.x", with the reasoning in the decision itself so the next reader knows the version was chosen.
Pouya's own account of the original pin, recorded at his instruction: ^5.0.0
was recalled, not checked, and was two majors stale on the day it was
written. That is a different failure class from the sweep problem in entries
(l) through (o) — not a change that failed to propagate, but a fact asserted
that one command would have settled.
Verified with npm view, and the staleness was not confined to Astro:
| was pinned | registry latest |
|
|---|---|---|
astro |
^5.0.0 |
7.2.7 |
@astrojs/mdx |
^4.0.0 |
7.0.8 — three majors |
eslint |
^9.0.0 |
10.9.1; npm flags 9.x deprecated |
sharp |
^0.33.0 |
0.35.4 |
@lhci/cli |
^0.14.0 |
0.15.1 |
The rule is now in CLAUDE.md: a version pin is verified against the
registry, never recalled. R11 requires re-checking currency at every phase
boundary in the build order — ten remain.
A caveat the rule needs, learned here: verified does not mean latest.
typescript 7.0.2 exists and is unusable — typescript-eslint peers <6.1.0
and @astrojs/check peers ^5 || ^6. @eslint/js latest is 10.0.1, but on
ESLint 9 it must track 9.x exactly. The check tells you the number; you still
have to know why you chose it. Both holds are now recorded in §7 so the next
agent running R11 does not re-derive them or break the build discovering them.
The rule was then not applied to its own manifest, which the second review
caught: eslint-plugin-astro was left at ^1.3.0 while §7 asserted a hold at
1.7.0, and typescript at ^5.7.0 against an installed 5.9.3. Both re-pinned.
Every one of the twelve pins is now verified against npm view.
The migration
Astro 5 introduced the Content Layer API and src/content.config.ts; Astro 6
removed the legacy src/content/config.ts fallback. Verified by moving the
file back and reproducing LegacyContentConfigError — an earlier draft of this
entry attributed the move to Astro 6, which claims-auditor caught as a version
fact recalled rather than checked, in the change that raised recalling-versions
to a rule.
type: 'content' → loader: glob(...), z from astro/zod. schema: ({ image }) => … still works — verified, not assumed.
An empty build proves nothing, so a throwaway page and article exercised
getCollection, render(entry), entry.id, MDX rendering, the image()
helper resolving to a hashed asset, and sitemap emission at /sitemap-0.xml
with no trailing-slash conflict. Then deleted.
compressHTML — held at true, and the first rationale was wrong
Astro 7 changed the default to 'jsx'. I asserted this would eat spaces in
prose, measured MDX, and found no difference — the premise was unsupported
as written. Measuring the .astro path instead:
'jsx': <em>inline</em><strong>pair</strong> ← space silently deleted
true: <em>inline</em> <strong>pair</strong>
Real hazard, wrong file. adversarial-reviewer reproduced both outputs
independently. The comment now names .astro templates specifically and records
that MDX is unaffected, so nobody inherits the original claim. R12 makes the
deviation revisitable only with a measurement.
What the review caught — seven defects in the fix itself
Both reviewers, independently: two source files cited this entry before it
existed. Current Truth had been rewritten in place with no append-only record,
and astro.config.mjs pointed at it as the authority for a measurement. Rules
1–3 of this file's own constitution, broken in the change that adds a rule about
not recalling things. Writing this entry is the fix.
z.coerce.date() turned a plausible typo into 1970. Reproduced: unquoted
publishDate: 20260801 — valid YAML, the obvious slip for 2026-08-01 — coerces
from epoch milliseconds to 1970-01-01 with no error, and would ship as
datePublished in the article's JSON-LD and sort the piece last in a
reverse-chronological index. The reviewer also tested my justification for the
coercion and found it false: plain z.date() accepts unquoted YAML dates fine.
Replaced with a bounded union — z.date() or an ISO-shaped string — which
accepts both real forms and rejects numbers.
The title bound made the SEO spec unsatisfiable. docs/04-seo-spec.md puts
50–60 characters on the rendered <title>, whose pattern is
"<headline> · Pouya Lajevardi". The suffix is 18 characters, so applying 50–60
to the headline guaranteed a rendered title of 68–78 — over the spec's own
ceiling on every article — while .min(50) hard-failed the build on a good short
headline ("What Med-Arb Actually Is"). The schema now enforces the rendered
length, adds an optional seoTitle override, and reports the computed title in
the failure message. docs/01-architecture.md updated to match.
An article could ship an image with no alt text — image and imageAlt were
independently optional, and the schema is the only gate that exists before the
/insights/ route is written. Now coupled.
Two comments asserted controls that did not exist: "every piece must link to
a practice-area page" (.min(1) checks a declaration, not a link) and "an
article with draft:false and reviewedByPouya:false is a bug" (nothing
enforced it). The second is now true — a refinement fails the build. All four
invariants were then tested, each with a failing and a passing case; each fires.
The schema diverged from two specs — docs/01 and docs/02 both specify
topics[] plural and readingTime; the collection had singular topic and no
reading time. Pre-existing, carried through a wholesale rewrite without notice.
Fixed to the specs.
eslint.config.js imported two undeclared packages, one resolving by
hoisting accident: globals was 14.0.0 at the root against 16.5.0 nested under
the Astro plugin, so which major linting used depended on npm's hoisting.
The second review pass found six more, four of them in the first round's fixes
The loop requires re-review after material fixes, and this is why.
frontmatterDate accepted impossible dates — the fix for the 1970 defect
introduced its own. The regex was unanchored at the end and nothing checked the
parsed result, so 2026-13-45 and 2026-08-01 nonsense both passed, each
producing an Invalid Date bound for datePublished in the article's JSON-LD;
and 2026-02-30 silently rolled over to 2026-03-02 — a wrong date shipped
with no error, which is worse than a failed build. The comment claimed it
accepted "both real forms and nothing else." Now anchored, date-only, parsed as
UTC and round-tripped so the day that comes back must be the day written.
A time component is rejected rather than guessed at: quoted
2026-08-01T10:00:00 parses as local time while the unquoted YAML form parses
as UTC, so the same frontmatter would mean different instants on a laptop and on
a CI runner.
The title rule rejected all five planned launch articles. The arithmetic was
right and the rule was still wrong. Every headline in docs/03-content-spec.md's
launch slate is 50–67 characters; with the · Pouya Lajevardi suffix they
render at 68–85 and 5 of 5 fail, making seoTitle mandatory on every article
— a second title field to hand-keep in sync forever, on the highest-volume
content type. Two specs contradicted each other and the schema enforced one.
Resolved by making articles the documented exception: an article's headline is
its <title>, with no suffix. Under that rule 4 of 5 launch headlines pass and
seoTitle is a genuine exception. docs/04-seo-spec.md — which owns the title
rule and had not been swept — now states it.
PRACTICE_SLUGS and PRACTICE_AREAS could drift silently. Splitting one
derived source into two literals bought back the literal types but lost the
guarantee: deleting an area while leaving its slug declared passed astro check
cleanly, so an article could name a practice area with no page, no nav child and
no chip. My first completeness check was written as a conditional type and
did not fire — verified by deleting a row and getting 0 errors. Replaced with
an AssertNever constraint that does: a missing area now fails with
ts(2344), and a typo'd slug fails with ts(2820) plus a "did you mean".
Zero runtime cost.
Three narrower schema gaps, each reproduced: imageAlt: " " passed, so an
image could ship with a whitespace-only accessible name — the exact thing the
coupling check exists to prevent; duplicate topics and practiceAreas passed
and would render duplicate pills; and seoTitle was bounded and re-checked,
so one mistake produced two errors. Trimmed, de-duplicated, and the field bound
dropped so the refinement is the single check.
scripts/deploy-local.sh claimed to do "EXACTLY" what CI does. It did not:
the workflow guards six values, the script guarded four, and the workflow runs
npm run check before building while the script ran neither check nor npm ci.
A local deploy — currently the only way this site ships, since Q22 and Q23
are open — was skipping the type and template gate. Guards brought to parity,
npm run check added, and the claim rewritten to name the two remaining
differences instead of denying them.
Sweep misses: README.md stated an engines floor of >=22.12.0 after
package.json had moved to >=22.13.0 for ESLint 10 — a duplicated version fact
of exactly the kind §7 exists to prevent, now replaced with a pointer.
package-lock.json's root engines still carried the pre-bump value because the
bump was made without reinstalling. engines.node was described as "the
intersection of every dependency's floor" and was not one — >=22.13.0 admits
Node 23.x, which ESLint 10 excludes; it now reads ^22.13.0 || >=24.
eslint.config.js still called itself an "ESLint 9 flat config", and
.gitea/workflows/deploy.yml still carried the Gitea version as [assumed]
after Q23 verified it as 1.27.2.
Three claims in src/data/site.ts bound for public pages, all pre-existing
and all found by claims-auditor: entity: 'SML Company Ltd. · Ontario, Canada'
asserted a jurisdiction of incorporation §4 does not verify (Q30);
provider: 'plausible' was a guessed value where D15 records the choice as
undecided, in a file whose own header says not to guess one to make the build
pass (Q31); and NO_RETAINER_NOTICE was missing the third element
docs/01-architecture.md requires — that submitting the form does not itself
create a conflict check. The notice is fixed; the other two are nulled with
TODO(pouya) and numbered.
One finding declined. claims-auditor reported that no build order exists in
the repository and that R11's "ten more boundaries" was therefore untraceable. It
does exist — docs/01-architecture.md §Build order, eleven numbered steps. The
audit grepped lowercase "build order" and missed the capitalised heading. R11
now carries the file and section so the next reader does not repeat the search.
Declined, with reasons
Accessibility linting is ON, and I was wrong about it twice.
adversarial-reviewer recommended eslint-plugin-astro@3.1.0 for the
eslint-plugin-jsx-a11y peer. I tested that upgrade, hit ERESOLVE (v3 requires
eslint >=10 and peers jsx-a11y >=6.10.2, whose latest peers eslint ^3..^9),
and concluded it was "not installable" — recording in §7 that
"there is no automated accessibility linting".
Wrong the first time: the already-installed eslint-plugin-astro@1.7.0 ships
36 jsx-a11y/* rules and a flat/jsx-a11y-recommended config. I had tested a
different upgrade path and generalised its failure to the capability. The stale
peer range is a declaration, not a runtime incompatibility.
Wrong the second time: having added a one-line overrides entry to fix that
declaration, I retested v3.1.0 — and it installs cleanly. The ERESOLVE was
never about v3; it was about the missing overrides. So the plugin is now on the
current major, not held two behind, and there is no stale pin left in the
manifest except typescript, whose hold is forced by peer ranges.
Verified by execution at each step — rules fired on a deliberately inaccessible
.astro file: html-has-lang, alt-text, heading-has-content,
click-events-have-key-events, no-static-element-interactions,
no-noninteractive-tabindex, tabindex-no-positive. Those are the checks
docs/02-design-system.md §Accessibility floor names.
One cost, recorded rather than discovered later: v3.1.0 declares
node ^22.22.3 || ^24.16.0 || >=26.3.0, which excludes Node 25.6.0 — so
npm install prints EBADENGINE on Pouya's machine. Dev-time only; nvm use
clears it, and .nvmrc already says 22.
This mattered more than the ERESOLVE did. §7 is the single source of operational truth, and it carried a false capability claim on the eve of the step that writes every landmark, heading and focus state, under a CLAUDE.md rule that accessibility is a build requirement rather than a polish pass.
@lhci/cli advisories carried, not fixed. All ten remaining findings trace
to it alone; 0.15.1 is latest, so there is no clean upgrade, and
npm audit fix --force installs @lhci/cli@0.1.0 — a fourteen-minor downgrade,
which is not a fix. Dev-only, never on the CloudFront origin.
Two Consider items taken rather than deferred, both in files already being
edited: image.service was dead configuration (it set Astro's own default) under
a comment describing a convention it did not enforce — deleted, convention stays
in CLAUDE.md; and sitemap({ lastmod: new Date() }) stamped every URL with the
build time, telling crawlers all 17 pages changed whenever one did, which spends
the signal docs/04-seo-spec.md wants. Removed, with step 7 named as the place
to reinstate it per-entry from updatedDate.
Two Consider items declined: the six extraneous @img/@emnapi packages
are cosmetic and npm ci --dry-run is clean; and no .npmrc with
engine-strict was added — the engines floor is a declaration, and making it
fatal is a decision with its own blast radius.
npm audit — the actual result, and it is now zero
after the upgrade: 10 vulnerabilities (2 low, 1 moderate, 7 high)
npm audit --omit=dev: found 0 vulnerabilities
after removing @lhci/cli: found 0 vulnerabilities
Every Astro advisory is cleared — zero findings against astro,
@astrojs/mdx, @astrojs/sitemap or sharp. adversarial-reviewer confirmed
the rationale independently by installing astro@^5.0.0 in a scratch directory
and reproducing 8 high-severity advisories, 6 of them distinct XSS.
npm ls traced all 10 residual findings to @lhci/cli alone. It was first
carried as acceptable dev-only risk; on the second pass that was reconsidered
and it is now removed. The reasoning: 0.15.1 is latest, so there was no
clean upgrade; npm audit fix --force would install @lhci/cli@0.1.0, a
fourteen-minor downgrade; and the tool cannot run at all today — no pages,
no lighthouserc. Carrying seven high-severity advisories for an unusable tool
is not a trade worth making when deferring it to step 7 costs nothing. Recorded
in §7; R11 forces a fresh pin when it returns.
A discrepancy with entry (s), stated rather than quietly dropped. Entry (s)
recorded 16 vulnerabilities and attributed the high-severity set to Astro ≤
7.0.9. After the upgrade the high count was still 7, all from @lhci/cli — so
(s)'s attribution cannot have been right as written. (s) is append-only and
stands; this is the correction. What is verifiable now is the audit output
above.
Verification
Run, not asserted: npm run check (0 errors, 0 warnings, 0 hints — three
ZodIssueCode is deprecated hints appeared mid-change and were fixed to the
string literal), npm run build, npm run lint (ESLint clean, Prettier clean).
npm audit and npm audit --omit=dev as above. npm install --dry-run for both
candidate lint upgrades. Each of the four schema invariants exercised with a
failing and a passing article. A deliberately broken .astro file linted to
confirm the plugin is live under ESLint 10. node -v → v25.6.0.
Not run: Lighthouse (removed — see above), the no-JavaScript render check (no
pages), link resolution (no pages). Those belong to build step 1
(docs/01-architecture.md §Build order), which entry (s) began and which
continues after this.
Still open: Q23 (runner registration — needs the second administrator), R6.
Standing reminders surfaced at the start of this build: R1 (licensure — the
layout about to be written is what carries it), R9 (the SES alarms still notify
nobody until the ses-alerts subscription is confirmed), R6, R10.
2026-08-26 (s) — Q22/Q23/Q27/Q28 answered; toolchain installed; build step 1 begun
Who: Pouya answered four questions mid-session and corrected one earlier instruction. Claude Code implemented and started build step 1.
Q22 — ANSWERED, and the answer is that it does not exist
aws iam get-user --user-name adr-sml-deploy returns NoSuchEntity. §7 now
carries Deploy credential — NOT PROVISIONED, and it is a to-do rather
than a question: the creation commands are with Pouya. Swept by command; no file
describes the credential as existing.
Pouya has been authenticating as user/pouya — the broadly-permissioned
personal user the Q10 inventory captured. Recorded in §7 as interim, and in §10
as a High risk: fine at an interactive keyboard, never as a CI credential.
scripts/deploy-local.sh refuses to run if sts get-caller-identity returns
that ARN, so the rule is enforced rather than merely written down.
Q23 — correction: the version is verified, and the blocker is a person
Old → new: entry (r) recorded the Gitea version [assumed] on Pouya's
instruction. It is now 1.27.2 at git.larsnolden.com
[verified 2026-08-26 — /api/v1/version], comfortably above the ~1.21 floor for
the vars context. The first-step guard is therefore belt-and-braces rather than
load-bearing — which is the right outcome: it was built so the answer would not
matter, and now it does not.
What remains is not a fact to look up. The instance is jointly
administered, so enabling Actions in app.ini and registering an act_runner
both depend on a second administrator. Q23 is rewritten to say so.
So the local path is documented, not treated as a workaround.
npm run deploy → scripts/deploy-local.sh (new) performs exactly what the
workflow performs: same guard, same three sync passes in the same order with the
same cache headers, same invalidation. At this scale the pipeline changes only
how a deploy is triggered, not what it does. docs/06 leads with it, and
both files say the script and the workflow are one artefact in two places.
§10 — a new risk that changes what the IAM policy is for
The deploy secret will live in a repository on jointly-administered infrastructure, where an instance admin can generally reach repo secrets or register a runner that receives them. That is inherent to how Actions runners are fed credentials, not a flaw in this setup, and it does not change the plan.
It changes the reading of the scoped policy. Four actions, one bucket, one distribution is no longer hygiene — it is the actual control standing between a shared Gitea instance and an AWS account holding another business's client-database backups. §10 now says so, and says that any request to widen it is a security decision rather than a convenience one.
Q27 and Q28 — answered, and both are facts with obligations attached
Q27 — two business days. In src/data/site.ts as responseTime, with a
derived responseTimeShort so the confirmation email cannot drift from the page.
Added to §4 Verified as a public commitment that must read identically on
/contact/, in the inquirer email, and in any bio. docs/05 says to render it
from SITE, never retype it. Swept for the superseded "one business day": no
occurrences.
Q28 — OBA Construction & Infrastructure, ADR, and Civil Litigation
[verified 2026-08-26 — Pouya], for now. §4's row and site.ts both carry
the "for now". Section membership renews yearly, so this is a fact with a shelf
life — R10 added, with OCNI as the precedent: §4 already records it as
lapsed and not publishable, which is exactly this failure arriving a year late.
Build step 1 — started
Dependencies installed; package-lock.json exists. That alone closes a
blocker documented since entry (m): npm ci had no lockfile, so the CI pipeline
could not get past its first step.
npm run check and npm run build both pass — 0 errors, 0 warnings. Worth
recording because entry (m) F6 asserted both would fail on an empty
src/pages/. adversarial-reviewer doubted that and was right: astro build
emits zero pages and exits 0. Correcting it here rather than editing (m).
ESLint and Prettier are now wired, closing the "not yet wired" rows:
eslint.config.js— flat config,js.configs.recommended+typescript-eslint+eslint-plugin-astro.typescript-eslintadded as a devDependency because.astrofrontmatter is TypeScript and the plugin cannot parse a component without it. Deliberately not type-aware:astro checkalready type-checks, and running both would be slower and would disagree at the edges..prettierrc.json— 80 columns, single quotes,prettier-plugin-astro..prettierignoreexcludes*.mdandsrc/styles/tokens.css, deliberately. Prettier's reformat ofAGENTS.mdalone is an 892-line diff with no reading benefit, and it collapses the aligned comment column intokens.cssthat lets the measured contrast ratios be scanned down the page — the one thing that file exists for. Both exclusions carry their reason in the file.
npm run lint now passes clean. npm run lighthouse remains unwired, and now
honestly so: there are no pages to measure.
npm audit reports 16 vulnerabilities (7 high) — not fixed, flagged. The
high-severity set is Astro ≤ 7.0.9 (XSS via define:vars, spread attribute
names, transition:* values, slot names; SSRF in a prerendered error page) plus
transitive esbuild/sharp, and a cookie advisory reached only through
@lhci/cli. npm audit fix --force wants Astro 7.2.7, a major upgrade, and
@lhci/cli@0.1.0, a catastrophic downgrade. Most of the Astro advisories need
authoring patterns this project does not use (no server islands, no
define:vars, no view transitions, static output, no spread props yet). This
needs a deliberate decision, not an automated fix — see the escalation below.
Escalated
Astro major upgrade. The project pins astro@^5.0.0; the advisories are
fixed in 7.x. Doing it now, before any pages exist, is far cheaper than doing it
after — there is nothing to migrate. Doing it later means shipping on a
known-vulnerable major. This is a D-series decision (D1 names Astro but not a
version), so it is yours.
Verification
npm run check — 0 errors, 0 warnings, 5 files. npm run build — completes,
0 pages, as expected. npm run lint — ESLint clean, Prettier clean.
bash -n scripts/deploy-local.sh — syntax OK. node --check astro.config.mjs —
OK. package.json re-parsed as JSON after editing. Sweeps run as commands with
output read: adr-sml-deploy (no file claims it exists), one business day (no
occurrences), operational identifiers in docs/*.md (no matches).
One error of mine, caught by my own sweep and recorded rather than quietly
fixed: the three new §7 rows initially landed in the §3 decisions table,
overwriting D15's label, because the anchor I matched on (| Analytics |)
appears in both sections. The adr-sml-deploy sweep surfaced it. §3 is restored
— git diff over the decisions table is empty — and the rows are in §7. An
anchor that is not unique is not an anchor.
Closed: Q22, Q27, Q28, and the version half of Q23. Still open: Q23 (runner registration — needs the second administrator), R6 (parked). Added: R9 (SES subscription pending), R10 (OBA renewal).
2026-08-26 (r) — Q19 closed; SES monitoring recorded; §7 made the single source of operational truth
Who: Pouya closed Q19, supplied the monitoring configuration, ruled on Q29, and made the structural call below. Claude Code implemented.
Q19 — CLOSED. Nothing now blocks /contact/
Production access granted in ca-central-1, confirmed by AWS in writing,
effective immediately. §7's SES account row and §9's Q19 both record it; §10's
High risk row for the sandbox is struck as resolved.
The per-region trap is kept in §7 as a standing caution rather than an open task — it was avoided, and it applies again to any future SES request. Deleting the row would delete the lesson.
Monitoring exists, and one part of it does not work yet
Recorded in §7 (the facts) and docs/05-backend-spec.md (why it matters):
- SNS topic
ses-alerts,ca-central-1. The email subscription toinfo@smlcompany.cais PENDING CONFIRMATION, stamped that way deliberately. An unconfirmed SNS subscription drops every message, so as things stand the alarms below fire into nothing. That is now a cutover checklist item in its own right, checked withsns list-subscriptions-by-topicrather than assumed. SES-BounceRate-High(≥0.03) andSES-ComplaintRate-High(≥0.001), bothca-central-1,treat-missing-data: notBreaching.- Bounce and complaint handling is SES email feedback forwarding — the default — not an SNS feedback topic, deliberately. Under 100 messages a month there is nothing to consume a programmatic feed, and an unused topic is one more thing to keep correct. Revisit when code needs to act on a bounce: suppression lists, retry logic, marking a record undeliverable.
docs/05 records why these are a real control rather than a formality: SES
suspends sending above roughly a 5% bounce rate, and at this volume five bounces
crosses it. An intake form is exactly where mistyped addresses arrive. The
alarms sit well below that line so there is room to react.
The structural fix — §7 is the single source of truth for operational facts
Old → new. Specs in docs/ carried their own copies of resource IDs,
regions, DNS records, and service state. They now cite §7 instead.
The reasoning is entry (q)'s DKIM inversion, generalised: the same operational
fact lived in §7 and in docs/05, a correction reached one of them, and the
stale copy ended up instructing an operator to delete the three records that
authenticate outbound mail — under the heading "Never delete". Same class as the
D3 amendment surviving three sweeps. A duplicated fact is a fact that will
eventually be wrong in one place, and the copy that goes stale is the one nobody
re-reads.
The rule is now in CLAUDE.md under Conventions, with that incident as its
stated reason so it is not softened later by someone who does not know the cost.
Swept. docs/05 no longer restates the DKIM token sets, the DNS records, the
MX, the region, or the table name — it cites §7 and keeps only the two facts it
depends on, stated as dependencies. docs/06 no longer restates the region,
bucket, distribution ID, intake endpoint, or account ID; its variable table now
points at §7 rows, and the IAM substitutions say explicitly that they are not
repeated and why. Verified by command:
grep -rn "ca-central-1\|adr-smlcompany-site\|E1OK7G98KNKUTA\|4tl0m5igkj\|327082975128\|adr-intake-submissions\|d26v23dhgsp2ta\|jkddzztszm" docs/*.md
Output read: no matches. Every operational identifier in docs/ is now a
reference.
Q29 — the guard is widened on both counts
Both gaps adversarial-reviewer found are closed:
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYare guarded. By Q22 nobody has confirmed the IAM user or its key exists, so an unset key is the single likeliest first-run failure — excluding it defeated the guard's stated purpose. Only emptiness is tested and no value is ever echoed, so nothing can leak into a run log.INTAKE_ENDPOINTpromoted to job-levelenv:and guarded. An empty one does not fail a build; it ships a live contact form posting to nothing. A silent production defect is worse than a failed build.
The build step still reads ${{ vars.INTAKE_ENDPOINT }} rather than the env
context — depending on Gitea's expression-context support is the exact thing the
guard exists not to do. Coverage checked against every vars.* and secrets.*
the workflow consumes: all guarded except BOOKING_URL, which is empty by
decision (R6).
Also done
docs/06: the account ID is out of the backup-bucket callout, pointing at
§10 instead — the same removal entry (m) made in README.md. Operational
usefulness does not outweigh pairing a live account number with a statement about
what the account holds, in a file that gets pasted around. The billing check now
derives the account from sts get-caller-identity rather than hard-coding it.
astro.config.mjs: prefetch removed entirely — recorded here as a decision,
not a silent deletion. Any prefetch setting ships Astro's prefetch script to
every page, against CLAUDE.md's "default to zero JS", for a marginal gain on a
small static site already served from CloudFront. Revisit only against real
Lighthouse numbers, once pages exist to measure. Entry (q) had set it to
hover; that was still JS on every page.
Gitea version stays [assumed] at Pouya's direction — he is running the
check. The guard is designed not to need the answer.
Verification
The widened guard's run: block was executed under sh -e across four input
states — all set, all empty, secret empty, intake empty. Exit 0 only when
everything is present; exit 1 naming exactly the missing items otherwise; no
value printed in any case. node --check astro.config.mjs passes. The
duplication sweep was run as the grep above and its output read — no matches.
npm run lint, check and build still cannot run: node_modules is
absent and npm ci needs a lockfile. Installing dependencies is the first
concrete task of build step 1.
Closed: Q19 (and with it the last blocker on /contact/). Still open:
Q22 (deploy credential), Q23 (runner registration), Q27, Q28, R6 (parked).
Q29 closed by implementation.
2026-08-26 (q) — Guard built; and the audit found an inverted DKIM table that would have broken production mail
Who: Pouya ruled on entry (o)'s escalations, ratified R7, promoted R8, and
reversed his own F5. Claude Code implemented, ran claims-auditor and
adversarial-reviewer in parallel, and resolved 31 + 17 findings.
The six rulings, implemented
- Q23 hardened, not checked.
.gitea/workflows/deploy.ymlnow runs a guard assteps[0]— before checkout, beforenpm ci, before any AWS call — that fails the run naming any ofAWS_REGION,S3_BUCKET,CLOUDFRONT_DISTRIBUTION_IDthat is empty. It tests the env names (AWS_DEFAULT_REGION) and reports the Gitea variable names, which is the mapping most likely to be got wrong.adversarial-reviewerexecuted the extracted block underbash -e,sh -e, andbash -euo pipefailacross all-unset / all-set / one-empty / whitespace-only and confirmed it fails closed and is POSIX-clean. - R7(3) dropped — the
aws s3 lspre-flight is out; the guard supersedes it. - R7(1) ratified — the cache table stays matched to the pipeline.
- R7(2) reasoning corrected. Old → new: "the intended cover is an S3
lifecycle rule" → no lifecycle rule exists and is not the cover; the cover
is that
aws s3 synconly goes multipart above 8 MB. Corrected once more after audit: the figure now measures what is uploaded, withsrc/assets/pouya-lajevardi.jpgat 357,627 bytes[verified — stat]as the largest source asset and an instruction to re-measure./distafter the first build. The old wording measured the repository, which is not what syncs. - R8 promoted to a rule in
CLAUDE.md,/buildPhase 6,/wrapstep 3. - R1 put in the conditional — there is no site;
src/pages/is empty.
The finding that mattered most was nowhere near the diff
docs/05-backend-spec.md had the two SES DKIM sets exactly inverted. It
labelled 3zsn… / jejgp… / xpiwy… "Live. Matches SES exactly. Never
delete", and f5pu… / jdue… / kznn… "Orphans from an earlier verification.
Inert." §7 records the opposite, [verified 2026-08-26 — DNS]: the f5pu set is
the one that resolves and backs the healthy ca-central-1 identity; the
3zsn set is NXDOMAIN and belongs to a stray us-east-1 identity.
Entry (j) corrected this. The correction reached §7 and never reached
docs/05. An operator tidying DNS from that table deletes the three CNAMEs
that make SES DKIM work — and since SES has no custom MAIL FROM, DKIM is the
only thing satisfying DMARC. Outcome: intake mail from ca-central-1 starts
failing authentication, silently, and the table said "Never delete" over the
wrong three records. Both rows rewritten from §7. The "six CNAMEs" claim below
them now says three, which is how many answer.
Current Truth was stale in three more places
§7 SPF and DMARC still read "ABSENT" [verified 2026-08-26], and §10 still
carried a live High risk row for it — while Q20, in the same file, records
both records added and independently verified the same day. Rule 2 requires
Current Truth updated in place; it was not. Both §7 rows now record the live
values; the §10 risk row is struck as RESOLVED, with the residual
p=none → quarantine tightening described as monitoring rather than risk.
docs/05 opened with "it is not in place" and a table headed "Add both of
these" for records that already exist — rewritten, with the superseded state kept
visible as a caution.
§9 carried Q24, Q25 and Q26 twice each, struck and open, with opposite verdicts. The live Q25 still asserted §4's boutique-naming permission stands — against D16 — after entry (p) had struck it. This is the duplicate-question defect entry (o) fixed for Q22, reintroduced three times in one session: entry (p) added struck rows rather than striking the existing ones. The three superseded open rows are deleted.
The guide added in entry (p) was a live procedure for the architecture being replaced
docs/reference/AWS-Hosting-Guide.md was added untracked — so Q24's answer
("copied into docs/reference/") was true of one working tree and false of the
repository — with no reference-only marker, in fully executable imperative voice.
Following it creates an IAM user with AdministratorAccess in account
327082975128, rebuilds the site through the standalone-HTML pipeline D1 and D3
replace, and wires SES to adr@ / intake@smlcompany.ca against D18's
info@. It is now git added and carries a do-not-execute banner tabulating
seven contradictions with Current Truth, each resolved in Current Truth's favour.
It also called Pouya a lawyer — "Audience: You (a lawyer, not a
sysadmin)" — and proposed public copy reading "does not create a lawyer-client
relationship", which is what NO_RETAINER_NOTICE in src/data/site.ts was
written to avoid. Both corrected.
The R8 sweep command recorded in entry (o) could not have caught either.
grep -rn -i "licensed\|legal professional" does not match "lawyer". The rule
promoted this session is right; the pattern it was demonstrated with was
incomplete. The corrected sweep is:
grep -rn -iE "\blawyer\b|licensed|legal professional|litigator" \
--include="*.md" --include="*.ts" --include="*.astro" .
Run, output read. Every surviving hit is rule text, Change Log history, or
"lawyer" referring to the reader or a third party (docs/03:20, docs/03:161,
docs/01:291, docs/08:111) — none describes Pouya.
Copy defects one step from shipped pages
- "a working litigator" —
AGENTS.md§1's positioning sentence anddocs/03-content-spec.md:71, the line designated for the hero, the About page and the PDF bio. Not in §4; in Ontario it asserts conducting litigation, which §4 Forbidden bars. §1's own line asserted "each element is verified"; it was not. Both now read "close to live litigation". §1's correction is annotated in place rather than silently rewritten. - "an active litigation practice" —
docs/03:118,docs/01:211,docs/01:246. §4's Verified row was itself corrected from "practice" to "exposure" in entry (l); the copy decks kept the barred word. Fixed to the register's own substitute. LegalServiceJSON-LD —docs/04-seo-spec.mdspecified it for the home page. schema.org definesLegalServiceas a business providing legal advice and representation: it asserts in machine-readable form precisely what D13 bars. Changed toProfessionalService, with the reason recorded so it is not changed back.worksForon thePersonnode had no register-safe value — populating it either names the boutique (D16) or misstates the employer — so it is dropped in favour ofjobTitle.- Tribunal-secretary work —
docs/01:173,180anddocs/03:32instructed offering it;docs/07-fees.md:116records Pouya removing the rate and barring the offer. Three files against one; the one was right. - §4's Forbidden table and
docs/03:52justified the superlative ban with "LSO marketing rules" — the fourth surviving form of the licensure claim, two rows below theNOT ESTABLISHEDrow. Restated on grounds that do not depend on licence status. (The §4 instance is the architect's to apply.) docs/07-fees.mdopened with "pending Pouya's sign-off — Q14" and "Nothing in this document publishes until Pouya confirms the figures", then contradicted itself 85 lines later with "This is the card. Build/fees/from it." An implementer reading top-to-bottom blocks the page. Header rewritten to D14 as confirmed.- Nine stale
Qblockers across five spec files — Q4, Q10, Q11, Q12, Q14, Q20, Q5 — all answered or re-tracked, three of them gating/fees/, whose figures are confirmed. Swept.
Code defects
src/data/site.tspublishedresponseTime: 'Inquiries are answered within one business day.'— a service level a reader can hold him to, not in §4. Setnullwith aTODO(pouya); Q27.- The memberships block carried a blanket
[verified]stamp over the OBA section names, which §4 marks[assumed]. Downgraded, flagged; Q28. src/content/config.ts—/** Drafts are excluded from the build, the index, and the sitemap. */describes three controls, none implemented; the sitemap filter covers/legal/only. Same defect class as Q22. Restated as intent with a deadline. Itstitle/descriptionbounds also contradicteddocs/04(max 70 / min 70 against 50-60 / 140-160); tightened to the spec.astro.config.mjssetprefetch: { prefetchAll: true, defaultStrategy: 'viewport' }, injecting Astro's prefetch script and an IntersectionObserver into every page and speculatively fetching every link that scrolls into view — against CLAUDE.md's "default to zero JS" and §7's "most pages ship zero JS", with no Change Log entry. Set toprefetchAll: false/defaultStrategy: 'hover', the setting the written conventions imply. If prefetch-all is wanted, it needs a decision entry.src/styles/tokens.cssattached the ink contrast figure (11.09:1) to both ink and maroon; on maroon--gold-lmeasures 8.11:1. Both pass AA, so no accessibility consequence — but the token file is what developers read.
Also corrected
docs/06 cited a billing alarm verified by cloudwatch describe-alarms; the
guide set up an AWS Budget, which that command never returns — and nothing
records either as created. Now names budgets describe-budgets and says confirm,
do not assume. The booking cutover check is marked conditional on R6, since
BOOKING_URL is empty by decision. scripts/aws-discover.sh and docs/06
claimed "every call is a list or describe" — four are not (sts get-caller-identity, apigatewayv2 get-apis, s3api get-bucket-versioning,
configure get); all are genuinely read-only, so the substance held and the
wording did not. Four stale AWS-Hosting-Guide.md paths repointed at
docs/reference/. The reference workflow still said "Two passes" above three
sync steps — the one file entry (o)'s three-pass sweep missed, and a
twelve-character grep would have caught it. The IAM procedure had 22 lines of
multipart prose between "attach this policy" and "create an access key", which
also broke the ordered list in CommonMark; moved below step 3.
Declined, with reasons
The guard was not widened. adversarial-reviewer showed it does not cover
the secrets — and by Q22, an unset key is the most likely first-run failure, so
the stated benefit ("one second instead of a full build") does not apply to the
likeliest case — nor vars.INTAKE_ENDPOINT, whose absence would ship a live
form posting to an empty endpoint. Both are real. Neither was done: the ruling
named three variables, and INTAKE_ENDPOINT additionally needs promoting to
job-level env:. What was fixed instead is the guard's comment, which
claimed it converted the problem "on every Gitea version" into a clean failure —
broader than what it implements. It now states its scope and points at Q29.
The Gitea 1.21 claim is stamped [assumed], not verified. The version could
be confirmed with one command on the instance. It was not run here; the guard is
designed to work without knowing the answer, so the stamp is the honest record.
docs/06 still pairs the account ID with the backup-bucket callout — the
pairing entry (m) removed from README.md as an inference vector. docs/06 is
an operational document where the account ID is load-bearing, unlike the README.
Flagged rather than changed; if you want it out, it moves to aws-inventory.txt,
which is gitignored.
Verification
The guard's run: block was extracted from parsed YAML and executed by
adversarial-reviewer under three shells and four input states — fail-closed
confirmed. Both workflow YAMLs parse. The barred-word sweep was run as the
grep -iE above and its output read. stat gave 357,627 bytes. npm run lint,
check and build were not run: node_modules is absent and npm ci
cannot install without a lockfile, so the astro.config.mjs, site.ts and
config.ts edits are unverified by any type-checker — they are small and
syntactically simple, and that is the whole of the assurance.
Opened: Q27, Q28, Q29. Still open: Q22, Q19 (blocking), Q23 (runner half), Q5/R6 (parked). Closed by entry (p), confirmed here: Q24, Q25, Q26.
Lesson. Entry (o) said the sweep must be a command whose output is read.
This entry adds what that costs when the command is wrong: the pattern
licensed|legal professional cannot match "lawyer", so a compliant sweep still
missed a file added in the same session. And the largest finding of the session
was in a file nothing had asked either reviewer to look at. Both reviewers were
pointed at a diff; the destructive defect was three files away, left by a
correction that reached one file and stopped. Scope the audit to the decision
that changed, not the lines that changed.
2026-08-26 (p) — Q24, Q25, Q26 answered; R7 ratified; R8 promoted to a rule
Who: Pouya relaying Claude Code's session close. Claude (architect) deciding.
Q25 — struck. This was the dangerous one and it was mine. §4's Verified table carried "The boutique may be named on the site", authorising precisely what D16 — never named forbids. Sequence: Pouya said it could be named, then reversed; D16 recorded the reversal; the permission was never removed. A locked decision and the table copy is written from disagreed, and the table was the more permissive of the two. Struck.
The general defect: reversing a decision requires removing the permission it granted, not only recording the new position. Same shape as the D3 amendment that went unswept three times.
Q26 — answered, and the third form of the same claim is gone. §4's opening asserted "Every public claim on this site is subject to Law Society of Ontario marketing rules" — which asserts regulatory status by implication, since those rules bind licensees. Claude Code was right that this is the same half-measure as the "legal professional" reversal, one level further down.
Old → new: the register's rationale no longer rests on licensure at all. It rests on the fabricated-credentials history, which is documented, sufficient, and true regardless of licence status. The paragraph's own history is kept in place as a caution — three progressively weaker forms of one unverified claim, each surviving a sweep.
§4 now carries a Licence status — NOT ESTABLISHED row. Stamped
[unestablished], a new stamp for this case. It records the honest position: this
file does not know, Pouya's instruction was about the site and not a statement
of fact either way, and nothing may assert, deny, or infer it. Claude Code was
also right that the two licensure rows were stamped [verified] as directives,
which is a different thing from a verified fact — this row separates the two.
Q24 — closed. AWS-Hosting-Guide.md copied to docs/reference/. It is the
only record of how the hand-built infrastructure was created, so it belongs in
the repo rather than in a Drive folder the repo cites but cannot reach. Scanned
before copying: no access keys, no secrets, no account ID.
Q23 — answered by hardening rather than by version-checking. The risk is real
— on Gitea below ~1.21, ${{ vars.* }} interpolates empty and the sync target
becomes s3://. But a version check is a fact that goes stale. Ruling: add a
pre-flight guard step to .gitea/workflows/deploy.yml that fails the run if
any required variable is empty, before any AWS call. That converts a silent
destructive misfire into a clean failure on every Gitea version, and it
subsumes R7's third item.
R7 ratified, with one amendment.
- Cache-policy table amended to match the pipeline — accepted. Documenting
what the pipeline does beats documenting an intention.
robots.txtandsitemap*.xmlatmax-age=0, must-revalidateis immaterial at this size. s3:AbortMultipartUploadomitted, lifecycle rule as intended cover — accepted, with the reasoning corrected. The lifecycle rule does not exist, so it is not the cover. The actual cover is thataws s3 synconly goes multipart above 8 MB and the largest asset in the repo is a 357 KB portrait. Record that, and add a note to revisit if any asset ever approaches 8 MB. Do not document a control that does not exist — that is Q22's defect again.aws s3 lspre-flight documented but not implemented — superseded by the guard step above, which covers more.
R8 promoted from a reminder to a rule. "A sweep is a command whose output is
read, not a claim." Entries (l), (m) and (n) each asserted a completed sweep;
instances survived all three — one of them inside claims-auditor.md, the
definition of the agent whose job is to catch it. A reminder is the wrong
instrument for something this reliable. It belongs in /build Phase 6 and
/wrap: any claim that a change was applied across files must cite the command
and be made only after reading its output.
R1's wording corrected: it says "the site currently asserts the JD only".
There is no site — src/pages/ is empty. Restated in the conditional.
Closed: Q23, Q24, Q25, Q26. Still blocking the build: nothing.
Still blocking /contact/: Q19. Still open: Q22, Q5 (parked).
2026-08-26 (o) — Five rulings implemented; both reviewers run; the D3 sweep finally completed
Who: Pouya ruled on entry (m)'s escalations and reversed his own F5 ruling.
Claude Code implemented, ran claims-auditor and adversarial-reviewer in
parallel on the diff, and resolved 27 + 17 findings.
The five rulings, implemented
- §4 and §10 "licensed" — the architect's, done in entry (n).
- Reversal on "a legal professional's" — cut from
README.md:35andCLAUDE.md(twice). The sentences now assert nothing about professional status and state the reason instead. s3:PutObjectAcldropped from the policy indocs/06-deployment.md. §10's "four actions" is now accurate without §10 being touched, and every file that states a count agrees with the policy:README.md:77,docs/06:82,.gitea/workflows/deploy.yml:13,AGENTS.md§10..github/workflows/deploy.yml→docs/reference/github-actions-oidc.yml.exampleviagit mv;.github/removed entirely. It held exactly one file — no CODEOWNERS, no dependabot, no templates — and nothing referenced the path. References updated inREADME.md,CLAUDE.md,docs/06,.gitea, and §7.- Q22 left open with the designed-not-confirmed phrasing intact.
The reviewers found that the D3 sweep had never actually reached docs/06
Both agreed on the same blocking defect, independently. docs/06-deployment.md
is the file README.md points to as "Full procedure, IAM policy, runner setup" —
and it still opened with:
Authority: AGENTS.md §3 D3 (git + GitHub Actions → ...)— D3 as amended says Gitea;- a topology diagram, the first content block in the file, reading
GitHub push to main → GitHub Actions → assume AWS role via OIDC (no stored keys). That is the exact sentence entry (l) called "the serious one", six lines above the section that contradicts it; - an entire 19-line section,
## Why OIDC and not access keys, in the imperative — "IAM → Identity providers → add OIDC provider..." — with no marker that it does not apply, naming a third principaladr-site-deploythat conflicts withadr-sml-deploy, and citing Q9, which is answered.
adversarial-reviewer named the concrete failure: an operator working the
document top-to-bottom adds a GitHub federation trust to account 327082975128
— the account §10 rates High blast-radius — that nothing will ever use, and
plausibly never creates adr-sml-deploy at all. Two mutually exclusive
credential procedures in one file with no signpost saying which is live.
Fixed. Authority line cites D3 as amended. Topology rewritten to Gitea + static key. The OIDC section is retitled Why OIDC would have been better — and why it is unavailable, opens with a do not execute block, and its executable steps are gone.
The "licensed" sweep had missed three more files, including the auditor's own persona
Entry (n) scoped the reversal to "README.md, CLAUDE.md (twice), and this
file." Five instances survived in three files it did not name:
.claude/agents/claims-auditor.md:8— "You audit public copy for a licensed legal professional's marketing site." This is the system prompt of the agent that enforces the rule. Its opening sentence asserted the exact claim its own §"Licensure (D13)" instructs it to flag..claude/agents/adversarial-reviewer.md:9— "a legal professional's dispute resolution practice", the precise phrase entry (n) reverses.docs/08-execution-protocol.md:63— "a licensed professional's public marketing page". This file was edited in that same diff, eleven lines above the surviving phrase.
Entry (n) said "twice in two entries that a claim about a sweep was made without the sweep being verified. The pattern, not the instances, is the finding." This was the third. It is now swept by command, not by recall:
grep -rn -i "licensed\|legal professional" --include="*.md" --include="*.yml" .
Every surviving hit is either rule text stating the prohibition, or Change Log history, which rule 3 forbids editing.
Also fixed
Register and rule accuracy. §4's internal-docs rule cited a [assumed] stamp
on licensure that does not exist — the two licensure rows are stamped
[verified] as directives not to publish, which is a different thing. A reader
following the pointer found nothing there, in the sentence stating the rule.
Reworded here and in CLAUDE.md:30 to say what is true: the Verified table does
not establish licence status either way. Duplicate Q22 — two rows, two
scopes, one number, one of them mine; merged into the fuller statement, keeping
the architect's aws iam get-user check command. §7 Node re-stamped
[verified] — it was [assumed] for something directly checkable.
Claims that overstated what exists. CLAUDE.md:164 still said "deploys use
OIDC role assumption" — verbatim the F1 claim, in a file both prior sweeps
touched; it and CLAUDE.md:119 are corrected. .gitea/workflows/deploy.yml and
CLAUDE.md asserted the deploy credential in the present tense; both now cite
Q22. docs/06 asserted five blocking PR checks — the workflow has no
pull_request trigger and neither lint nor lighthouse is wired, so nothing
gates a merge today; retitled planned, not implemented. README.md:5 and
docs/06's "every push deploys" both hedged against the never-run-green fact the
same files establish elsewhere.
The moved reference file carried the defect the move was meant to contain.
Its line 16 still read "OIDC role assumption — no long-lived AWS credentials in
this repository": present tense, about this repository, and false. It also
carried a live TODO(pouya): AGENTS.md Q9, Q10 against two struck rows — which
CLAUDE.md's definition of done treats as a build failure — for a variable that
will never be set. Both fixed, along with its claim that the file is usable on
GitLab (different CI schema entirely; it is the design there, not the
implementation).
Two security-posture claims narrowed. docs/06 said the workflow's
aws sts get-caller-identity pre-flight means "a credential problem fails loudly
and early rather than halfway through a sync." sts:GetCallerIdentity requires
no IAM permission — it succeeds for any valid key regardless of policy. It
catches a missing or revoked key and misses an under-scoped one, which is exactly
the risk this diff introduced by removing an action. Narrowed, with the one-line
aws s3 ls check that would make it load-bearing. And README.md had re-dropped
§10's "which by its name is" hedge on mlp-clientdb-prod-backups — the defect
entry (m) F2 records as fixed — as had docs/06:113, escalated in entry (n) and
not done. Both restored.
Documentation matching implementation. The cache section described two
sync passes; the workflow runs three, and two ordering dependencies are
load-bearing and undocumented — pass 3 re-walks the tree and preserves pass 2's
image headers only because sync skips just-uploaded objects, and pass 3's
excludes also exclude those prefixes from --delete, which is why hashed assets
from old deploys are kept deliberately. Both now stated. The robots.txt /
sitemap*.xml row claimed max-age=3600; nothing implements it, so the table
now records what the pipeline does. Two conflicting variable tables for the same
five names — one under Secrets with real values, one under Variables with
placeholders — merged; entering those as secrets would have masked them in the
logs the doc says to read them from. Duplicate booking references (Q5 vs R6)
resolved to R6. README.md said "no src/pages/"; the directory exists and is
empty, which is not what a reader would find.
docs/08-execution-protocol.md — entry (n)'s inserted block landed between
"claims-auditor reads the copy..." and its follow-up "It is a separate agent
on purpose", leaving "It" twelve lines from its antecedent and reading as though
the frontmatter were a separate agent. Block moved below.
Declined, with reasons
s3:AbortMultipartUpload not added. adversarial-reviewer traced every AWS
call in the workflow against the policy and found the happy path fully covered,
with this one gap: objects over 8 MB upload multipart, and an interrupted
transfer cannot clean up its own parts. It recommended against adding the
permission — an S3 lifecycle rule aborting incomplete uploads after 7 days solves
it without widening the policy. Agreed, and left as an operational item below
rather than a policy change.
The aws s3 ls pre-flight is documented, not implemented. Adding it changes
the deploy pipeline, which was not in scope. It is written into docs/06 as the
fix for the narrowed claim.
§4's tables untouched. Two findings land in the register itself; both are escalated below rather than edited. §4 is the architect's instrument.
Escalated — for the architect
(All four now tracked: 1 and 2 as Q25 and Q26 in §9; 3 folded into Q26; 4 left in R1, which already owns the licensure framing.)
- §4's Verified table authorises what D16 forbids.
AGENTS.md:153reads "The Toronto litigation and ADR boutique may be named on the site ... the name itself is pending, see Q7." D16 says "Never named." Q7 is answered: "Pouya reverted to generic. The boutique is never named." A Verified row currently permits publication of the exact thing a locked decision bars — the same shape as the F5 defect in entry (l), in the same table, and the register is where copy is written from. - §4's opening asserts regulatory status by implication. "Every public claim on this site is subject to Law Society of Ontario marketing rules" — LSO marketing rules bind licensees. Stating flatly that they govern this site asserts the licensure §4 declines to verify, in the sentence that replaced "Pouya is a licensed legal professional". A half-measure of the same shape as the "legal professional" reversal.
- Consider an explicit §4 row — "LSO licence status | UNKNOWN / not verified either way" — which would make the internal-docs rule's pointer land somewhere real instead of being reworded around.
- R1 (§12) describes "the site currently asserts the JD only". There is no
site;
src/pages/is empty. R1 is the reminder that must not ossify, so it should be accurate about what it describes. - Operational: add an S3 lifecycle rule aborting incomplete multipart
uploads after 7 days; and once Q22 is answered, run
aws s3 sync ./dist s3://adr-smlcompany-site --dryrununder the new key to confirm nos3:GetBucketLocationcall is needed. - Minor:
.yml.exampleis not a Prettier-recognised extension, so the moved reference file has silently dropped out ofprettier --checkcoverage. Frozen reference material, so near-harmless — but nothing validates it as YAML now.
Q23–Q26 opened, none blocking the build. Q23: is an act_runner registered, and is the Gitea
instance ≥ 1.21 for the vars context the workflow depends on throughout? On an
older instance vars.S3_BUCKET interpolates empty and the sync target becomes
s3://. Q24: AWS-Hosting-Guide.md is cited by docs/06 as the source for
existing infrastructure and two cutover checks, and does not exist in the
repository; the two dependent checks have been restated self-containedly, so
nothing is blocked. Q25 and Q26 carry the two §4 escalations above.
R7 and R8 added to §12. R7 records three spec decisions made on Pouya's
behalf and not ratified — the amended cache-policy row, the omitted
s3:AbortMultipartUpload, and the documented-not-implemented aws s3 ls
pre-flight. R8 records the sweep-verification lesson as a standing discipline
rather than a lesson buried in one entry, which is what let it recur three times.
Verification
npm run lint, npm run check, and npm run build were not run —
node_modules is absent, and npm ci cannot install without a lockfile. No
JavaScript, TypeScript, or Astro source was touched. The .gitea workflow and
the .yml.example were not YAML-parsed either: PyYAML is unavailable in this
environment and the project has no YAML tooling installed. Both were changed only
in comment blocks, and the indentation of every edited comment was checked by
eye. The "licensed" sweep was executed as the grep above and its output read, not
asserted. Every prose line added to README.md and CLAUDE.md is ≤ 80 columns.
Lesson. Entry (m) recorded that a correction is a change and gets reviewed
like one. This pass adds the sharper version: the sweep must be a command, and
its output must be read. Three consecutive entries claimed a completed sweep;
three times instances survived, and the third time one of them was inside the
definition of the agent whose job is to catch it. The /wrap step should require
pasting the grep, not describing it.
2026-08-26 (n) — Register corrected; a declined finding reversed on appeal
Who: Pouya relaying Claude Code's re-audit. Claude (architect) recording.
Correction to entry (l), which was false. It claimed all three files had been
rewritten off "licensed". Two were. This file still said it twice — §4's own
opening sentence ("Pouya is a licensed legal professional") and §10's risk row
("professional-conduct exposure for a licensed practitioner"). The register was
asserting as established fact the very thing it marks [assumed], in the section
that exists to prevent exactly that. Entry (l) stands as written per rule 3; this
is the correction. Both lines are now fixed.
That is twice in two entries that a claim about a sweep was made without the sweep being verified. The pattern, not the instances, is the finding.
Reversal — "a legal professional's" is out too. claims-auditor wanted the
phrase cut entirely, arguing that in Ontario it reads as an LSO licensee and so
trips the same bar as "licensed". Claude Code declined it, correctly, because it
was my explicit ruling — and recorded the reasoning rather than silently keeping
it, which is what let it be revisited.
On appeal the auditor is right and I was wrong. "Legal professional" was a
half-measure: a weaker assertion of the same unverified thing. The fix is to
assert nothing about professional status and state the reason instead — the
previous site carried fabricated credentials, which is true regardless of licence
status and carries the full force of the rule. Applies to README.md,
CLAUDE.md (twice), and this file.
Worth preserving: the mechanism worked because the decline was reasoned rather than silent. A finding dropped quietly is a finding that cannot be appealed.
Q22 opened — does adr-sml-deploy exist? Claude Code caught that its own
README rewrite asserted a control nothing records as in place. docs/06 is
written imperatively — "Create the user", "Create an access key" — a procedure,
not a record. Nothing in this file says it was performed. Its framing of this as
"same failure class, inverted" is exactly right: the original README understated
the credential risk, its replacement overstated the control. The §10 rotation
obligation has no subject until Q22 is answered.
Accepted — §10's "four actions" resolves by tightening the policy, not editing
the count. docs/06 grants five: ListBucket, PutObject, PutObjectAcl,
DeleteObject, CreateInvalidation. s3:PutObjectAcl is unused by aws s3 sync
without --acl, and inert under OAC with ACLs disabled. Dropping it makes §10
accurate and the policy tighter — strictly better than correcting the number.
Handed to Claude Code.
Accepted — move the GitHub workflow out of .github/. Gitea falls back to
.github/workflows when .gitea/workflows is absent, so a live push: trigger
sitting there is conditionally inert, not inert. Moving it to
docs/reference/github-actions-oidc.yml.example makes it structurally inert.
Handed to Claude Code.
Also noted, no action: Claude Code reported that npm run lint and
npm run check could not run because node_modules is absent, and said so
rather than reporting a check it did not run. That is the Phase 5 rule working.
Opened: Q22. Still blocking the build: nothing. Still blocking
/contact/: Q19.
2026-08-26 (m) — README corrected against D3-as-amended; second audit found nine more
Who: Pouya ruled on the five findings in entry (l); Claude Code implemented,
re-ran claims-auditor on the result, and resolved the second pass.
The architect's rulings, applied
F5 — no change, as ruled. D13 bars "practice" for the boutique role, not for
Pouya's own ADR practice. README.md:3 and package.json:5 stand.
F2 — scope corrected, wording changed. The Forbidden table governs published
pages, not repository documentation; that scope is now stated in §4. The real
defect was different and the architect identified it: licensure is [assumed]
in §4, so no file should call Pouya "licensed". README.md:36 now reads a legal
professional's public marketing surface — the clause that follows, about the
previous site's fabricated credentials, already carries the reasoning.
F1, F3, F4 — Deployment section rewritten against §7 and
docs/06-deployment.md. The section had been written against D3 as originally
decided and never swept when D3 was amended to Gitea. It now names
.gitea/workflows/deploy.yml as the live pipeline, says Gitea Actions rather
than GitHub Actions, states plainly that a long-lived AWS credential is involved
and why (Gitea is not an AWS OIDC provider), and names the quarterly rotation
obligation. format and lighthouse added to the scripts table.
The second audit found nine findings in the replacement text
Seven fixed, one declined, one escalated. The re-audit was worth running: the rewrite traded the original defect for its mirror image.
F1 (fixed) — the replacement asserted a control state that may not exist.
The new text said deploys do authenticate as IAM user adr-sml-deploy with a
key in Gitea secrets. Only the decision is verified (D3 as amended, §7).
docs/06-deployment.md is written entirely in the imperative — "Create the
user", "Create an access key" — i.e. a procedure to perform, not a record of one
performed, and adr-sml-deploy appears nowhere else in this file. The old README
understated the credential risk; the new one asserted a control that nothing
records as in place. Same failure class, inverted. Now phrased as designed-not-
confirmed, and raised as Q22.
F2 (fixed) — a hedge was dropped, and an account number was disclosed. §10
says mlp-clientdb-prod-backups is "which by its name is a law firm's
production client-database backups" — only the bucket name was ever observed.
The README stated the contents flatly. It also paired the live account ID with
that statement in the repo's most pasteable file, and D16 says the boutique is
never named — "this account holds a law firm's client database" is an inference
vector that costs nothing to remove. The account number and the bucket name are
both now out of the README; the security reasoning stays, pointing at §10.
F3 (fixed). The bullet led with "The key is rotated quarterly" — a description of an operating control — and conceded four lines later that the task has no owner. Now an obligation: must be rotated quarterly, and nobody owns that yet.
F4 (fixed). "It does not run. Editing it changes nothing." is conditional,
not absolute: Gitea falls back to .github/workflows when .gitea/workflows is
absent, and that file carries a live push trigger on main. Realised risk is
low — it would fail on role-to-assume with no OIDC provider rather than
mis-deploy — but describing a trigger-eligible file as inert invites someone to
treat it as safe. Now states the condition. The auditor's better fix was
declined for scope: moving it to docs/reference/github-oidc-deploy.yml.example
would make the claim structurally true instead of dependent on Gitea's
resolution order. Worth doing; not done here.
F6 (fixed) — "Pushes to main build and deploy automatically" was not true.
Verified against the filesystem, not inferred: there is no package-lock.json,
so .gitea/workflows/deploy.yml:46's npm ci exits non-zero at step one; there
is no src/pages/, so both npm run check and npm run build fail; and whether
an act_runner is registered is recorded nowhere, which docs/06:125-130 warns
"looks exactly like a broken pipeline." The README now says the pipeline has
never run green and names all three reasons.
F7 (fixed). "No wildcard actions, no wildcard resources" is literally false —
the policy uses arn:aws:s3:::BUCKET_NAME/*, correctly scoped but a wildcard. In
a paragraph whose entire job is to stop a future maintainer widening the policy,
an overstatement is the wrong error: the first reader who notices it discounts
the rest. Now: no Action: "*", no Resource: "*", nothing outside one bucket
and one distribution.
F8 (fixed). npm run lint and npm run lighthouse are listed in a table a
reader takes as "these work." Neither does — there is no ESLint flat config, and
ESLint 9 (pinned ^9.0.0) errors without one, so the command dies before
Prettier runs; and there is no lighthouserc, so lhci autorun has no budgets
even though docs/06:221-222 makes Lighthouse a blocking check. Both rows are
now marked not yet wired.
F9 (fixed). Two precision defects. The README restated the project's single
most important rule more narrowly than §4 states it — "no factual claim about
the practice" versus §4's "him, his credentials, his experience, or his
practice" — in the file a new contributor reads first. And it said /build runs
two review agents unconditionally, where .claude/commands/build.md:52-53 allows
skipping claims-auditor when no copy changed. Both now mirror their sources.
F5 (declined) — the auditor asked to cut "a legal professional's" entirely,
on the grounds that in Ontario the phrase reads as an LSO licensee and so trips
§4's bar on "phrasing that implies entitlement to practise law." Declined
because the architect ruled this exact wording four hours earlier, having already
considered and rejected "licensed", and because the same phrase now appears
deliberately in CLAUDE.md. Recorded rather than silently kept: the auditor's
reasoning is not obviously wrong, and if the phrase is judged to imply licensure
it needs changing in three files at once, not one.
Escalated to the architect — not fixed here
-
Entry (l) records
CLAUDE.md,AGENTS.md, andREADME.mdas all rewritten off "licensed". Two of the three were.CLAUDE.mdis clean. This file still says it twice — line 110, "Pouya is a licensed legal professional", in the opening sentence of §4 itself, and line 303, "professional-conduct exposure for a licensed practitioner", in §10. The register is asserting as fact the thing it marks[assumed], in the section whose whole purpose is to stop that. Not edited here because §4 is the architect's instrument. -
§10 says the deploy policy "grants four actions on one bucket and one distribution." It grants five:
s3:ListBucket,s3:PutObject,s3:PutObjectAcl,s3:DeleteObject,cloudfront:CreateInvalidation. The discrepancy resolves in a useful direction —s3:PutObjectAclis unused byaws s3 syncand inert under OAC with ACLs disabled (docs/06:205-206), so the policy is one action wider than it needs to be. Dropping it makes the §10 count correct and the policy tighter at the same time.docs/06:108also drops §10's "by its name" hedge on the backup bucket. -
The
.githubworkflow's location — see F4 above.
Verification
npm run lint and npm run check were not run: node_modules is absent, so
neither can execute. This change touches only README.md, which is outside
astro check and ESLint; Prettier has no config in the repo, so its default
proseWrap: "preserve" leaves markdown prose unreflowed. Line widths were
checked by hand and every line Claude Code wrote is ≤ 80 columns.
Lesson, extending entry (l)'s. (l) recorded that amending a decision requires sweeping every file that referenced the original. This pass adds the corollary: a correction is a change, and gets reviewed like one. Seven of the nine findings above were in text written specifically to fix an audit finding. The first rewrite of a false claim is the most dangerous moment for it, because everyone involved is now confident the area is clean.
2026-08-26 (l) — First adversarial review run; it found defects in the specs
Who: Pouya ran claims-auditor against README.md to verify the agents load.
It loaded, and returned five findings before any site code exists.
The agents are confirmed working. R1 was surfaced unprompted, as §12 requires. F2 and F5 were escalated rather than silently edited — the behaviour the brief asks for on judgement calls.
Two register-level defects fixed here (architect's, not Claude Code's):
F5 root cause — §4 contradicted D13. Old → new: the Verified table read "Active litigation practice: personal injury, construction, regulatory (POA), insurance (SABS)" → "Active litigation exposure: …". The row quoted the strategy brief verbatim and used the exact word D13 bars in exactly the context D13 bars it. Anyone writing copy from the register would have inherited the violation from the register itself — the worst place for it to live.
D13 now also states the distinction explicitly: "practice" is barred for the
boutique role, not for Pouya's own ADR practice. So "the dispute resolution
practice of Pouya Lajevardi" is correct — README.md:3 and package.json:5 need
no change. F5 resolved, no edit to the README.
F2 — the Forbidden table's scope was overbroad. Old → new: heading read "must not appear anywhere" → "must not appear on the site", with the scope stated: published pages including JSON-LD, not repository documentation.
The auditor was right that something was wrong, though the rule it cited was the
wrong one. README.md, CLAUDE.md, and this file all called Pouya "a licensed
professional" while §4 marks licensure [assumed]. Internal docs were
asserting as fact what the register cannot vouch for. A new rule now covers this
directly. The three phrases are rewritten to state the reason for the
strictness — the previous site carried fabricated credentials — rather than a
credential we cannot confirm.
F1, F3, F4 — handed to Claude Code. One root cause: README.md's Deployment
section was written against D3 as originally decided and never updated when D3
was amended to Gitea. CLAUDE.md, docs/06, and this file were all updated; the
README was missed.
F1 is the serious one. "Deploys via OIDC — there are no long-lived AWS credentials
in this repository" is false, and dangerous in the specific way the auditor
identified: it is narrowly defensible, since the key is not tracked in git — which
is exactly why it would suppress the quarterly rotation obligation in D3, in an
account §10 rates High blast-radius because it holds mlp-clientdb-prod-backups.
Lesson recorded. Amending a decision requires sweeping every file that
referenced the original, not the files that happen to come to mind. The review
caught it; the amendment process should have. Worth adding to /wrap.
2026-08-26 (k) — Q21 resolved: nothing broken; SES sandbox is per-region
Who: Pouya ran the per-region check.
Q21 closed — the good case.
| Region | Verified | DKIM | Tokens |
|---|---|---|---|
ca-central-1 |
true |
SUCCESS | kznn… jdue… f5pu… — resolve correctly |
us-east-1 |
false |
PENDING | 3zsn… jejgp… xpiwy… — doubled domain |
The region that matters is healthy. The three broken CNAMEs belong to a stray
us-east-1 identity that nothing in this project uses; it is PENDING precisely
because those records were entered with the domain doubled. No fix required.
Recommendation recorded: leave the three dead CNAMEs in DNS. They are inert,
and the only way to cause harm is deleting the wrong row from a list of six
visually similar records. Optionally delete the us-east-1 identity itself so it
stops reporting PENDING — that is a control-plane action with no DNS risk.
New finding, and it would have cost a day: the SES sandbox is per-region.
ProductionAccessEnabled: false was read from ca-central-1, which is where the
intake Lambda, DynamoDB, and API Gateway all live. The AWS console defaults to
us-east-1, and the panel Pouya screenshotted was us-east-1 — so the natural
path would have been to request production access in the wrong region, wait a
day, and find the confirmation email still failing. Flagged in §7 and on Q19.
Also worth noting for the record: entry (j)'s correction was itself only half the story. DNS proved which records resolved; it could not prove which mattered. That needed the control plane. Neither source is sufficient alone — the data plane says what exists, the control plane says what is expected, and a discrepancy between them is the finding.
Closed: Q21. Still blocking: Q19 only.
2026-08-26 (j) — SPF/DMARC live; and a correction: three DKIM records are broken
Who: Pouya added the two TXT records. Claude verified independently.
Q20 closed. SPF and DMARC are both live and correct, confirmed by direct DNS
query rather than taken on report. Also confirmed unchanged: both existing @
TXT records, google._domainkey, MX 1 smtp.google.com, and
adr.smlcompany.ca → CloudFront. Nothing was disturbed.
Correction to entries (h) and (i) — I had the two DKIM sets backwards.
- Old → new: "
3zsn…/jejgp…/xpiwy…are the live set;f5pu…/jdue…/kznn…are inert orphans" → the opposite. Thef5puset resolves NOERROR at the correct names. The3zsnset returns NXDOMAIN. - Root cause found.
3zsnvsjg…._domainkey.smlcompany.ca.smlcompany.caresolves; the correct name does not. Those three were entered into Namecheap's Host field with the full name including the domain, so Namecheap appended the domain a second time. This is precisely the gotcha flagged in entry (h) — which was written before checking whether the existing records had already fallen into it. - Why I got it wrong: I inferred "live" from the SES console listing them and did not verify against DNS. The console shows what SES expects, not what exists. Lesson worth keeping: a control-plane listing is not evidence about the data plane.
- No harm done. The advice in (i) was "leave both sets alone", which happened to be the safe action either way.
Q21 opened. SES is regional, and ca-central-1 reported
VerifiedForSendingStatus: true — only consistent with the f5pu set. So the
console panel screenshotted was probably a different region's identity. Until
that is confirmed per region, it is unknown whether the broken records matter.
Nothing should be deleted before that check.
Closed: Q20. Opened: Q21. Still blocking: Q19 (SES production access).
2026-08-26 (i) — SES DNS panel resolves the DKIM ambiguity; MAIL FROM absent
Who: Pouya sent the SES "Domain DNS records" panel.
The six DKIM CNAMEs are now fully explained. SES lists exactly three for
smlcompany.ca: 3zsnvsjg…, jejgp7na3…, xpiwyftpo…. The other three in
Namecheap — f5puwearz…, jdue2r22c…, kznn3cklv… — are not referenced by
SES and are residue from an earlier verification of the same domain. Note the
TTLs corroborate this: the live set reads "1 min", the orphans "Automatic",
consistent with two separate additions.
Recommendation recorded: leave the orphans in place. They are inert. The only way this becomes a problem is deleting the wrong three, which would break SES DKIM and therefore DMARC. There is no upside to tidying a DNS zone.
Custom MAIL FROM is not configured — "No MAIL FROM records found". This
independently confirms the correction in entry (h): SES's envelope sender is at
amazonses.com, its SPF pass is unaligned with smlcompany.ca, and DMARC is
satisfied through DKIM alone. Configuring a custom MAIL FROM would add SPF
alignment as belt-and-braces; it is optional and not on the critical path.
AWS's suggested DMARC value is v=DMARC1; p=none; — valid but minimal, with
no rua, so it produces no reports. Using v=DMARC1; p=none; rua=mailto:info@smlcompany.ca; fo=1
instead so the monitoring period actually yields data before tightening to
quarantine.
Net action for Q20 is two TXT records and nothing else.
2026-08-26 (h) — DNS inspected; earlier SPF reasoning corrected
Who: Pouya sent the Namecheap DNS panel, asking exactly what to change without breaking anything.
Correction to entry (g) — the SPF reasoning was wrong. Old → new: "SES needs
SPF or mail lands in spam" → SES authenticates via DKIM, not SPF. Without a
custom MAIL FROM domain, SES's envelope sender sits at amazonses.com, so its
SPF pass is not aligned with smlcompany.ca and contributes nothing to DMARC.
The six *.dkim.amazonses.com CNAMEs already in Namecheap are what make SES
DMARC-compliant, and they are working. The SPF record's real value is
authenticating Google Workspace mail, which has none. include:amazonses.com
stays because it is harmless and becomes live if a custom MAIL FROM is added.
Entry (g) is left as written per rule 3; this is the correction.
Also found in the DNS panel:
- Six SES DKIM CNAMEs where three are needed — the domain was verified twice. Harmless, but do not prune without checking SES first.
- Two ACM validation CNAMEs →
jkddzztszm.acm-validations.aws. Flagged as never-delete: they auto-renew theadr.smlcompany.cacertificate, and removing them breaks HTTPS silently at the next renewal rather than immediately. This is the only genuinely destructive mistake available in that panel. @carries a Namecheap URL Redirect tosml-coming-soon.ca. TXT records coexist with it, so adding SPF does not disturb the redirect.
DMARC rua changed from pouya@ to info@smlcompany.ca — the mailbox
confirmed to exist.
Still blocking: Q19 (SES production access), Q20 (the two TXT records).
2026-08-26 (g) — SES purpose clarified; D18; SPF and DMARC found missing
Who: Pouya asked why SES is needed given he already has an info@ mailbox.
Fair question — the answer exposed two things.
The distinction, recorded because it will come up again. A mailbox receives
mail and a person logs into it. SES lets code send mail — the intake Lambda has
no mailbox and no login. They are complementary, not alternatives: SES sends
to info@smlcompany.ca. Verifying the address in SES did not create a
mailbox; it authorised sending.
D18 — two emails per submission (new decision). The sandbox blocks only one
of the two: the notification to Pouya works (his address is verified), the
receipt to the inquirer does not. That receipt was Claude's addition in
docs/05-backend-spec.md, not part of the original AWS-guide build, so it was
put back to Pouya as a real choice rather than treated as a defect. He confirmed
he wants it — so Q19 is genuinely blocking rather than optional.
Q20 opened — the domain has no email authentication at all. A DNS query of
smlcompany.ca on 2026-08-26 returned:
- TXT: only
anthropic-domain-verificationandgoogle-site-verification. Nov=spf1record of any kind. _dmarc.smlcompany.ca: no record.- MX
1 smtp.google.comandgoogle._domainkeypresent — Google Workspace with DKIM configured. adr.smlcompany.caCNAME →d26v23dhgsp2ta.cloudfront.net, as expected.
Old → new (planned): no SPF, no DMARC → one SPF record covering both Google
and Amazon SES, plus a DMARC record starting at p=none.
This is wider than this project. Pouya's existing business email from
smlcompany.ca is sending unauthenticated today. Google Workspace's DKIM carries
it, but Gmail and Yahoo both penalise missing SPF and DMARC, so mail he sends as a
human is already more likely to be filtered than it should be. Raised to High
in §10 and flagged to him directly. Both records should go in before the SES
production-access request — AWS weighs domain authentication when reviewing, so
it improves the odds of first-time approval as well.
Opened: Q20. Still blocking: Q19, Q20.
2026-08-26 (f) — SES sandbox confirmed; execution protocol built into the repo
Who: Pouya. Claude recording.
Q18 closed, Q19 confirmed as a live blocker. smlcompany.ca is verified
for sending (true) — the earlier None was a query artefact, not a fault. But
ProductionAccessEnabled: false: the account is in the SES sandbox. Mail
reaches only pre-verified addresses, so an inquirer's confirmation email fails
silently. Raised from Medium to High in §10: this is the failure mode where
the site looks like it works and the inquirer concludes they were ignored.
Production access must be requested; ~24 h turnaround, and it is the only
dependency on this project with external lead time.
D17 — working method, made structural (new decision).
- Old → new: an implicit understanding that Claude Code implements what Pouya decides → an explicit six-phase loop encoded in the repository, so it applies without being restated in each prompt.
- Why: Pouya's direction — he architects, Claude Code programs and then adversarially reviews, with deep reasoning on, permanently.
- Added:
.claude/agents/adversarial-reviewer.md— code, accessibility, crawlability, performance, security, simplicity. Instructed to treat uncertainty as a defect and told that an approving review which misses a real defect is a failure..claude/agents/claims-auditor.md— every factual assertion traced to §4. Separate agent on purpose: a generic reviewer consistently under-weights professional-conduct checks, which is this project's highest-stakes failure mode. It also audits JSON-LD, since a machine-readable misrepresentation is still a misrepresentation..claude/commands/build.md— the loop./review— phase 3 alone./wrap— the session-endAGENTS.mdritual..claude/settings.json— thinking on, high effort, and a read-deny on.env*andaws-inventory.txt.CLAUDE.md— the protocol and the reasoning behind it.docs/08-execution-protocol.md— the prompt template Pouya writes from.
- The design decision worth preserving: the reviewers are given the diff and the specs, and deliberately not the implementer's rationale. Explaining why the work is correct anchors the reviewer and converts review into agreement. Independence is the mechanism, not a courtesy. If a future change to this protocol removes that, it removes the value.
Closed: Q18. Still blocking: Q19 (SES production access).
2026-08-26 (e) — AWS inventory captured; Q10 closed; blast-radius risk logged
Who: Pouya ran scripts/aws-discover.sh. Claude recording.
Q10 closed. All identifiers in §7. Bucket adr-smlcompany-site,
distribution E1OK7G98KNKUTA, region ca-central-1, intake API
4tl0m5igkj. Certificate ISSUED. S3 versioning is already Enabled, so the
rollback path in docs/06-deployment.md works as written — no change needed.
Data residency confirmed. Hosting, Lambda, and DynamoDB are all in
ca-central-1. docs/05-backend-spec.md treated Canadian residency as a
selling point conditional on the table's region; it is now a verified fact and
/legal/privacy/ can state it plainly.
New risk logged — deploy-credential blast radius. The inventory shows this is
a shared account across at least five unrelated properties, including
mlp-clientdb-prod-backups-327082975128. A Gitea runner holding a static AWS key
for this site sits in the same account as a law firm's client-database backups.
Nothing about the plan changes — the scoped IAM policy was already correct — but
its narrowness is now load-bearing rather than good practice, and that is
recorded so nobody widens it later for convenience.
Q18 and Q19 opened — SES. list-email-identities returned
VerifiedForSendingStatus: None for all six identities, and sandbox status was
not checked. Either condition means the intake confirmation email silently fails.
This is the failure mode where the site appears to work and inquirers think they
have been ignored, so it is on the cutover checklist, not the nice-to-have list.
Closed: Q10. Opened: Q18, Q19.
2026-08-26 (d) — Memberships, analytics, prep time, firm naming; one blocker left
Who: Pouya. Claude recording.
Q16 closed — preparation time is bundled and stated. 2 h in the half day,
3 h in the full day, printed on /fees/. The carried-forward assumption is now a
decision. At $4,000 a day counsel will assume preparation happened; saying so
converts the assumption into a selling point and forecloses an argument about
what the fee covered.
Q15 and Q17 closed. Non-mediation hourly (ENE, settlement counsel, dispute-system design, technical advisory) and mediation overtime both $500/h, matching the arbitration rate.
Q8 closed — memberships. Old → new: five [assumed] bodies from the strategy
brief → ADRIC, ADRIO, and OBA sections, verified. OCNI is not current and
must not be published; the brief listed it as aspirational positioning for the
nuclear niche, which is a different thing from a membership.
- LSO was deliberately not offered as an option and is not published. Listing the Law Society in a credentials block implies licensure, which D13 bars. Worth recording explicitly so a later reader does not "helpfully" add it back.
- Which OBA sections remains
[assumed]— the brief names Construction & Infrastructure, ADR, and Civil Litigation. Low stakes; confirm in passing.
D15 — analytics (new decision). Old → new: unchosen → privacy-first and cookieless, Plausible or Fathom. No GA4, no consent banner, no personal data leaving the site, one honest line in the privacy policy. Consistent with a practice whose privacy posture is part of its offer, on a site where visitors are often describing live disputes.
D16 — the boutique is never named (new decision). Old → new: "may be named, name pending" → generic throughout: a Toronto litigation and ADR boutique. Pouya reversed his earlier answer. Trade-off, recorded for the record: naming a recognisable firm is a meaningful credibility signal to appointing bodies, and the generic phrasing gives that up in exchange for zero coordination cost and no dependency on anyone else's sign-off. Do not infer the name from his email domain or from anything else.
Closed: Q7, Q8, Q11, Q15, Q16, Q17. Opened: none.
Q10 is now the only blocker. Everything else needed to build is answered.
R6 (booking) stays parked by choice and does not block /contact/.
2026-08-26 (c) — Contact, headshot, LinkedIn; booking parked
Who: Pouya. Claude recording.
Closed. Q3 — contact is info@smlcompany.ca, no public phone, consultations
by scheduled call. Q6 — headshot supplied; committed as
src/assets/pouya-lajevardi.jpg (1600×1600 master, for Astro to derive AVIF and
WebP from at build) and src/assets/og-portrait.jpg (1200×630, cropped high so
the face is not centred under the fold of a link preview). Q12 — LinkedIn URL,
which now feeds sameAs in the Person JSON-LD.
Q5 parked, not closed. Old → new: "choose a booking tool" → deferred at
Pouya's request; /contact/ ships with the intake form and a reserved slot for
an embed, so adding one later is a drop-in rather than a rebuild. He asked to be
reminded, so it is now R6 in §12 rather than a question that quietly expires.
Worth restating when it comes back up: D10 chose booking because it removes the
scheduling back-and-forth that loses appointments, and the form alone only
half-solves that.
Q7 partially answered. The boutique may be named. The name itself was not
given. His email domain (meshkinilaw.ca) points to Meshkini Law, but an
inferred firm name is exactly the kind of thing §4 exists to stop — asked for
confirmation rather than published.
2026-08-26 (b) — Fees confirmed; Gitea; licensure wording; standing reminders
Who: Pouya, answering the second round. Claude recording.
D14 closed — rate card confirmed, and it is not what was recommended.
- Old → new: two-tier card (Tier A insurance/SABS at $1,800/day, Tier B commercial at $3,200/day) → one rate for all mediation matters: $2,000 half day, $4,000 full day, $500 per party beyond two. Arbitration $500/h, hearing day $4,000, documents-only flat $6,500 simple / $9,500 complex. Tribunal- secretary line removed. Cancellation terms adopted as recommended.
- Why: Pouya's decision.
- Recorded dissent, for the 12-month review (R5). A single premium rate puts the insurance / SABS / LAT segment out of reach — the published market there runs roughly $800–$1,200 a day, and $4,000 is three to five times it. That is the segment the strategy brief (§IV.7) identifies as the highest realistic near-term volume, flowing directly from the firm's existing accident-benefits work. The trade is deliberate if it is deliberate: a premium specialist position that forgoes volume. It is a mistake if the volume was being counted on. Flagged once, implemented as directed.
- Three parameters were not specified and are carried forward as assumptions, logged as Q15–Q17: non-mediation hourly ($500), bundled prep hours (2 h half day / 3 h full day), mediation overtime ($500/h).
D13 refined — approved wording for the boutique role.
- Old → new: "describe the role factually" → the specific approved phrases are "active litigation exposure" and "involvement in litigation and ADR matters". The word "practice" is barred in that context.
- Pouya asked to be continuously reminded that this is interim. That request is now structural rather than a note — see the new §12.
New §12, Standing Reminders. Five items (R1–R5) that any agent must surface at the start of a substantial session. Added because a parked decision otherwise becomes permanent by never being raised again — and R1 is Pouya's explicit instruction to keep raising it.
D3 amended again — Gitea.
- Old → new: "private git server, software unknown" → self-hosted Gitea with
Gitea Actions, clone at
/Users/pouya/Dev/Websites/adr-sml. - Consequence: Gitea Actions uses GitHub Actions syntax, so the workflow ports
almost unchanged into
.gitea/workflows/deploy.yml. But Gitea is not an AWS OIDC provider, so the OIDC role assumption is replaced by a scoped IAM user whose key lives only in Gitea's secret store. The IAM policy stays exactly as narrow. Quarterly rotation is now an operational obligation that OIDC would have made unnecessary — noted indocs/06-deployment.md.
Closed: Q4, Q9, Q13, Q14. Opened: Q15, Q16, Q17. Still open: Q3, Q5, Q6, Q7, Q8, Q10, Q11, Q12, Q15, Q16, Q17.
2026-08-26 (a) — Licensure decision; Q.Arb dated; git host changed; fees researched
Who: Pouya, answering the blocking questions from §9. Claude recording.
Date correction. The entry below is stamped 2026-08-25. The correct date of
that work was 2026-08-26 — the session clock was a day behind. Per rule 3 the
entry is left as written rather than edited; this note is the correction. Facts
touched today are re-stamped [verified 2026-08-26].
D13 — licensure left out of the site (new decision).
- Old → new: "confirm LSO licence status before launch" → the site asserts the JD and makes no licensure claim at all.
- Why: Pouya's direction.
- Consequence, and it is not small. The strategy brief describes an "active litigation practice in personal injury, construction, regulatory (POA), and insurance (SABS) matters." Copy written that way would imply entitlement to practise law without stating it — which is the risk the decision is meant to avoid, restated in different words. So the Forbidden table now bars implication as well as assertion, and the About page describes the role factually: Director of Firm Operations at a Toronto litigation and ADR boutique, plus the matter types worked on. Flagged back to Pouya: if he is in fact licensed and in good standing, that is a material credential for exactly the audience this site targets, and omitting it costs more than it protects.
Q.Arb dated. Old → new: [assumed], stage unknown → commenced August
2026 [verified 2026-08-26]. Describe as newly commenced. Not as held, not as
nearing completion.
D3 amended — the repository is not on GitHub.
- Old → new: "git repo + GitHub Actions" → repo
adr-smlon a private git server; the GitHub Actions workflow in.github/workflows/is retained as a working reference implementation, to be translated once the server software is known (Q13). - Why: Pouya is running his own git server.
- Knock-on: GitHub's OIDC provider is unavailable. GitLab CE can federate to AWS by OIDC; Gitea/Forgejo and bare git cannot, so those need a scoped IAM user with rotated keys, or deploys run from Pouya's machine. The choice is real and waits on Q13.
D14 — fee structure recommended (new decision, pending sign-off). Market research completed against the Ontario mandatory-mediation tariff, ADR Chambers published ranges, and four published Ontario practitioner rate cards. A two-tier structure is recommended so the price-sensitive SABS/LAT volume work does not anchor the commercial rate. Figures are in the response to Pouya and are not written into any page until he signs off (Q14).
Still open: Q3, Q5, Q6, Q7, Q8, Q10, Q11, Q12, Q13, Q14.
2026-08-25 — Project initiated; architecture decided; repo bundle authored
Who: Pouya Lajevardi with Claude (Cowork session), acting as architects. Claude Code to implement.
What was discussed. Pouya asked for a modernization of adr.smlcompany.ca
going beyond appearance — content and codebase included — with Claude Code doing
the implementation and deploying to AWS.
What was found. An audit of the live site and the local sources established the facts in Current Truth §2. The headline finding: the site is a browser- compiled single-file React bundle that serves crawlers three words of content. For a personal-brand site whose function is discoverability, this is the dominant problem, ahead of any question of looks or copy.
What was decided. Twelve decisions, D1–D12, recorded in §3. In summary: an Astro static rebuild, full content re-architecture, ~20 pages, English only, git + GitHub Actions onto the existing S3/CloudFront, palette and infinity mark retained with a modernized execution, a published rate card, an Insights section launching with drafted content, a rebuilt intake form plus booking, and a single clean cutover.
- Old → new (framework): browser-compiled React single file → Astro static site
- Old → new (publishing): manual
rebuild-standalone.py+ console upload → git + GitHub Actions with OIDC - Old → new (structure): one scrolling page → ~20 pre-rendered pages
- Old → new (content): partially-corrected placeholder template → written fresh against a claim register
- Old → new (record-keeping): none → this file, under the AGENTS constitution
Why. Discoverability is the practice's growth constraint and the current architecture forecloses it. Every other decision follows from fixing that, with the content decisions following from the strategy brief of 2026-05-26.
Also decided, not yet discussed with Pouya: no Indigenous engagement practice page at launch (§6), on the reasoning recorded there. Flag this to him.
Planned, not yet done. Astro implementation; all page copy; 3–5 Insights drafts; intake backend rework; CI/CD wiring; pre-cutover audit. Twelve blocking questions are open in §9 — several pages cannot be written until they are answered.