Two of Pouya's rulings of 2026-08-31, committed as one tree at his instruction
("as-is") because AGENTS.md Change Log entry (ai) covers both and splitting it
would mean rewriting the record rather than moving it. This is a deliberate
departure from one-logical-change-per-commit, recorded here rather than left to
be inferred.
THE EYEBROW. `--text-eyebrow: 0.8125rem` added to tokens.css; `.eyebrow` in
global.css retargeted to it. One edit site, which is what the design system
claimed. 13px is not a rung on the modular ladder — it sits between xs and sm
deliberately, because uppercase mono at 0.18em tracking reads smaller than it
measures. `--text-2xs`'s comment stopped calling itself the eyebrow floor.
The rendered sweep (22 pages × 2 widths, 838 mono elements measured over CDP,
not grepped) found exactly one escape and one deliberate override:
- h2.footer-heading, 176 instances — an ESCAPED EYEBROW. Five declarations
byte-identical to `.eyebrow`, differing only in colour. Consolidated to
`class="eyebrow footer-heading"`; the scoped rule is now colour + margin.
The colour is load-bearing, not decorative: `.eyebrow`'s own `--text-meta`
on ink is 3.07:1 and fails.
- span.eyebrow.brand-tagline, 21 instances — HELD at `--text-2xs`. Measured:
at 13px the header grows 81 → 83.4px while `--header-h` is pinned at 81 and
drives `scroll-padding-top`; and with a seventh nav item the CTA lands past
`.header-inner`'s content edge by 42px at 1216, 18px at 1240, 26px at 1280
and 1440. Document overflow is 0 in all of those, so no page-level check
can see it. Insights is that seventh item.
Everything else mono-uppercase is a genuinely different component and was left:
the 0.06em `--tracking-wide` family, the 14px mixed-case designation strip, the
/bio/ print sheet, /contact/'s form labels.
Measured after: contrast unchanged on all 817 instances (11.09 / 8.11 / 5.47 /
5.01:1, all pass at 13px, which is still normal text and needs 4.5:1). Zero
document overflow and identical header geometry at 15 widths. Two eyebrows gain
a line below 414px — /'s hero, already wrapping at 320px before this, and
/insights/'s empty state at 320px only. Accepted, not re-tuned.
docs/02's type spec moved 11–12px → 13px and now enumerates the three 11px
carve-outs instead of implying there are none. The /type-scale/ proof sheet
(d) asked for no longer exists — deleted at build step 2 — so the spec prose is
the proof sheet now.
THE TTL. backend/intake/handler.mjs CONFIRMED to match `AttributeName=ttl`: it
writes `ttl` as a Number, in epoch seconds, at RETENTION_MONTHS = 24. Nothing
needed changing for the enable command.
Removed `|| 0` from the TTL computation. DynamoDB does not expire an item whose
TTL is more than five years past, so `ttl: 0` meant RETAINED FOREVER while
/legal/privacy/ promises deletion — a fallback whose failure mode was the exact
inverse of the claim it was protecting. Unreachable in practice, which is why
it would never have been noticed. A bad value now fails the write.
§7 records TTL as DISABLED at first verification, so the privacy policy's
automatic-deletion promise was unbacked from the moment it was written. §7 is
the only place that status lives; docs/05 and docs/06 carry the constraint and
cite §7, because round 2 of review caught this change set reproducing the SES
DKIM defect — five copies of a status that is about to be re-stamped.
Added, and these are the gate: `TODO(pouya)` on /legal/privacy/'s retention
section, §9 Q60, §12 R19. The page does not publish a period, it asserts a
MECHANISM — deleted by the database rather than by someone remembering — and
nothing in the toolchain can see that. check:claims is frozen with no pattern
for it and deploy does not read docs/06. The copy was NOT softened: it is about
to be true, and weakening a privacy commitment to make it defensible is the
move the rules forbid. What was missing was the gate, not the caveat.
Reviewed by adversarial-reviewer, two rounds (D19 cap), 15 findings, all
accepted, none declined. claims-auditor did not run — D20. Round 1's findings
were almost entirely in prose written that session, and round 2's blocking
finding plus its sharpest should-fix were both defects in round 1's own fixes.
public/pouya-lajevardi-bio.pdf is deliberately NOT in this commit. It was
regenerated and reverted: /bio/'s eyebrow sits inside `.no-print`, so the sheet
has no eyebrow at all, and `cmp -l` showed exactly 10 differing bytes, all in
/CreationDate and /ModDate.
Gates, every one read as an exit status and none through a pipe: check 0,
build 0 (22 pages), check:claims 0, og:proof 0, check:intake 0, lint 0,
minifier tripwire clean, TODO in dist 0. Lighthouse run twice with identical
output: perf 99 on / and 100 on the other 21, a11y 100, best practices 100,
SEO 100 on every indexable page, CLS 0.000, LCP 1.50–2.03s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
| Intake table | `adr-intake-submissions` (DynamoDB, ca-central-1) `[verified 2026-08-26]`. ⚠️ **TTL WAS `DISABLED` AT FIRST VERIFICATION — Pouya ran `describe-time-to-live` on 2026-08-31 and it returned `DISABLED`.**`/legal/privacy/` has stated since build step 10 that a record is *"deleted automatically by the database rather than by someone remembering to do it"* after 24 months, so **that promise was unbacked from the moment it was written, and it stays unbacked in this record until a test record is confirmed deleted** — the handler wrote the attribute and nothing on the table consumed it. This is the Q22 shape on a public privacy commitment rather than on a deploy control: a documented mechanism that did not exist. **Pouya initiated enabling the same day with `AttributeName=ttl`** ``[assumed — initiated 2026-08-31 on his report; the status was NOT re-read to this record, and DynamoDB reports `ENABLING` for a period before it settles]``**, which is the attribute the handler writes** — `backend/intake/handler.mjs` writes `ttl` as a Number in **epoch seconds** at **24 months** (`RETENTION_MONTHS = 24`, added to `getUTCMonth()`), matching `docs/05` §Retention and the `ttl` row of its item table `[verified 2026-08-31 — read from the handler, not recalled]`. ⚠️ **`ENABLED` IS NOT YET STAMPED HERE, DELIBERATELY.** Enabling was initiated, not confirmed to this record, and DynamoDB reports `ENABLING` for a period before it settles — so the status must be re-run and stamped. **And `ENABLED` would prove the setting, not the behaviour:** `docs/06`'s cutover item requires a test record written with a near-future `ttl` and confirmed to disappear, and it is not ticked by reading this row or the handler code |
| 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]` |
@@ -772,6 +772,7 @@ Nothing below can be invented. Each needs an answer from Pouya.
| # | Question | Blocks |
|---|---|---|
| **Q60** | ⚠️ **IS DYNAMODB TTL `ENABLED` ON THE INTAKE TABLE, AND HAS A TEST RECORD BEEN OBSERVED TO DISAPPEAR?** Two halves, and neither substitutes for the other. `/legal/privacy/` does not merely publish a retention *period* — it asserts a **mechanism**: *"the record is deleted automatically by the database rather than by someone remembering to do it"*. `describe-time-to-live` returned **`DISABLED`** when Pouya first ran it (2026-08-31; §7 holds the status and its stamp), so the mechanism was not running from the day that sentence was written. **The handler's side is already verified and is not what is being asked** — it writes `ttl` as a Number in epoch seconds at 24 months, which is the attribute name being enabled; writing the attribute is not the mechanism. **Do not answer this from the handler code, and do not answer it from `ENABLED` alone:**`ENABLED` proves the setting, and only a record written with a near-future `ttl` and watched to vanish proves the behaviour. `TODO(pouya)` sits on the retention section of `src/pages/legal/privacy.astro`; `docs/06`'s cutover checklist carries both halves; §12 R19 keeps it surfacing. **Why this is a numbered question and not only a checklist line:**`CLAUDE.md` requires a `TODO(pouya)` plus a §9 row when a page needs a fact the repository does not have, and this page needs one — a cutover checklist fires once, at cutover, and §9 is what a person editing this page reads. Raised by `adversarial-reviewer` round 2, 2026-08-31 | **`/legal/privacy/` going public.** Nothing else — no other page states the mechanism, verified by sweeping `dist/` for the retention vocabulary and reading each hit in context |
| ~~Q59~~ | ✅ **RULED AND CLOSED 2026-08-31 — Pouya. OVERTIME RUNS FROM THE SESSION CAP**: the fourth hour of a half day, the seventh of a full day. Not the billed envelope. `/fees/` shipped at build step 9 on this ruling and `docs/07` carries it in full. ⚠️ **THIS ROW NAMED A CONSTANT THAT NO LONGER EXISTS** — `FEES.mediation.overtimeStartsAfterSessionHours` was deleted the same day as dead data: nothing read it, so reversing it would have changed nothing and failed nothing, which is Q22's shape at constant scope. **Where the ruling actually lives:** the trigger is rendered on `/fees/` from `halfDay.hours` / `fullDay.hours`, and `FEES.mediation.reservation` carries the half that publishes as prose. Found by `adversarial-reviewer` round 2 — §9 is what a later implementer reads to find where a ruling is recorded, so pointing it at a deleted identifier is the same defect one layer up. ⚠️ **AND THE RULING CAME WITH A SECOND HALF THAT ANSWERS THE ARITHMETIC ANOMALY THIS ROW EXISTED TO ESCALATE, WHICH THE TRIGGER ALONE COULD NOT.** His words: *"a full day reserves the day; half-day overtime is subject to availability."***The full-day fee buys the DAY, not six hours of it.** Read as a price comparison the table below says the full-day rate is never the cheaper choice; read knowing what each fee reserves, the $2,000-narrowing-to-$500 spread is the price of certainty rather than a defect. The sentence is `FEES.mediation.reservation` and it publishes **adjacent to the overtime row**, not as a footnote — the same structural rule as `PROCESS_FRAMING` beside the five timings under Q43, because a reader who takes the number and skips the framing has read a different offer. **THE ANOMALY IS NOT CLOSED AND STAYS ON §12 R5.** The gap is in D14's own figures — the half-to-full step is $2,000 against $1,500 for three hours of overtime — and the reservation point explains what it buys without removing it; the spread is largest at three to five hours, which is the band a half-day booking actually overruns into. `docs/07` §Recorded dissent carries the table for the 12-month review. **The original question, kept because the shape of it is the lesson.***Where does the overtime hour start?*`docs/07`'s card carried *"Overtime, per hour — $500"* and had never said what it was overtime **to**. Q58's ruling settled the two allowances and did not reach this; Q15–Q17's answer records the rate with no trigger. The two candidates were the session cap (3 h / 6 h) and the billed envelope (5 h / 9 h), and this repository was barred from picking one — a fee term is a fact we do not have, and `CLAUDE.md`'s rule for that is a question, not an inference. **It cost two strikes to hold that line:** a first pass at `docs/07`'s Q58 note asserted the session cap as applied fact and `adversarial-reviewer` struck it in the change set that wrote it; a round-1 fix then published the $500 rate on `/for-parties/` beside an unambiguous *"up to 3 hours"*, which **defines the trigger by adjacency** — nothing else on the page is a quantity it can attach to — and round 2 struck that too. Both strikes were right, and the ruling supplied the value they were waiting for | ~~`/fees/`, `/for-parties/`~~ — both now unblocked and shipped |
| ~~Q58~~ | **RULED 2026-08-31 — `hours` IS THE SESSION, AND THE AMBIGUITY WAS IN `docs/07` RATHER THAN IN ANY COPY. Pouya owned it in terms:***"the ambiguity is mine… My `docs/07` wording said "up to 3.5 h, including 2 h preparation", which is genuinely unclear: 3.5 was meant as the TOTAL time committed, of which 2 is preparation — leaving 1.5 hours in the room. Your arithmetic caught it: if prep sat inside, 3.5 and 7 wouldn't be exactly 2×, because preparation doesn't scale with session length. The intended reading is the market's, and my wording obscured it."***THE CORRECTED CARD, in his words:***"Half day — up to 3 hours of session. Fee includes up to 2 hours of preparation. $2,000. Full day — up to 6 hours of session. Fee includes up to 3 hours of preparation. $4,000."* His reason for 3 and 6: *"the market convention — Patey and Zuber both publish "all or part of 3 hours" and "all or part of 6 hours", and those were the comparables the rate was set against. Selling 1.5 hours of room time as a half day would be an outlier nobody would recognise."* ⚠️ **ONE PROVENANCE NOTE, and it is R14's rule rather than a doubt about the ruling:**`docs/07`'s committed extract records Patey and Zuber at **3 h** and **6 h** but **does not carry the phrase "all or part of"** — so `docs/07` cites the hours, not the phrase, and the phrase is not attributed to them anywhere in the repository. The hours corroborate the ruling on their own, and ADR Chambers' roster rate in the same table is the clearest corroboration of the *shape*: *"one half hour of preparation time per party **and** up to three hours of mediation"* — preparation counted separately from a three-hour session. **APPLIED:**`docs/07`'s two card rows and its §All parameters confirmed (which prescribed the flat *"including 2 hours"*, the form `/for-parties/` then shipped); `FEES.mediation.*.hours` 3.5 → 3 and 7 → 6 with the semantics in the constant's doc comment; `/for-parties/` now states the session length interpolated from the constant and the preparation allowance **as a cap**. **The preparation allowance is CAPPED and must be published as capped** — *"including **up to** 2 hours"*, never the flat form and never "preparation included". **`/fees/` is UNBLOCKED for build step 9.** **The question as raised is preserved below.****DOES `hours` IN THE MEDIATION RATE CARD MEAN THE LENGTH OF THE DAY, OR THE BILLED ENVELOPE INCLUDING PREPARATION?**`docs/07-fees.md` reads *"Half day — **up to 3.5 h, including 2 h preparation**"* and *"Full day — up to 7 h, including 3 h preparation"*. Taken at face value, 3.5 is the whole billed envelope and the **time in the room is 1.5 h** for a half day and **4 h** for a full day. **Against that reading:** 3.5 and 7 are exactly 2×, which they would not be if preparation sat inside them (1.5 vs 4 is not 2×). So either the card's wording is wrong in the one document that is the authority on money, or `FEES.mediation.*.hours` in `src/data/site.ts` does not mean what a page would naturally publish it as. **This was one sentence from shipping.** A draft of `/for-parties/` answered *"What happens on the day?"* with *"A half day is about 3.5 hours"* — the envelope presented as the day, to the reader least able to check it. The sentence was removed rather than resolved by inference; the page now says only that you book a half day or a full day. **What is needed:** one line from Pouya saying which the 3.5 and 7 are. Then `docs/07`'s two rows or `site.ts`'s field gains the correction, and the semantics go in the constant's doc comment (a warning is there now). **`/fees/` at build step 9 publishes this table and cannot be built without the answer.** Raised by `adversarial-reviewer`, 2026-08-30 | **Nothing.** No page stated a duration while the question was open — the one draft sentence that did was removed rather than reconciled, which is why the ruling had nothing to correct in public copy |
| ~~Q57~~ | **CLOSED 2026-08-31 — NO SEVENTH UNDERTAKING, AND THE PAGE IS COMPLETE AS IT STANDS.** Pouya: *"`/process/` stating when conflicts are run and what the check needs is complete. A reader assumes the outcome, and the obvious undertaking ("if a conflict is found I decline") adds nothing a reader doesn't already infer. Your restraint was right — §4's gate held. Record it closed rather than open, so it stops appearing in the live list."***So this is a closure, not a deferral:** the answer is that the page says nothing further, which was one of the two outcomes the question named. §4 gains no seventh conduct undertaking and `CONDUCT_UNDERTAKINGS` still holds six. **APPLIED:** the `TODO(pouya)` is removed from `src/pages/process.astro` §Conflicts and replaced with the ruling, so a later reader finds the decision where the question was rather than an open marker; the file header's *"see the TODO below"* is corrected to cite this closure. `src/` now carries **zero** live `TODO(pouya)` markers. **The question as raised is preserved below.****WHAT HAPPENS WHEN A CONFLICTS CHECK TURNS SOMETHING UP?**`/process/` §Conflicts ships saying **when** the check runs (the intake call, before anything is agreed) and **what it needs** (full legal names of the parties, the parent or affiliate actually behind the dispute, counsel on each side). It stops there, and the stop is deliberate: **any sentence naming the outcome is a SEVENTH conduct undertaking**, and §4's gate for that class is one line — *"an undertaking may be published only where Pouya has made it in terms. Not 'would obviously agree to', not 'follows from the process' — said."**"If a conflict appears I decline the appointment"* is exactly what that gate refuses to let this repository infer, however obvious it looks. **What is needed:** one sentence from Pouya, in his words, or a decision that the page says nothing further. `TODO(pouya)` sits at `src/pages/process.astro` §Conflicts. Raised at build step 6, 2026-08-30 | **Nothing.** The section shipped accurate and unchanged; what closed is whether anything more was owed |
@@ -928,6 +929,7 @@ never being raised again.
| ~~R16~~ | ✅ **DISCHARGED 2026-08-31 — build step 9, which is the trigger this row set. The one-page PDF exists, is committed at `public/pouya-lajevardi-bio.pdf`, and `/about/` links it.** Q45's two open sub-decisions are both taken, and the second one is what makes the first safe. **(a) Generated at build, or authored once? NEITHER — and the third option answers this row's actual objection.** The bio is a PAGE, `src/pages/bio.astro`, so every line of it is reviewed by the same apparatus as every other page: `astro check`, `npm run check:claims` on the built HTML, the per-step code review, and the cutover claims pass. `npm run bio:pdf` then RENDERS the PDF from the built page through Chrome, with **no new dependency** — `chrome-launcher` is already a devDependency because Lighthouse needs it. That is aimed squarely at what this row said the problem was: *"It is the one artefact class this project's review apparatus cannot reach… a PDF circulated with an appointment proposal is read once, by the reader who matters most, and never seen by a reviewer again."* A rendering of a reviewed page is back inside the apparatus. **It is deliberately NOT part of `astro build`** — CI has no Chrome, and a build step that cannot run in CI is Q22's shape again — so the PDF is committed, which R14 wants anyway. **(b) Does it carry anything the site does not? NO, and that is the answer that avoids the §4 question this row flagged.** Every line renders from `CREDENTIALS`, `ROLE`, `BOUTIQUE`, `PRACTICE_AREAS`, `FEES` and `CONTACT`. No matter list — which this row correctly said *"would collide with §4 Forbidden directly"* — no referees, and no figure that is not on `/fees/`. The fee summary IS there, because this row's own reasoning is that an appointment proposal needs the rate card as much as the bio. ⚠️ **`npm run bio:pdf` ASSERTS ONE PAGE and writes nothing if the count is wrong** — counted from the PDF's own page objects, cross-checked against the page tree's `/Count`. A one-page bio that silently becomes two is invisible from the source, because it depends on the print stylesheet, the paper size and how much §4 has grown. **And reading the rendered PDF caught a real breach the source review had not:** the opening sentence read *"I act as a neutral in commercial disputes — as a mediator, as an arbitrator in commercial matters…"*, whose leading clause scoped **mediation**, which Q56 leaves unscoped deliberately. `/bio/` is `noindex` and out of the sitemap: it is a condensed duplicate of `/about/` and `/fees/`. **What is NOT discharged:** nothing in the build regenerates the PDF. It must be re-rendered and re-committed whenever `/bio/`, §4, the rate card or the print styles change, and `docs/06`'s cutover checklist carries that | 2026-08-28 | It is the one artefact class this project's review apparatus cannot reach. A web page is re-reviewed by every audit and re-checked by every build; a PDF circulated with an appointment proposal is read once, by the reader who matters most, and never seen by a reviewer again |
| R17 | **THE DEPLOY KEY'S FIRST ROTATION IS DUE 2026-11-26, AND THAT DATE IS THE WHOLE ROW.** Access key for `adr-sml-deploy` created **2026-08-26**; D3 commits to **quarterly** rotation. Pouya's instruction when Q22 closed: *"Put the DATE in the R-series reminder, not the intention."***Rotation procedure is in `docs/06-deployment.md` §Key rotation** — create the second key, update the Gitea secrets, verify a deploy, **then** delete the first; never delete before verifying, or the next push fails with no way to authenticate the fix. **Then set the next date here (2027-02-26) rather than deleting the row.** Also re-run **all nine**`simulate-principal-policy` checks after any policy edit (they are listed and reproducible in `docs/reference/deploy-credential-verification.md` §5) — the narrowness is the control (§10), and a widened policy is invisible from inside the repo. **The key is currently UNUSED — `AccessKeyLastUsed` returns `ServiceName`/`Region` = `N/A` and no `LastUsedDate`** — because deploys still run locally via `npm run deploy`; that does not pause the clock, and an unused long-lived key is exactly the one nobody remembers to rotate | 2026-08-28 | This is the reminder D3 has been waiting for a subject since 2026-08-26. §10 records the reason it matters more here than on a normal project: the secret lives on **jointly-administered** infrastructure, and the account also holds another business's client-database backups. A static key on shared infrastructure is only as safe as its age and its scope, and **nothing in the repo, the build, or AWS will prompt anyone on either.** §7 carries the inventory; this row carries the date |
| R18 | **THE SIX `docs/reference/` EXTRACTS BEHIND `/practice/*` ARE DATED 2026-08-29, AND SIX SHIPPED SENTENCES TURN ON FACTS THAT MOVE.** Build step 5 put statute, regulation, tribunal and bill status onto public pages — sourced, but **sourced as at one day**. The volatile ones, in order of how fast they move: **(a) federal Bill C-36** — `/practice/technology/` says it *"was introduced in June 2026 and was at second reading when this page was written"*; if it receives royal assent the page is wrong about the most load-bearing fact on it. **(b) the Ontario regulation under `Electricity Act` s. 28.1** — `/practice/energy/` says it *"had not been made as of August 2026"*. **(c) the ERO 026-0853 consultation**, comment period to **12 September 2026**. **(d) the Tribunals Ontario annual report** — `/practice/insurance/` publishes FY2024-25 figures and the extract records that a 2025-26 edition was never ruled out. **(e) the SABS**, amended with effect 1 July 2026. **(f) the ADRIC National Mediation Rules**, under review by ADRIC's own committee. **(g) ADRIC's Code of Ethics** — added 2026-08-30, build step 6. `/process/` §Confidentiality quotes it verbatim from `docs/reference/adr-institution-names.md` (retrieved 2026-08-29) **with a live link to ADRIC's page**, which is what makes it checkable and also what makes a stale quotation visible. It is the slowest-moving item here — a professional code, not a bill — so it does not change the cadence; it is listed because the trigger below says "all six" and there are now seven. **THE TRIGGER: re-check all seven extracts before any cutover, and before any republish that turns on one of them — the same event trigger R10 uses.** Then re-stamp the extract. **A page that was true when it was written and is false when it is read is still a false page**, and this is the first change set on the project to put that class of fact into public copy at volume | 2026-08-29 | Six sentences, six files, one retrieval date. Nothing here fires on its own; a fact with a shelf life and no owner is exactly what §12 exists for |
| R19 | ⚠️ **DYNAMODB TTL BACKS A PUBLISHED PRIVACY PROMISE AND `/legal/privacy/` ASSERTS THE MECHANISM, NOT JUST THE PERIOD.****§7 records the status and its stamp; this row deliberately does not restate it** — one place for a service status, or the copy that goes stale is the one nobody re-reads. **THE TRIGGER, and its two halves are not interchangeable: re-run `describe-time-to-live` and confirm `ENABLED`, THEN write a record with a near-future `ttl` and confirm it actually disappears.**`ENABLED` proves the setting; only the test record proves the behaviour. Writing the attribute proves neither — the handler's side is verified and is not what this row is about. Both halves are on `docs/06`'s cutover checklist and the question is §9 Q60. Re-stamp §7 the same day, **and when you do, sweep for the copies: this fact reached five files outside §7 in one change set and had to be pulled back.** Close this row only when the test record has been observed to vanish | 2026-08-31 | **This is R9's exact shape at higher stakes.** R9 exists because the SES alarms are configured and notify nobody until one subscription is confirmed — a control that reads as covered and is not. Here the control backs a **statement to the public on a privacy policy**, which is the one class of claim this project treats as unrecoverable, and the failure is silent in both directions: nothing in the repo, the build or AWS reports that records are accumulating forever. A cutover checklist fires once; §12 is read aloud every session |
| ~~R7~~ | **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 | — |
| ~~R8~~ | **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 | — |
@@ -935,6 +937,161 @@ never being raised again.
# Change Log
## 2026-08-31 (ai) — The eyebrow goes to 13 px from one definition, and the sweep by rendering found one escaped copy of it and one deliberate override; DynamoDB TTL was `DISABLED` while `/legal/privacy/` promised automatic deletion
**Type:** design-system change (site-wide) + an operational fact recorded + a one-line handler correction.
**Scope:** Pouya's two items of 2026-08-31. (1) The mono eyebrow reads too small against the display type — raise it one step, at the single definition, and sweep for escaped copies **by rendering rather than by grep**. (2) He ran `describe-time-to-live` on the intake table and it returned `DISABLED`; confirm the handler's attribute before he re-runs the enable command, and record the state. `claims-auditor` did not run — D20. No copy changed, so nothing in §4 is touched.
---
### 1. The eyebrow: 12 px → 13 px
**Old → new.** `.eyebrow` in `src/styles/global.css` — the one definition `docs/02` gives it — moves from `font-size: var(--text-xs)` (12 px) to `font-size: var(--text-eyebrow)` (**0.8125rem, 13 px**). With `0.18em` tracking the letter-spacing follows it from 2.16 px to 2.34 px, which is the reason 1 px is visible here at all.
**`--text-eyebrow` is a new token and it is deliberately not a rung.** The scale runs 11 / 12 / 14 / 16 at the small end; 13 px sits between `--text-xs` and `--text-sm` because uppercase mono at this tracking reads a size smaller than it measures. Written as a token rather than a literal for the reason step 1's review already established once — `SiteHeader` wrote `0.6875rem` as a literal and that was finding #7 — and so that `.eyebrow` and the footer's column headings cannot drift apart again.
**`--text-2xs`'s comment was wrong the moment this landed and is corrected.** It read `/* 11 — eyebrow */` and described itself as *"the eyebrow floor docs/02 sets at 11px"*. It is now the **compressed-label** floor: the header tagline and the `/bio/` print sheet. Nothing about the eyebrow.
**`docs/02` (c).** The eyebrow rule read *"mono, 11–12 px, `0.18em` tracking"*; it now reads 13 px, states that 13 px is not a rung and why, records that there is one definition and that the footer headings carry the class rather than repeating it, and adds a line for what `--text-2xs` is now for. The scale line read *"Tokens `--text-xs` through `--text-6xl`"* and now names `--text-2xs` as the floor and `--text-eyebrow` alongside.
⚠️ **(d)'s "type-scale page" does not exist, and this is reported rather than quietly skipped.** The proof sheet at `/type-scale/` was **deleted at build step 2** — `AGENTS.md` records it in two places, and `docs/05` §314 and `docs/reference/brand-assets.md` §87 both note the deletion. There is no rendered proof sheet to bring into line with reality; `docs/02`'s prose is the type spec, and that is what (c) updated.
### 2. The sweep, by rendering — and what it judged
**Instrument.** Headless Chrome over CDP, `--force-prefers-reduced-motion`, **all 22 built pages at 1280 and 390 CSS px**, enumerating every element whose *computed*`font-family` contains `Geist Mono`**and** which is `text-transform: uppercase`**or** carries non-`normal` tracking **and** has a text node of its own. **838 rows, both revisions.** Deliberately a superset of "mono + uppercase + tracked", so an element that is eyebrow-shaped on two of the three axes could not hide. Run against `dist/`, not the dev server.
**Distinct treatments, before → after.** Counts are instances across 22 pages × 2 widths.
| `h2.footer-heading` (176) | 12 px / 2.16 | **13 px / 2.34** | ⚠️ **ESCAPED EYEBROW — consolidated into the class.** Five declarations that were a byte-for-byte copy of `.eyebrow` (family, size, weight, tracking, uppercase), differing only in colour. Raising the eyebrow would have left all four footer columns behind at 12 px |
| `span.eyebrow.brand-tagline` (21 shown) | 11 px / 1.98 | 11 px / 1.98 | **eyebrow with a deliberate, measured override — held.** It carries `.eyebrow`, so it would have followed to 13 px. The string measures **283.1 px** at 11 px; 13 px adds ~51 px to a one-row header whose true fit `docs/02` puts at **1047 px** with seven nav items. The override is a layout constraint, and its comment now says so instead of citing "the eyebrow floor in docs/02" |
| `label`, `legend`, `span.req`, `dt` on `/contact/` (46) | 11 px / 1.98 | unchanged | **different component** — form labels and definition terms, not section labels. See the batched list: they now sit 2 px below the eyebrow on the page that collects inquiries |
| `h2`, `p.sheet-strap` on `/bio/` (22) | 11 px / 1.98 | unchanged | **different component** — the print sheet, compressed because `npm run bio:pdf` asserts one page. Its print block already overrides to 7–9 pt |
| `span.visually-hidden` (12) | 11 px / 1.98 | unchanged | inherits from `label`; never rendered |
| `dt.def-name` (100), `span.pill` (36), Breadcrumbs `a`/`span` (48), `dd.credential-label` (22), `p.step-timing` (20), `span.step-n` (20), `li` (12) | 12 px / **0.72** | unchanged | **different idiom — `--tracking-wide` (0.06 em), not `--tracking-eyebrow` (0.18 em).** A third of the tracking is what separates these from the eyebrow, and Pouya named pills as legitimate |
| `p.footer-designation` (44), `span.designation-part` (8), `span.sep` (6), `p.sheet-desigs` (2) | 14 px, **mixed case** | unchanged | **different component** — the designation strip, which Pouya named as legitimate. Not uppercase, so not eyebrow-shaped |
**So: one escaped copy, one deliberate override, and every other mono-uppercase element is a genuinely different component.** The consolidation is `<h2 class="footer-heading">` → `<h2 class="eyebrow footer-heading">`×4, with `.footer-heading` reduced from seven declarations to two — the gold-on-ink colour (11.09:1) and the gap to its link list. Scoped specificity (0,2,0) beats `.eyebrow` (0,1,0), so the colour still wins.
**`.eyebrow`'s own comment changed with it.** It read *"An eyebrow is a label, never the page's heading element."* That is now too blunt to be true: **the class is the type treatment, `Eyebrow.astro` is the label component**, and a real heading may carry the class — the footer's four column headings do. What the component still enforces is the thing that mattered: an eyebrow sitting *above* a heading is never itself an `<h*>`, because that is where a skipped level creeps in.
### 3. Re-measurement (d) — every number read, none recalled
- **Size.** 796 of 817 `.eyebrow`/`.footer-heading` instances moved 12 px → 13 px; **21 unchanged at 11 px**, which is exactly the header tagline on the 21 pages that render it (`/` omits it).
- **Contrast — 0 of 817 instances changed ratio.** Grounds after: gold-l on ink **11.09:1** (412), muted on cream **5.47:1** (204), muted on `--bg-alt`**5.01:1** (112), gold-l on maroon **8.11:1** (68). All pass 4.5:1, and 13 px is still "normal" text for WCAG, so the increase cannot cross a threshold in the harmful direction.
- **Overflow and geometry.** 242 of 242 (page × width) rows joined — asserted, not assumed. **0 rows differ** on document `scrollWidth`, header height or header inner slack, and **0 elements extend past the viewport** at 320 / 360 / 390 / 768 / 1024 / 1047 / 1056 / 1216 / 1280 / 1440 / 1920 on all 22 pages, in **both** revisions. Header height 189 / 141 / 81 px, matching `docs/02`'s recorded figures.
- **Line counts — 2 of 817 changed, both accepted, and both confined to below 414 px.**`/`'s hero eyebrow `Mediation · Arbitration · Toronto` goes 1 → 2 lines **at 390**; it was **already 2 lines at 320 before this change**, so what moved is the wrap point (~360 → ~400), and it breaks at a `·`. `/insights/`'s `Nothing published yet` goes 1 → 2 **at 320 only** — an empty-state label on a `noindex` page that is out of the nav and disappears at two published articles. A second pass at the four widths `docs/02`'s ladder lists and the first pass had missed — **414, 640, 900, 1200** — returned **796 instances, 0 on more than one line, 0 overflowing**, so between them the two passes cover all fourteen widths in that table plus 1047, and nothing wraps from 414 up. Neither case was re-tuned: Pouya's brief said no re-tuning was expected, and a small-width size step would reintroduce the second eyebrow size this change exists to remove.
- **Lighthouse — unchanged, and run twice: once on the change and again on the whole set of review fixes, with identical output.** 22 pages: performance **99** on `/` alone and 100 on the other 21, accessibility **100**, best practices **100**, SEO **100** on every indexable page (six `noindex` pages read `69n` and are asserted audit-by-audit), CLS **0.000** everywhere, LCP 1.50–2.03 s. `/` stays at **2.03 s** — the extra hero line did not move it, which is consistent with the LCP element being `p.hero-lede`.
- **The bio PDF is UNCHANGED and stays at HEAD.**`npm run bio:pdf` was run and exits 0 at 1 page — but the regeneration was a no-op and re-committing it was wrong; see the review section. `/bio/`'s eyebrow is inside `.no-print`, so the printed sheet contains none.
- **Gates, all read as exit statuses and none through a pipe.**`check` 0 errors / 0 warnings / 0 hints · `build` 22 pages · `check:claims` clean, every pattern still firing on its fixture · `og:proof` 22 pages, 21 og:image targets, 20 headlines matched · `check:intake` 12 fields both sides · `lint` clean · minifier tripwire `grep -rE 'animation:[^;}]*(scroll\(\)|view\(\))' dist --include='*.css'` → no match · `TODO` occurrences in all 22 built HTML files → **0**, so Q60's marker does not ship.
⚠️ **`check:claims` must run AFTER the build and BEFORE anything writes into `public/`.** `npm run bio:pdf` writes there, and the staleness guard then refuses to run — `build → bio:pdf → check:claims` exits **2** every time. Found in review; it is an ordering trap, not a bug in the guard.
⚠️ **A defect in my own procedure, recorded because `CLAUDE.md`'s rule is what caught it.** The first before/after comparison returned **0 changes in every column — including font-size**, which reads as a clean pass. It was worthless: I had `git stash pop`ped without rebuilding, so both measurements were of the same `dist/`. The **sanity assertion — "the count of instances whose font-size changed must be non-zero" — is the only reason it surfaced**, and it surfaced because a uniform pass had been made alarming rather than reassuring. That is the *re-check "uniformly GOOD" too* rule doing exactly its job. Re-run with a state stamp printed off `dist/` before every probe:
```
state() { if grep -rqF '.8125rem' dist --include='*.css'; then echo "AFTER(13px)"; else echo "BEFORE(12px)"; fi; }
build exit=0 dist state: AFTER(13px)
build exit=0 dist state: BEFORE(12px)
build exit=0 dist state: AFTER(13px)
```
Then, and only then: `796 12px -> 13px`, `21 unchanged at 11px`.
**Sweep commands, per R8 — the claim is the output, not the recollection.**
The `git grep` is what located the candidates; **the rendering is what judged them**, and the two disagree in the way the vocabulary rule predicts — `git grep` cannot see that `h2.footer-heading` computes to the identical five properties as `.eyebrow`, and it cannot see that `.brand-tagline`*is* an eyebrow whose size is overridden two hundred lines away from the class it overrides.
---
### 4. DynamoDB TTL — the setting was off while the page promised it
**(a) The handler's attribute is confirmed and Pouya can proceed.** `backend/intake/handler.mjs` writes **`ttl`** — the exact `AttributeName=ttl` he is enabling — as a DynamoDB **Number** (`ttl: { N: String(ttl) }`) in **epoch seconds** (`Math.floor(Date.UTC(…) / 1000)`) at **24 months** (`RETENTION_MONTHS = 24`, added to `getUTCMonth()`, whose month overflow is well-defined). That matches `docs/05` §Retention and the `| ttl | epoch seconds |` row of its item table. **No name or unit mismatch — nothing to change before the enable command**`[verified 2026-08-31 — read from the handler]`.
**(b) One thing was wrong in that computation and it is fixed.** The value was `Math.floor(…) || 0`, and **the fallback's failure mode is the inverse of the promise it serves**: DynamoDB does not expire an item whose TTL is more than five years in the past, so a `ttl` of `0` means **retained forever** while `/legal/privacy/` promises deletion. It was unreachable — `Date.UTC` cannot return `NaN` from a valid `Date` — which is precisely why it would never have been noticed. Removed, with the reason recorded in place: an uncomputable value must now fail the `PutItem` loudly, and because the write is the point at which a submission becomes *accepted*, a rejection redirects to `/contact/could-not-send/` and nothing is silently kept.
**(c) §7 records the state, and deliberately does not stamp `ENABLED`.** The `Intake table` row now carries: `describe-time-to-live` returned **`DISABLED`** at first verification on 2026-08-31; **the automatic-deletion promise on `/legal/privacy/` was therefore unbacked from the moment it was written** — the handler wrote the attribute and nothing on the table consumed it; enabling was initiated the same day with `AttributeName=ttl`. **`ENABLED` is not stamped because it was not verified to this record**, and DynamoDB reports `ENABLING` for a period before it settles. Re-run the command and stamp it.
**This is the Q22 shape on a public privacy commitment rather than on a deploy control** — a documented mechanism that did not exist — and it is the second time on this project that the thing to check was the *setting* rather than the *code that assumes it*.
**(d) The checklists already carried the test-record check; the stale half of them is what needed fixing.** `docs/06`'s cutover item already required `TimeToLiveStatus: ENABLED` by command **and** a record written with a near-future `ttl` and confirmed to disappear — so nothing was added there. What both `docs/06` and `docs/05` said and no longer say is *"§7 does not record that it is"*: §7 now records it, and both items now state that the setting was measured **off**, and that **`ENABLED` proves the setting while only the test record proves the behaviour**. Both halves, or the item stays open.
**No new §9 question — D19.** The TTL verification gates cutover and `docs/06` already carries it as a blocking item, so a numbered question would add a second place to keep true without gating anything the checklist does not.
---
### 5. Review — `adversarial-reviewer`, two rounds (D19/D20)
**`claims-auditor` did not run — D20.** No copy changed in this change set: the diff is CSS tokens, four `class` attributes, comments and specs. `npm run check:claims` is the per-step claims control and it ran clean.
**Round 1 returned one blocking and eight should-fix findings. All nine were accepted; none was declined.** Every central claim was re-verified here before acting on it, per *don't take an agent's result at face value*.
**BLOCKING — `npm run check:claims` was exiting 2 on the working tree, and the sequence that causes it is one I introduced.** `scripts/check-claims.mjs`'s staleness guard refuses to run when any source file is newer than `dist/`, and `npm run bio:pdf` writes into `public/`**after** the build — so `build → bio:pdf` guarantees the guard fires, and under D20 that script is the only per-step claims control there is. It had passed earlier in the session because I ran it before `bio:pdf`; the tree I would have committed could not have run it. A second consequence the finding caught: `dist/pouya-lajevardi-bio.pdf` still held HEAD's PDF while `public/` held the new one, so my statement that `dist/` was current with the diff was false for that file. Resolved by the PDF revert below, then rebuild; `check:claims` now exits **0**.
⚠️ **AND THE PDF SHOULD NEVER HAVE BEEN REGENERATED. I read a changed digest and concluded the content had changed; it had not.**`cmp -l` reports **exactly 10 differing bytes**, all inside `/CreationDate` and `/ModDate`. It could not have changed: `/bio/`'s only `class="eyebrow"` sits inside `<div class="no-print">`, and the built print rule is `.no-print{display:none!important}` — **the printed sheet contains no eyebrow at all**, and the sheet's own labels (`.sheet-strap`, `.block h2`) use `--text-2xs`, untouched here. `scripts/bio-pdf.mjs`'s own header says not to re-commit it out of habit, and it was right. Reverted to HEAD; `public/` and `dist/` now hold the same `ede6385…`. **The one-page assertion passing is not evidence that a regeneration was warranted** — that was the inference I made, and it does not follow.
**The other eight, and what each corrected:**
1. **The TTL sweep had missed four places, one of which my own §7 edit made false.**`src/pages/legal/privacy.astro` said *"TTL must also be ENABLED on the table, which §7 does not record"* — §7 now records it in detail, so the comment sent the next reader to §7 with a claim that §7 is silent. `docs/05` §Retention still read *"a mechanism that runs whether anyone remembers or not"*, unmarked, 181 lines above the checklist item saying the mechanism was measured off — and §7's new row **cites that section as corroboration**, so the citation pointed at a claim the same change set knew to be unverified. Plus `docs/05`'s item table and `handler.mjs`'s `RETENTION_MONTHS` comment. All four now either cite §7 or carry the warning. **This is the vocabulary-sweep rule with the narrower blade: a sweep for the *subject* would have found all four, and I swept for the *edit*.**
2. **§7 asserted an unstamped operational fact and then contradicted it.** *"Pouya enabled it the same day"* was bolded with no stamp, and *"unbacked … until the setting was turned on"* states as fact that the promise is now backed — which the same cell denies two sentences later. Now `[assumed — initiated 2026-08-31 on his report; the status was NOT re-read to this record]`, and the promise *"stays unbacked in this record until a test record is confirmed deleted."*
3. **No §12 reminder existed for it — added as R19.** The finding's argument is that **R9 is the identical shape at lower stakes**: a configured-but-unconfirmed AWS setting that reads as covered. A §7 table cell is not read aloud each session; a cutover checklist fires once. R19 carries both halves of the trigger.
4. **`SiteHeader`'s override comment cited the wrong threshold — the most useful finding of the round.** I wrote that 13 px *"adds ~51px to a header the comment above records as fitting only above 1047px"*. The 51 px is right; **1047 px is the threshold for the row *without* the tagline, and the tagline does not render below 76 rem (1216 px)** — so at today's six items, 13 px overflows nothing at any width and the stated reason does not reproduce. An implementer who tested it would conclude the override was dead code. **It is not:** the binding constraint is the seventh nav item, which `src/data/site.ts` already carries and `showInsights` turns on at two published articles. **Re-measured here rather than taken on trust**, against `.header-inner`'s content-box edge, seven items, tagline forced to 13 px — and the instrument was made to prove its own item count and forced size before its numbers were read:
Tagline 283.1 px → 334.6 px; header 81 px → 83.4 px. **One figure the finding did not have and my re-measurement added: it does not resolve as the viewport widens** — 1440 is still +26, because the nav itself steps 480.1 → 528.1 px at 1280. ⚠️ **And document overflow is 0 in every one of those rows**: flex lets the CTA sit past the padding edge without growing the document, so a page-level overflow check cannot see this at all. *Measure the elements, not only the page* — the comment now says so, because the next person to check this will reach for the page-level number first.
5. **`--header-h` is pinned at 81 px "measured, not chosen" and `global.css` drives `scroll-padding-top` from it.** A 13 px tagline measures 83.4 px, which would have staled it silently — the skip-link would drop a keyboard user 2.4 px behind the sticky header. Named as the override's second reason.
6. **`BaseLayout`'s font-preload rationale still said 12 px.** It is the recorded reason 23,128 B of Geist Mono stays off the critical path, and the argument is explicitly size-dependent (*"a size where the fallback is metrically close"*) and ends *"revisit against real Lighthouse numbers"* — so it will be re-read, on a stale input. 12 → 13.
7. **`docs/02` was quietly ratifying 11 px uppercase-mono form labels one step below the size just judged too small.** The finding says plainly that it measured **no failure** — 5.47:1 passes, and neither this document nor WCAG sets a minimum size — so it is a consistency argument, and the reason it matters is that **the diff is what turns 11 px into a rule** a later reader can cite. My bullet was also wrong as an enumeration: `/contact/` has **two** such groups, `label`/`legend` and the direct-contact `<dl>` terms, and I named only the first. The bullet now lists three carve-outs, says they are deliberate rather than licence, and marks the `/contact/` one as an accepted size and not a ratified one. **Not changed unilaterally — it is in the batched list, because a form-label size is Pouya's call.**
8. **D19: ~31 comment lines for ~7 changed code lines, across five files.** Accepted in all five and trimmed. The sharpest instance: in `SiteHeader` the declaration is **byte-identical** before and after, so six new comment lines documented a change that did not happen to the code. Also two comments that were simply wrong — `tokens.css` claimed the token was what stops `.eyebrow` and the footer headings drifting, when `git grep -- '--text-eyebrow' -- src` returns only two lines and **the shared class is the mechanism, not the token**; and `handler.mjs` claimed a bad value would *"fail the PutItem loudly"* when `{"N":"NaN"}` is not validated client-side and the rejection would come from DynamoDB server-side. Both reasons corrected rather than deleted. `global.css`'s note was also sitting between `.eyebrow` and `.eyebrow .dot`, reading as the dot's rationale; it is now one three-line note above the rule it describes.
**Two findings recorded as out of this diff, pre-existing, and deliberately not fixed here.**
- **`/bio/` measures 152 / 112 / 82 px of document overflow at 320 / 360 / 390 with a 32 px root** — worse than any other page by an order of magnitude, and `/bio/` is absent from `docs/02`'s overflow table. The 12 → 13 px delta is **0**, so this change set neither caused nor worsened it. It is a real WCAG 1.4.10 exposure at large text settings on a `noindex` page, and it belongs to whoever next opens `/bio/`.
- **`SiteHeader.astro`'s block at line 373 says the header "stands at 137px" below 66 rem; it measures 141 px** at 1024 — the same figure `docs/02` records. Pre-existing by four pixels.
**Stopping at two rounds is D19's cap and the reasoning is stated rather than assumed:** round 2 exists because a patch written under review pressure is where the second defect lives — which round 1 demonstrated exactly, since eight of its nine findings were in prose I had written that session. Round 3 does not exist because past two rounds the manufacture rate exceeds the value of the marginal finding.
**Round 2 returned one blocking and five should-fix. All six accepted; none declined.** Round 2's own hypothesis came back clean and is worth recording as corroboration rather than as a finding: it A/B-tested the bump independently — 22 pages × 7 widths × root 16 px and 32 px, `--text-eyebrow` at `0.8125rem` against a forced `0.75rem` — and reported **zero differences across 440 comparisons**, with `--header-h` holding at exactly 81 px at every desktop width. It also reproduced every figure in the new `SiteHeader` comment exactly. So the type change itself carries no layout regression by two independent instruments, and `docs/02`'s overflow table is not invalidated.
**BLOCKING — the page that makes the promise was the one place the change set did not touch.** Every mitigation went into records: §7, §12 R19, `docs/05`, `docs/06`, two source comments. `/legal/privacy/` still shipped *"the record is deleted automatically by the database rather than by someone remembering to do it"*, and **nothing in the toolchain objects to that** — `check:claims` is frozen and pattern-based with no pattern for it, `astro check` and `og:proof` are blind to semantic truth, and `npm run deploy` has no dependency on `docs/06`'s checklist. One deploy publishes a sentence this repository's own records say was unbacked. **And `CLAUDE.md` prescribes the remedy for exactly this case, which I had not applied:** a `TODO(pouya)` in the source *and* a numbered §9 question. There was no `TODO(pouya)` anywhere in `src/` or `backend/`. Both now exist — the marker sits on the retention section itself, and **Q60** asks the question in the two halves that answer it. The copy is **not** softened: the mechanism is very likely true within the hour, and weakening a privacy commitment to make it defensible is the move `CLAUDE.md` forbids. What was missing was the gate, not the caveat. *(Verified the marker does not ship: 0 occurrences of `TODO` across all 22 built HTML files, checked without a pipe after `head` masked the first attempt's exit status.)*
**And the fix for finding S3 had itself become the SES DKIM defect — this is the one to read twice.** In pulling the TTL fact into the four places that asserted it flatly, I copied **the status and the date** into five files outside §7. `docs/05` ended up saying *"`AGENTS.md` §7 holds the state and this section does not restate it"***two sentences after restating it.** The concrete failure is the one `CLAUDE.md` records having already cost: when the test record is confirmed and §7 is re-stamped, five copies would read *"THE MECHANISM WAS NOT RUNNING"* in the present tense — two of them in the specs an operator reads **at cutover**. Every non-§7 location now carries the **constraint only** — *writing the attribute is not the mechanism; §7 records whether it is enabled* — with no status value and no date. §7 keeps the status, the date and the stamp. R19 dropped its ~200-word re-argument of §7's row and gained the instruction that matters: **when you re-stamp §7, sweep for the copies, because this fact reached five files in one change set.**`docs/05` also had a sentence that parsed backwards (*"true of the design only until the cutover item is ticked"*, which says it stops being true once ticked); reworded.
**The remaining four:**
1. **`docs/02` stated the tagline override as contingent on a future event when it binds today.** I wrote that 13 px overflows the header *"once the seventh nav item lands"* — but the `--header-h` reason is live at **six** items: the header measures 83.4 px against a token pinned at 81 px, and `tokens.css` explicitly instructs a reader to re-measure that token if header sizing changes. A reader trusting the spec would conclude the override was dead code until Insights publishes, delete it, and stale `--header-h` on all 22 pages. Both reasons are now in `docs/02`, not just `SiteHeader.astro`.
2. **`docs/02`'s "one definition" claim was false, and its own next bullet proved it.** The eyebrow *treatment* — mono + `0.18em` + uppercase — is repeated in four independent blocks (`bio.astro`×2, `contact.astro`×2), which the following bullet lists as carve-outs. Two adjacent bullets in a governing spec said opposite things. Narrowed to *one definition of the 13 px eyebrow*, with the four 11 px blocks named as deliberate repetition.
3. **D19 again, and this time against the fix rather than the change.** Round 1's finding was over-commenting; my "trim" grew `SiteHeader`'s block from 5 lines to **15**, on a declaration whose value did not change — roughly +24 comment lines across `src/` for one functional declaration, from a change set whose brief included reducing exactly that. Cut to nine lines carrying only the two measured reasons and the flex warning. Deleted *"not the widths this comment first cited"* — revision history, which D19 puts in this log and not in the source — and the rhetorical sentence the next line superseded.
4. **Two pre-existing comments in `SiteHeader` that my new one now contradicted.** The block three lines above still said restoring the tagline *"pushed the one-row header past its content box by 18px at 1024 with six items and 84px with seven"* — and **neither figure is reproducible**, because below 66 rem `.nav` takes `flex-basis: 100%` and `.header-cta` is `display: none`, so at 1024 there is no one-row header and no CTA box to be past. The same file legislates this case 180 lines lower: *"a number nobody can re-derive is worse than no number."* Struck, with the reason; the *"~285px"* stayed, because it measures 283.1. Separately, a comment at the 66 rem block said the nav gap widens *"at 76rem"* when the `@media` that widens it is **80 rem**, and the 76 rem block exists specifically to say it does not — so the file told a reader two breakpoints for one declaration, and my new comment's *"steps to 528.1px at 1280"* depends on the 80 rem one. Corrected. Also swept the eyebrow size out of `about.astro`, which still said *"the 12px eyebrow"* — round 1 fixed that number in `BaseLayout.astro` and the sweep stopped there, which is the vocabulary rule for the third time in this change set.
5. **A nested-backtick defect in §7's stamp.** `` `[assumed — … reports `ENABLING` …]` `` — the inner pair closes the outer code span, so the stamp rendered as two code spans with bare text between them, on the one row whose entire point is that it is `[assumed]` rather than verified. Round 2 ran it through the actual CommonMark parser rather than reasoning about it. Fenced with a double-backtick run.
**One factual correction to the review, and to my own earlier note.** Round 2 reported that §9 has *"exactly one open row, Q5"*. It has **five** — Q5, Q23, Q34, Q38 and now Q60 — and both its grep and my first one missed the bolded rows. It does not weaken the finding (none of them was this question), and I had made the mirror-image error in the other direction a session earlier.
**Stopped at two rounds — D19's cap, and the reasoning is stated so it does not read as laxness.** Round 2 exists because a patch written under review pressure is where the second defect lives, and this change set is the cleanest demonstration yet: **round 1's nine findings were almost entirely in prose I had written that session, and round 2's blocking finding plus its sharpest should-fix were both defects in round 1's own fixes** — one of them a textbook reproduction of the SES DKIM duplication the rule was written from. Round 3 does not exist because past two rounds the manufacture rate exceeds the value of the marginal finding.
---
## 2026-08-31 (ah) — Build steps 7a–10: the site is complete and reviewable. Lighthouse returns and its first run found three defects in itself; the OG generator discharges R15 and exposes a hole in `check:claims`; and an article auditor found a false assertion on a page that shipped two days ago
**Type:** build (four steps) + two rulings applied + two standing reminders discharged.
| `ttl` | epoch seconds — **the input to automatic deletion; see §Retention for why writing it is not the mechanism** |
**Encryption at rest** with a customer-managed KMS key. **Point-in-time recovery
on.** Table access limited to the Lambda role and one named administrative
@@ -188,6 +188,13 @@ principal.
**24 months, enforced by DynamoDB TTL.** Not a policy someone remembers — a
mechanism that runs whether anyone remembers or not.
⚠️ **WRITING THE ATTRIBUTE IS NOT THE MECHANISM.** The handler supplies `ttl`;
TTL must also be **enabled on the table**, and **`AGENTS.md` §7 records whether
it is — this section deliberately does not.** So the paragraph above is a
statement about the design and not about the running system until the cutover
item below is ticked on **both** halves: `ENABLED` by command, and a test record
observed to disappear.
Rationale: long enough to serve conflicts screening across a normal matter
lifecycle; short enough to be defensible under PIPEDA's requirement to retain
personal information only as long as necessary. Whatever number ships must match
@@ -366,7 +373,7 @@ Plausible or Fathom, cookieless, no consent banner.
- [x]**Honeypot live.** ⚠️ **The timing check is NOT implemented** — see deviation 1 above; it is unimplementable on a CDN-cached static page and would be a control that does nothing
- [ ]**Rate limit configured** — API Gateway throttling, 5 requests / 5 minutes per source IP. Not expressible in handler code; not done
- [x]**The form's own protection is the `Origin` check, not CORS** — see deviation 2. CORS on the endpoint still to be restricted for scripted calls
- [ ]**TTL set and verified by test record.** ⚠️ **THIS ONE BACKS A PUBLISHED PROMISE.**`/legal/privacy/` states that records are deleted automatically after 24 months. The handler writes the `ttl` attribute; TTL must also be **enabled on the table**, and §7 does not record that it is. Until this is verified the page promises a mechanism that may not run
- [ ]**TTL set and verified by test record.** ⚠️ **THIS ONE BACKS A PUBLISHED PROMISE, AND THE SETTING WAS MEASURED OFF ON 2026-08-31.**`/legal/privacy/` states that records are deleted automatically after 24 months. The handler writes the `ttl` attribute — epoch seconds, 24 months, confirmed against this spec `[verified 2026-08-31]` — but `describe-time-to-live` returned **`DISABLED`** when Pouya first ran it, so the page promised a mechanism that was **not running**, from the day it was written. Enabling was initiated the same day with `AttributeName=ttl`; §7 records the state and deliberately does not yet stamp `ENABLED`. **The test record is what closes this, not the status:**`ENABLED` proves the setting, a record written with a near-future `ttl` and observed to vanish proves the behaviour
- [ ] KMS customer-managed key and PITR enabled. **Neither is claimed on `/legal/privacy/`** — the page says "encrypted at rest", which is true of every DynamoDB table unconditionally, and does not mention either of these because §7 does not verify them
- [ ] Both emails send; SPF/DKIM/DMARC aligned; inbox-tested, not spam-tested
- [ ] DLQ and CloudWatch alarm configured. The handler writes to DynamoDB **before** sending mail, so a replay cannot lose a submission
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.