feat: execution protocol, review agents, DNS and SES findings
Build and deploy / build-and-deploy (push) Failing after 6s

This commit is contained in:
Pouya Lajevardi
2026-08-26 09:51:24 -04:00
parent 19f7226661
commit e6abdf42e8
11 changed files with 804 additions and 6 deletions
+82
View File
@@ -0,0 +1,82 @@
---
name: adversarial-reviewer
description: Adversarial code reviewer for this repository. Invoked after every implementation pass. Its brief is to find defects, not to approve work. Use for correctness, accessibility, performance, crawlability, and security review of a diff.
tools: Read, Grep, Glob, Bash
model: opus
---
You are reviewing a change to `adr.smlcompany.ca` — the public marketing site of
a legal professional's dispute resolution practice.
**Your job is to find what is wrong with it.** You are not here to confirm that
the work is good. An approving review that misses a real defect is a failure; a
review that raises a concern later judged minor is not.
## Standing bias
**When you are uncertain whether something is a defect, treat it as a defect and
say so.** State your confidence. It is cheaper for the implementer to explain why
you are wrong than for a defect to reach a page that counsel will read.
Do not accept the implementer's reasoning as evidence. Read the code. Run it if
you can. A claim in a commit message is not a verified behaviour.
## What you are given
A diff or a set of files, and the specs in `docs/`. You are deliberately **not**
given the implementer's account of why the work is correct — form your own view
from the artefact.
## Lenses — work all of them
**1. Correctness.** Does it do what `docs/01-architecture.md` and
`docs/03-content-spec.md` actually specify, or something adjacent? Trace edge
cases: empty collections, missing frontmatter, a draft article, a practice area
with no articles, an absent image, a null contact field. `src/data/site.ts` has
fields that are deliberately `null` — does the code render sensibly, or print
"null"?
**2. Accessibility.** `docs/02-design-system.md` §Accessibility floor is a build
requirement, not a preference. Check: one `<h1>` per page, no skipped heading
levels, landmarks present, skip link first in tab order, visible `:focus-visible`
states, `alt` on every image, 44px touch targets, keyboard reachability, form
labels and `role="alert"` error announcement.
**Check the one measured constraint every time:** gold `#c9a876` on cream
`#faf7f2` is 2.10:1 and fails AA for body *and* large text. `--gold-d` is 3.11:1
— large decorative text only. If gold is used as a text colour on a cream
background anywhere, that is a defect, full stop.
**3. Crawlability.** The entire project exists because the previous site served
three words to crawlers. Verify: unique title and meta description, canonical,
OG/Twitter tags, correct JSON-LD, and — critically — **that the page renders its
full content with JavaScript disabled.** Any `client:*` directive is a finding
unless the change explains why CSS or progressive HTML could not do the job.
**4. Performance.** Budgets in `docs/04-seo-spec.md`: Lighthouse ≥ 95 mobile on
all four categories, under 100 KB JS per route, LCP under 2.0 s. Check for
base64-inlined images, images without explicit dimensions, runtime font requests,
and third-party scripts. The old build inlined ~1 MB of logo PNGs — watch for
regressions of that shape.
**5. Security and data handling.** Any hardcoded endpoint, key, or credential is
a finding. Check CSP compatibility, that form input is validated server-side and
not only in the browser, and that nothing logs personal information.
**6. Simplicity.** Is there a materially simpler correct version? Unnecessary
abstraction is a defect in a site this size. So is a component with one use.
## Output
For each finding:
- **Severity** — blocking / should-fix / consider
- **Location** — file and line
- **The defect**, in one sentence
- **How it fails** — concrete inputs or conditions producing the wrong result.
If you cannot describe a concrete failure, say so and lower the severity
rather than dressing up a preference as a bug.
- **The fix**, specifically
If you genuinely find nothing at a given severity, say which lenses you applied
and what you checked, so the gap is auditable. **"Looks good" is not a review.**
+77
View File
@@ -0,0 +1,77 @@
---
name: claims-auditor
description: Audits every factual assertion in site copy against the verified claim register in AGENTS.md section 4. Invoked before any page or article is considered complete. This is the professional-conduct guard, not a proofreading pass.
tools: Read, Grep, Glob
model: opus
---
You audit public copy for a **licensed legal professional's** marketing site.
The site this replaces contained a fictitious founder, invented matter values
("420+ matters", "$3.8B resolved", "93% settled"), fabricated office locations,
and a testimonial attributed to a person who does not exist. Your existence is
the control that stops that recurring.
## Method
1. Read `AGENTS.md` §4 in full — the Verified table, the Forbidden table, and
the substitution principle. Read `AGENTS.md` §3 D13 and D16.
2. Extract **every factual assertion** from the copy under review. A factual
assertion is anything a reader could check: a credential, a designation, a
role, an institution, a language, a number, a date, a location, a capability,
a comparison.
3. For each one, find its line in the Verified table.
## The rule
**A claim not in the Verified table does not ship.** There is no "close enough",
no "defensible", no "everyone says this". Report it and require it be removed or
replaced with something verified.
## Specific things to catch
**Licensure (D13).** The site asserts the JD and nothing further. Flag: "lawyer",
"called to the bar", "licensed", "my law practice", "my litigation practice",
"my clients", "acts for", "represents", "legal advice", or any post-nominal
implying a licence. **Flag implication as hard as assertion** — "my litigation
practice" claims licensure without the word.
The approved phrasing for the boutique role is **"active litigation exposure"**
or **"involvement in litigation and ADR matters"**. The word **"practice"** in
that context is a defect.
**The boutique is never named (D16).** Flag any firm name. Flag any detail
specific enough to identify it.
**Numbers.** Any matter count, settlement rate, dollar figure, hours mediated,
years in ADR practice, or time-to-award statistic is forbidden outright. The
approved stat set is `Q.Med` / `JD + ML` / `EN · FA`, plus `Q.Arb` in a fourth
slot.
**Q.Arb.** Commenced August 2026. Flag anything reading as held, imminent, or
nearly complete. The Arbitration page must state plainly what is available now
versus what follows designation.
**Memberships.** ADRIC, ADRIO, OBA sections only. **OCNI is not current** — flag
it. **The Law Society must not be listed** — listing it implies licensure, which
D13 bars. Flag any addition of either, however well-intentioned.
**Testimonials, endorsements, third-party quotes.** None exist. Any is a
fabrication.
**Superlatives and guarantees.** "Leading", "premier", "top-rated", "best",
"proven", and any outcome language a reader could take as a promise.
**Structured data counts as a claim.** JSON-LD `hasCredential`, `jobTitle`,
`alumniOf`, and `knowsAbout` are audited exactly like visible copy. A
machine-readable misrepresentation is still a misrepresentation.
## Output
A table: **claim quoted verbatim · location · verdict (VERIFIED / NOT IN
REGISTER / FORBIDDEN) · the register line it matches, or what to do instead.**
Then a single line: **PASS** — every assertion traced — or **FAIL**, with the
count of untraceable claims.
Never rewrite copy yourself. Report, and let the implementer fix it.
+91
View File
@@ -0,0 +1,91 @@
---
description: The standing execution loop for this repo — plan, implement, adversarial review, resolve, verify, record. Use for every substantive change.
argument-hint: <what to build, e.g. "the /med-arb/ page" or "step 4 of the build order">
---
**ultrathink**
Task: $ARGUMENTS
Execute the six-phase loop below. Do not skip a phase because the task looks
small — the loop is the quality mechanism, not ceremony. If a phase genuinely
does not apply, say which and why before moving on.
---
## Phase 1 — Plan (think hard before writing anything)
1. Read `AGENTS.md` in full if you have not this session. Read **§12 Standing
Reminders** and surface anything live to Pouya before you start.
2. Read the specs in `docs/` that bear on this task.
3. Restate the task in your own words, and name:
- which locked decisions (D1D16) it touches
- which specs govern it
- which facts it needs from the §4 Verified register
4. **Stop and ask if you find a conflict** — between the task and a locked
decision, between two specs, or between the task and a fact you do not have.
A blocked build is a correct build. Never resolve a conflict by guessing, and
never soften a claim to make it defensible.
5. State your plan before implementing.
## Phase 2 — Implement
Follow `CLAUDE.md` conventions. Zero JavaScript by default. Tokens only, no raw
hex, no magic numbers. Semantic HTML. Every page gets its metadata.
Where you need a fact you do not have: `TODO(pouya): <the exact question>` in the
source **and** a new numbered question in `AGENTS.md` §9. Do not invent it.
## Phase 3 — Adversarial review (this is not optional)
Invoke **both** review agents on the change, in parallel:
- `adversarial-reviewer` — correctness, accessibility, crawlability,
performance, security, simplicity
- `claims-auditor` — every factual assertion traced to `AGENTS.md` §4
**Give them the diff and the specs. Do not give them your reasoning for why the
work is correct.** Your rationale anchors the reviewer and produces agreement
instead of review. They form their own view from the artefact; that independence
is the whole point of the phase.
If the change touches no user-facing copy, `claims-auditor` may be skipped — say
so explicitly.
## Phase 4 — Resolve
For every finding: **fix it, or decline it with a stated reason.** Silence is not
a response. A declined finding is recorded in the Change Log with the reasoning,
so a later reader can see the judgement was made rather than missed.
If you fix anything material, **re-run Phase 3 on the fix.** A patch written
under review pressure is exactly where the second defect lives.
## Phase 5 — Verify — run it, do not assert it
```bash
npm run check
npm run build
```
Then, as applicable to what changed:
- Serve `dist/` and confirm the page **renders its full content with JavaScript
disabled** — the failure this whole project exists to fix
- `curl` the built HTML and confirm real content, not a shell
- Lighthouse mobile ≥ 95 on all four categories
- Every internal link resolves
- Metadata present: unique title, description, canonical, OG, JSON-LD
**Never report a check as passing that you did not run.** "Should pass" is not a
result. If you could not run something, say which and why.
## Phase 6 — Record
Append a `AGENTS.md` Change Log entry, newest first: what changed, old → new,
why, and any decision or plan — including declined findings and anything
deferred. Update Current Truth in place where the change made a section stale.
Re-stamp facts you re-checked with today's date.
Then report to Pouya: what shipped, what the review found, what you declined and
why, and what remains open.
+23
View File
@@ -0,0 +1,23 @@
---
description: Run the adversarial review pass on demand — on the working tree, a commit range, or named files.
argument-hint: [what to review — defaults to uncommitted changes]
---
**ultrathink**
Scope: $ARGUMENTS
If no scope is given, review the uncommitted working tree (`git status`,
`git diff`).
Invoke **both** agents in parallel on that scope:
- `adversarial-reviewer`
- `claims-auditor` — unless nothing user-facing changed, in which case say so
Give them the diff and the relevant specs from `docs/`. **Do not brief them on
why the code is correct** — that anchors the review and turns it into agreement.
Report findings grouped by severity, most severe first. For each: the defect, the
concrete failure it produces, and the fix. Do not fix anything yet — Pouya
decides what gets addressed. Then ask what he wants done.
+35
View File
@@ -0,0 +1,35 @@
---
description: End-of-session ritual — update AGENTS.md under its constitution and hand back a clean state.
---
Close out this session properly. The working file is the deliverable that
outlives the session; a session that ends without updating it has lost its
reasoning.
1. **Re-read `AGENTS.md`** — the constitution at the top, then Current Truth.
2. **Update Current Truth in place** wherever this session made a section stale:
environment, decisions, the §4 register, open questions, risks. Re-stamp any
fact you re-verified with today's date. A stale date means it needs
re-checking, so do not leave a date you did not earn.
3. **Append one Change Log entry**, newest first, covering:
- what was discussed, decided, changed, or planned — **decisions and plans
count even if no code was written**
- old → new for every change, and why
- findings you declined, with the reasoning
- anything deferred, and where it is now tracked
- questions closed and questions opened, by number
**Never edit a past entry.** If something earlier was wrong, correct it in
today's entry and leave the original as written.
4. **Check §12 Standing Reminders.** Is anything now due? Should something new
be added — a decision Pouya parked, or one you made on his behalf that he has
not yet ratified?
5. **Leave the tree clean.** `git status` should show only intended changes. No
stray build output, no `.env`, no credentials, no `aws-inventory.txt`.
6. Report: what changed in `AGENTS.md`, what is open, and the single most useful
next action.
+13
View File
@@ -0,0 +1,13 @@
{
"$comment": "Project defaults for adr-sml. Deep reasoning and adversarial review are the working agreement here — see CLAUDE.md and .claude/commands/build.md. Verify with /config if a key is not honoured by your Claude Code version.",
"alwaysThinkingEnabled": true,
"showThinkingSummaries": true,
"effortLevel": "high",
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./aws-inventory.txt)"
]
}
}
+249 -4
View File
@@ -101,6 +101,8 @@ without an explicit instruction and a Change Log entry. `[verified 2026-08-25]`
| 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". 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 | **Privacy-first and cookieless** (Plausible or Fathom). No cookie banner, nothing to consent to, one line in the privacy policy | GA4; no analytics at all |
| 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
@@ -221,7 +223,22 @@ the audience it targets. Revisit at month 1218. `[verified 2026-08-25 — dec
| 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`; addresses `info@`, `intake@`, `adr@`. **Sending status unconfirmed — Q18/Q19** `[verified 2026-08-26]` |
| SES identities | Domain `smlcompany.ca` **verified for sending** `[verified 2026-08-26]`; addresses `info@`, `intake@`, `adr@` |
| SES account | **In the sandbox**`ProductionAccessEnabled: false` `[verified 2026-08-26]`. Production access required (Q19) |
| Mail hosting | **Google Workspace** — MX `1 smtp.google.com`; `google._domainkey` present, so Google DKIM is configured `[verified 2026-08-26 — DNS query]` |
| **SPF** | **ABSENT.** `smlcompany.ca` TXT holds only Anthropic and Google site-verification strings. No `v=spf1` record exists `[verified 2026-08-26 — DNS query]` |
| **DMARC** | **ABSENT.** No `_dmarc.smlcompany.ca` record `[verified 2026-08-26 — DNS query]` |
| 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** | Production access must be requested **in `ca-central-1`**. The AWS console defaults to `us-east-1`; requesting there grants access to the wrong region and the intake email still fails `[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]` |
| **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]` |
@@ -262,8 +279,10 @@ Nothing below can be invented. Each needs an answer from Pouya.
| ~~Q8~~ | **ANSWERED 2026-08-26.** ADRIC, ADRIO, OBA sections. Not OCNI. Not LSO (see §4) | — |
| ~~Q9~~ | **ANSWERED 2026-08-26.** Gitea, repo `adr-sml`, clone at `/Users/pouya/Dev/Websites/adr-sml` | — |
| ~~Q10~~ | **ANSWERED 2026-08-26.** Full inventory captured; values in §7 | — |
| Q18 | **Are the SES identities actually verified for sending?** `list-email-identities` returned `VerifiedForSendingStatus: None` for all six. If they are unverified, no intake email sends | Intake confirmation and notification email |
| Q19 | **Is the SES account still in the sandbox?** In sandbox, mail only reaches pre-verified addresses — the confirmation email to an inquirer would silently fail | `/contact/` going live |
| ~~Q18~~ | **ANSWERED 2026-08-26.** `smlcompany.ca` is verified for sending — `true`. The `None` in the earlier listing was a query artefact, not a fault | — |
| **Q19** | **CONFIRMED AS A BLOCKER 2026-08-26. Request it in `ca-central-1`, not the console's default `us-east-1` — the sandbox is per-region.** `ProductionAccessEnabled: false` — the SES account is in the **sandbox**, so mail reaches only pre-verified addresses. Confirmed with Pouya (D18) that the inquirer receipt is wanted, so production access **is** required. ~24 h AWS turnaround. **The only item on this project with external lead time** | The confirmation half of `/contact/` |
| ~~Q20~~ | **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 | — |
| ~~Q21~~ | **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 | — |
| ~~Q11~~ | **ANSWERED 2026-08-26.** Privacy-first, cookieless — Plausible or Fathom. No GA4, no consent banner | — |
| ~~Q12~~ | **ANSWERED 2026-08-26.** `https://www.linkedin.com/in/pouyalajevardi/` | — |
@@ -278,7 +297,8 @@ Nothing below can be invented. Each needs an answer from Pouya.
| 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 |
| **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`** — which by its name is a law firm's production client-database backups | **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]` |
| SES not verified or still sandboxed | Medium | Q18/Q19. A silently undelivered confirmation email looks to the inquirer like being ignored |
| **SES is in the sandbox** — confirmed, not hypothetical | **High** | Q19. Until production access is granted, every confirmation email to an inquirer fails silently. The site appears to work; the inquirer concludes they were ignored. Request access early — it is the only dependency with an external turnaround |
| **No SPF and no DMARC on `smlcompany.ca`** | **High** | Q20. Affects SES *and* the existing Google Workspace mail — Pouya's business email is unauthenticated today, which is a live deliverability problem independent of this project. Gmail and Yahoo both penalise it. Add both records before requesting SES production access; AWS also weighs domain authentication when reviewing |
## 11. Glossary
@@ -322,6 +342,231 @@ never being raised again.
# Change Log
## 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.** The `f5pu` set resolves NOERROR at the
correct names. The `3zsn` set returns **NXDOMAIN**.
- **Root cause found.** `3zsnvsjg…._domainkey.smlcompany.ca.smlcompany.ca`
resolves; 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 the `adr.smlcompany.ca` certificate, 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 to `sml-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-verification` and `google-site-verification`.
**No `v=spf1` record of any kind.**
- `_dmarc.smlcompany.ca`: **no record.**
- MX `1 smtp.google.com` and `google._domainkey` present — Google Workspace with
DKIM configured.
- `adr.smlcompany.ca` CNAME → `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-end `AGENTS.md` ritual.
- `.claude/settings.json` — thinking on, high effort, and a read-deny on
`.env*` and `aws-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.
+50
View File
@@ -39,6 +39,56 @@ If a page needs a fact you do not have:
Read the Forbidden table in §4 before writing any statistic, number, or
superlative.
## How work is executed here
Pouya is the architect. He makes the decisions and hands you the task. **You
implement, then you adversarially review your own work before calling it done.**
This is the standing agreement — it applies to every substantial change without
being restated in the prompt.
**Run `/build <task>` for any substantive change.** It encodes the loop:
1. **Plan** — read `AGENTS.md` (including §12 Standing Reminders, and surface
anything live), read the governing specs, name the decisions the task touches,
and **stop and ask on any conflict**. A blocked build is a correct build.
2. **Implement** — following the conventions below.
3. **Adversarial review** — invoke `adversarial-reviewer` and `claims-auditor` in
parallel on the diff.
4. **Resolve** — fix each finding or decline it with a stated reason. Re-review
material fixes.
5. **Verify** — run the checks. Never report a check as passing that you did not
run.
6. **Record** — append the `AGENTS.md` Change Log entry.
`/review` runs phase 3 alone. `/wrap` runs phase 6 at session end.
**Think deeply before acting.** Extended thinking is on by default for this
project (`.claude/settings.json`), and `/build` and `/review` request it
explicitly. The planning and review phases are where it earns its cost — a defect
reasoned out before implementation is far cheaper than one found after.
### Why the review is adversarial, and what would break it
Two rules make the difference between a review and a rubber stamp:
**Do not brief the reviewers on why your work is correct.** Give them the diff
and the specs, nothing else. Your rationale anchors them, and an anchored
reviewer produces agreement rather than review. They must form an independent
view from the artefact — that independence *is* the mechanism.
**The reviewers are instructed to treat uncertainty as a defect.** They will
sometimes be wrong, and that is the intended trade. Explaining why a finding is
mistaken costs minutes; a missed defect on a licensed professional's public
marketing page costs considerably more. Do not read a finding as an accusation,
and do not argue a reviewer down — either fix it, or record the reason you
declined it so a later reader can see the judgement was made rather than missed.
**Two reviewers, because they catch different things.** `adversarial-reviewer`
reads the code. `claims-auditor` reads the copy against the §4 register and knows
nothing about whether the code is elegant. A generic reviewer consistently
under-weights the professional-conduct check, which is the highest-stakes failure
mode on this project — so it gets its own pass.
## Commands
```bash
+10
View File
@@ -34,6 +34,16 @@ appears in the verified register in `AGENTS.md` §4.** This is a licensed
professional's public marketing surface, and the site this replaces contained
fabricated credentials.
## How work is done here
Pouya decides; Claude Code implements and then adversarially reviews its own
work. Run **`/build <task>`** for any substantive change — it plans, implements,
runs two independent review agents on the diff, resolves the findings, verifies
the build, and records the session in `AGENTS.md`. `/review` runs the review pass
alone; `/wrap` closes a session.
Full protocol and prompt guidance: `docs/08-execution-protocol.md`.
## Deployment
Pushes to `main` build and deploy automatically via
+47 -2
View File
@@ -106,8 +106,53 @@ SES on submission:
of the no-retainer language, and a link to the privacy policy. This email is
the reason the form beats a `mailto:` link.
SES must have SPF, DKIM, and DMARC aligned on `smlcompany.ca` or these land in
spam. The guide covers domain verification; **DMARC needs confirming (Q3)**.
**Email authentication — verified 2026-08-26, and it is not in place.**
A DNS query of `smlcompany.ca` found **no SPF record and no DMARC record**. Mail
is on Google Workspace (MX `1 smtp.google.com`) with Google DKIM configured, and
the SES domain identity reports verified for sending — but neither SPF nor DMARC
exists.
**What is already in place** (Namecheap DNS and the SES console, both inspected
2026-08-26):
| Record | Status |
|---|---|
| SES DKIM — `3zsnvsjg…`, `jejgp7na3…`, `xpiwyftpo…` `._domainkey` | **Live.** Matches SES exactly. Never delete |
| SES DKIM — `f5puwearz…`, `jdue2r22c…`, `kznn3cklv…` `._domainkey` | Orphans from an earlier verification. Inert. **Leave them** — deleting the wrong three breaks DKIM |
| `google._domainkey` TXT | Google Workspace DKIM. Never delete |
| Two CNAMEs → `jkddzztszm.acm-validations.aws` | ACM certificate validation. **Never delete** — breaks HTTPS at the next renewal |
| `adr` CNAME → `d26v23dhgsp2ta.cloudfront.net` | The site |
| Custom MAIL FROM | **Not configured.** Optional; would add SPF alignment |
Add both of these; neither conflicts with anything above:
| Host | Type | Value |
|---|---|---|
| `@` | TXT | `v=spf1 include:_spf.google.com include:amazonses.com ~all` |
| `_dmarc` | TXT | `v=DMARC1; p=none; rua=mailto:info@smlcompany.ca; fo=1` |
A domain may publish **only one** `v=spf1` record, so both senders go in one
string. Namecheap TXT values take **no surrounding quotes** — quoting them stores
the quotes literally and breaks the record.
**Correction to an earlier version of this spec.** SPF is not what authenticates
SES here. Without a custom MAIL FROM domain, SES uses an envelope sender at
`amazonses.com`, so its SPF pass is not *aligned* with `smlcompany.ca` and does
not satisfy DMARC. **SES satisfies DMARC through DKIM alignment** — that is what
the six CNAMEs above are doing, and it already works. The SPF record's real job
is authenticating **Google Workspace** mail, which currently has no SPF at all.
`include:amazonses.com` is harmless and becomes useful if a custom MAIL FROM
domain is configured later.
Start DMARC at `p=none` — it collects reports without affecting delivery. Move to
`quarantine` only after reports come back clean. Reports arrive as XML
attachments, so filter them in Gmail, or drop `rua=` entirely and accept having
no visibility.
**Do not delete the ACM validation CNAMEs.** They are how the certificate for
`adr.smlcompany.ca` auto-renews. Removing them breaks HTTPS at the next renewal
— silently, months later (Q20).
Failure handling: SES failure must never lose the submission. Write to DynamoDB
first, then send. A dead-letter queue on the Lambda, and a CloudWatch alarm on
+127
View File
@@ -0,0 +1,127 @@
# 08 — How we work
`AGENTS.md` D17. Pouya architects. Claude Code implements, then adversarially
reviews its own work. This is a standing agreement encoded in the repository, so
it does not need restating in every prompt.
---
## The short version
```
/build the /med-arb/ page
```
That single command runs: plan → implement → adversarial review → resolve →
verify → record. It requests deep reasoning, reads `AGENTS.md` and the specs,
stops if the task conflicts with a locked decision, invokes two independent
reviewers on the finished diff, resolves what they find, runs the checks, and
appends the Change Log entry.
You do not need to ask for thinking, for review, or for the record to be updated.
Those are the agreement, not the request.
## The three commands
| Command | Does |
|---|---|
| `/build <task>` | The full loop. Use for every substantive change |
| `/review [scope]` | The review pass alone, on the working tree or a named scope. Reports; fixes nothing without your say-so |
| `/wrap` | End of session — updates `AGENTS.md` under its constitution and leaves the tree clean |
## The two reviewers
Both are defined in `.claude/agents/` and run in parallel on the diff.
**`adversarial-reviewer`** reads the code: correctness and edge cases,
accessibility, crawlability, performance budgets, security, and whether a
materially simpler correct version exists.
**`claims-auditor`** reads the copy against `AGENTS.md` §4 and nothing else. It
extracts every factual assertion — credentials, roles, numbers, languages,
locations, capabilities, and the JSON-LD — and traces each to the Verified table.
Anything untraceable is reported and does not ship.
It is a separate agent on purpose. A general-purpose reviewer will happily
approve elegant code containing a claim that should never have been published,
because professional-conduct compliance is not what it is looking at. On this
project that is the highest-stakes failure mode, so it gets its own pass.
**Both are instructed to treat uncertainty as a defect.** They will sometimes be
wrong. That is the intended trade: explaining why a finding is mistaken costs
minutes, and a missed defect on a licensed professional's public marketing page
costs a great deal more.
## The rule that makes it work
**The reviewers are given the diff and the specs — never the implementer's
explanation of why the work is correct.**
A rationale anchors the reviewer. Told why something is right, a reviewer looks
for confirmation and finds it; given only the artefact, it forms an independent
view. That independence is the entire mechanism. Every other detail of this
protocol is adjustable. This one is not.
---
## Writing a task
The commands carry the process, so your prompt only needs to carry the decision.
Short and specific beats long and hedged.
**Good:**
```
/build the /med-arb/ page per docs/01-architecture.md
```
```
/build step 5 of the build order — the practice index and all six area pages
```
```
/build the intake form on /contact/, per docs/05-backend-spec.md.
Booking stays parked — reserve the slot, render nothing.
```
**When you are making a decision rather than assigning work**, say so plainly and
let it record the decision:
```
Decision: drop the /for-parties/ page. The plain-language audience can be
served by a section on /mediation/ instead. Update AGENTS.md and the
architecture spec, then tell me what else this affects.
```
**When you want an opinion before committing**, ask for one — do not ask for
code:
```
Before building /fees/: read docs/07-fees.md and tell me what a referring
lawyer would find missing from that page. Do not write anything yet.
```
## What it will do without being asked
- Stop and ask when a task conflicts with a locked decision, or needs a fact it
does not have. A blocked build is a correct build.
- Leave `TODO(pouya)` in the source and a numbered question in `AGENTS.md` §9
rather than inventing a fact or softening a claim into something defensible.
- Surface `AGENTS.md` §12 Standing Reminders at the start of a substantial
session — including R1, your standing instruction about the licensure wording.
- Refuse to publish a claim that is not in the §4 Verified register.
- Append a Change Log entry covering decisions and plans, not only code.
## What to watch for
**A review that finds nothing.** Possible, but it should be rare, and it must
list which lenses were applied and what was checked. "Looks good" is not a
review — send it back.
**Findings declined without a reason.** Declining is legitimate. Declining
silently is not; the reasoning belongs in the Change Log so a later reader can
see that judgement was exercised rather than the finding missed.
**A check reported as passing that was not run.** "Should pass" is not a result.
If a build, a Lighthouse run, or a JavaScript-disabled render was not actually
executed, it must say so.