Files
adr-sml/src/pages/bio.astro
T
Pouya LajevardiandClaude Opus 5 02739adac9
Build and deploy / build-and-deploy (push) Failing after 4s
fix: refute (ar)'s intake finding; fix the D20 gloss class; add X-Robots-Tag on *.pdf
Pouya's rulings of 2026-09-03, in five parts.

1. THE INTAKE FORM IS NOT BROKEN. (ar) was wrong. docs/09 §7.1 verbatim —
POST /api/intake with an Origin header — returns 303 to /contact/could-not-send/
with access-control-allow-origin echoed; the same probe without Origin returns
403. A bare POST 403s BY DESIGN and §7.1 says so three lines below the probe it
prescribes: "403 means the Origin header did not arrive". The earlier finding
read a status code without reading the document that defines it. Second time in
two days. CLAUDE.md's instrument list goes eight to nine. D20 findings 12 and 19
fall with it; §7.2 (that both emails arrive) is still owed.

The correction is APPENDED as entry (as); (ar) stands unedited.

2. The privacy retention comment was stale, not a defect — superseded by his
decision to publish and confirm after launch, reading from 2026-09-04. Reworded;
the TODO(pouya) came off with the gate it enforced. The mechanism finding
survives: it was a JSX comment, stripped by Astro, so no build or deploy path
could see it. A publication gate that lives only in a stripped comment is not a
gate. §9 Q60 corrected.

3. The gloss class is fixed — 15 of the 20 D20 findings, 14 distinct edits across
9 files, under the rule "the gloss may say no more than the extract says; no new
claims, no new sources". Swept three unpublished insights drafts too, and
corrected the wrong CAA attribution at its source in docs/reference/, which is
where a fixed page re-seeds. /bio/ changed, so the committed PDF is regenerated
(89,549 B, 1 page asserted). Three findings outstanding: 10 needs a ruling, 11 is
ruled and owed via Q60, 13 needs him to have said it. R1 is not one of the twenty.

4. X-Robots-Tag cannot be done with S3 object metadata — --metadata writes user
metadata, returned as x-amz-meta-x-robots-tag, which no crawler reads. Built as
the CloudFront response-headers policy docs/06 has specified all along:
configure.mjs section 4. It needs a --apply run, not a deploy. The policy is
cloned from whatever is attached at run time and reconciled on every run, because
a response-headers policy replaces rather than merges.

5. Headshot deferred as an open non-defect. The master and the srcset ladder are
both fine; Astro passes no quality, so AVIF encodes at sharp's default 50 and is
served first.

Two review rounds, 29 findings, all resolved, none declined; stopped at two per
D19. NINE of round 2's fourteen were defects in round 1's own repairs — including
a fix that harmonised both /fees/ rows onto wording that was itself unregistered,
publishing an unsourced fee term twice where it had been once.

Gates, exit status read for each: check 0 (0 errors, 0 warnings, 0 hints),
build 0 (23 pages), check:claims 0, check:intake 0, og:proof 0, lint 0, minifier
grep exit 1, router.test.mjs 30/30. Lighthouse NOT run. Nothing deployed and
nothing applied to the distribution.

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

412 lines
15 KiB
Plaintext

---
/**
* `/bio/` — the one-page bio, and the SOURCE of the PDF. Build step 9.
* Discharges `AGENTS.md` R16 / Q45.
*
* ⚠️ **R16 LEFT TWO DECISIONS OPEN AND BOTH ARE TAKEN HERE, UNDER STANDING
* AUTHORISATION. Read them before changing anything.**
*
* **(a) Generated at build, or authored once as a designed artefact?** Neither,
* exactly — and the third option is better than both. The bio is a PAGE in this
* repository, so every line of it is reviewed by the same apparatus that reviews
* every other page: `astro check`, `npm run check:claims` on the built HTML, the
* adversarial review, and the cutover claims pass. The PDF is then RENDERED from
* this page by `npm run bio:pdf`, deterministically, with no new dependency —
* `chrome-launcher` is already a devDependency because Lighthouse needs it.
*
* That answers R16's actual worry, which was never about tooling: *"It is the
* one artefact class this project's review apparatus cannot reach. A web page is
* re-reviewed by every audit; a PDF circulated with an appointment proposal is
* read once, by the reader who matters most, and never seen by a reviewer
* again."* Making the PDF a rendering of a reviewed page puts it back inside the
* apparatus. **It is not generated during `astro build`** — CI has no Chrome, and
* a build step that cannot run in CI is the Q22 shape again.
*
* **(b) Does it carry anything the site does not? NO.** Every line here renders
* from the same constants as the pages: `CREDENTIALS`, `ROLE`, `BOUTIQUE`,
* `PRACTICE_AREAS`, `FEES`, `CONTACT`. R16 flagged that both open sub-decisions
* were "each a §4 question of its own, and the matter list would collide with §4
* Forbidden directly" — so the answer that avoids both is a bio that adds
* nothing. No matter list, no referees, no figure that is not on `/fees/`. The
* fee summary IS here, because R16's own reasoning says an appointment proposal
* needs the rate card as much as the bio, and every figure in it is `/fees/`'s.
*
* `noindex`, and excluded from the sitemap in `astro.config.mjs`: it is a
* condensed duplicate of `/about/` and `/fees/`, and two URLs competing on the
* same content is the one thing `docs/04` is most concerned with.
*
* PRINT LAYOUT. `global.css`'s `@media print` block already hides the header,
* the footer and the skip link, neutralises the inverse grounds, and disables
* the reveal — all of it added because `/about/` is printed by people evaluating
* an appointment. This page adds only what makes it fit ONE sheet, and
* `scripts/bio-pdf.mjs` ASSERTS the page count rather than trusting it.
*/
import BaseLayout from '../layouts/BaseLayout.astro';
import Eyebrow from '../components/Eyebrow.astro';
import { getImage } from 'astro:assets';
import ogDefault from '../assets/og-portrait.jpg';
import { pageGraph } from '../data/schema';
import {
BOUTIQUE,
CONTACT,
CREDENTIALS,
FEES,
PRACTICE_AREAS,
ROLE,
SITE,
} from '../data/site';
const ldImage = await getImage({
src: ogDefault,
format: 'jpeg',
width: 1200,
height: 630,
});
const graph = pageGraph(new URL(ldImage.src, Astro.site).href);
const money = (amount: number) =>
new Intl.NumberFormat('en-CA', {
style: 'currency',
currency: FEES.currency,
maximumFractionDigits: 0,
}).format(amount);
const { halfDay, fullDay } = FEES.mediation;
/* The processes, each with a §4 Offerings row. Arbitration is scoped commercial
because Q39's gate is a legal one; mediation is unscoped because it has no
such gate (Q56). The asymmetry is designed — do not tidy it. */
const PROCESSES = [
'Mediation — sole mediator',
'Commercial arbitration — sole, party-appointed, co-arbitration',
'Med-arb — mediation converting to binding arbitration, agreed in advance',
'Early neutral evaluation — delivered to both parties together',
'Dispute-system design',
'Pre-dispute technical advisory',
];
---
<BaseLayout
title="One-Page Bio · Pouya Lajevardi · Mediator · Toronto"
description="A one-page record for circulation with an appointment proposal: designations, education, memberships, the processes offered, the practice areas, and the rates."
jsonLd={graph}
noindex
>
<section class="section bio-sheet">
<div class="wrap">
{
/* The download sits above the sheet and is `.no-print`, so the printed
copy does not carry a link to itself. */
}
<p class="no-print sheet-note">
This page is the source of the one-page PDF.
<a href="/pouya-lajevardi-bio.pdf" download>Download the PDF</a>, or
print this page.
</p>
<header class="sheet-head">
{
/* THE EYEBROW IS `.no-print`, AND IT IS HERE BECAUSE `og:proof` ASKED
FOR IT. That check compares each card's eyebrow against its page's
first `.eyebrow`, and this page had none — so the card said "Bio"
against nothing. The options were to weaken the check or to give the
page the element every other page has; weakening a check to match an
artefact is how a control stops controlling. On paper the sheet leads
with the name, so the eyebrow prints away. */
}
<div class="no-print">
<Eyebrow dot>Bio</Eyebrow>
</div>
<h1 class="sheet-name">{SITE.name}</h1>
<p class="sheet-desigs">{CREDENTIALS.designations.join(' · ')}</p>
<p class="eyebrow sheet-strap">{SITE.tagline}</p>
</header>
<div class="sheet-grid">
<section class="block block-wide">
<h2 class="eyebrow">The practice</h2>
<p>
{
/* ⚠️ NO LEADING SCOPE. This sentence read "I act as a neutral in
commercial disputes — as a mediator, as an arbitrator in
commercial matters, and in med-arb…", and the leading clause
scoped ALL THREE, mediation included. §4's mediation row is
unscoped deliberately (Q56) and says in terms: "do not scope it
on a page." It is the `/practice/` shape exactly — the two words
never appear in the same element, so no proximity grep reaches
it — and it was found by reading the rendered PDF. The scope
belongs on the arbitration clause alone, where Q39's legal gate
puts it.
⚠️ AND THE VERB IS `accept appointments`, NOT `act as`. §4
verifies exactly one practised role — "Mediator" — and says in
terms that "Arbitrator" as a practised role is NOT a row; what it
verifies is that appointments are ACCEPTED. `/` and `/about/`
carry the same construction. */
}
I act as a neutral. I accept appointments as a mediator, as an arbitrator
in commercial matters, and in med-arb where the parties want one neutral
across both phases. I read the contract and the technical record underneath
it rather than either side's summary of them.
</p>
<p>
I am {ROLE.title} at {BOUTIQUE}, with {ROLE.litigationLine} across
{' '}{ROLE.litigationAreas.join(', ')} matters. I am also a practising
machine-learning and infrastructure engineer, which is what lets me work
through a technical record at first hand.
</p>
</section>
<section class="block">
<h2 class="eyebrow">Designations</h2>
<ul role="list">
{CREDENTIALS.designations.map((d) => <li>{d}</li>)}
</ul>
<h2 class="eyebrow">Education</h2>
<ul role="list">
{CREDENTIALS.education.map((d) => <li>{d}</li>)}
</ul>
<h2 class="eyebrow">Certifications</h2>
<ul role="list">
{CREDENTIALS.certifications.map((d) => <li>{d}</li>)}
</ul>
</section>
<section class="block">
<h2 class="eyebrow">Memberships</h2>
<ul role="list">
{CREDENTIALS.memberships.map((d) => <li>{d}</li>)}
</ul>
<h2 class="eyebrow">Languages</h2>
<ul role="list">
<li>
{CREDENTIALS.languages.join(' and ')}, without an interpreter
</li>
</ul>
</section>
<section class="block">
<h2 class="eyebrow">Processes</h2>
<ul role="list">
{PROCESSES.map((p) => <li>{p}</li>)}
</ul>
</section>
<section class="block">
<h2 class="eyebrow">Subject matter</h2>
<ul role="list">
{PRACTICE_AREAS.map((area) => <li>{area.name}</li>)}
</ul>
<p class="fine">
Family arbitration under the <em>Family Law Act</em> is not offered.
</p>
</section>
<section class="block block-wide">
<h2 class="eyebrow">Rates</h2>
<ul role="list" class="rates-list">
<li>
Half day, up to {halfDay.hours} hours of session — {
money(halfDay.amount)
}. Fee includes up to {halfDay.prepIncluded} hours of preparation.
</li>
<li>
Full day, up to {fullDay.hours} hours of session — {
money(fullDay.amount)
}. Fee includes up to {fullDay.prepIncluded} hours of preparation.
</li>
<li>
Each party beyond two — {money(FEES.mediation.additionalParty)}.
Overtime beyond the session hours the fee covers —
{' '}{money(FEES.mediation.overtimePerHour)} an hour.
{' '}{FEES.mediation.reservation}
</li>
<li>
Arbitration — {money(FEES.arbitration.perHour)} an hour,
{' '}{money(FEES.arbitration.hearingDay)} a hearing day, or a flat fee
for documents-only and expedited references.
</li>
<li>
{FEES.taxNote} The full card, the cancellation schedule and the terms
are published at {SITE.url}/fees/.
</li>
</ul>
</section>
<section class="block block-wide sheet-contact">
<h2 class="eyebrow">Contact</h2>
<p>
{CONTACT.email} · {CONTACT.phoneFallback} · {CONTACT.location}
<br />
{CONTACT.responseTime} · {SITE.url} · {CONTACT.linkedin}
</p>
</section>
</div>
</div>
</section>
</BaseLayout>
<style>
.sheet-note {
margin-block-end: var(--space-7);
font-size: var(--text-sm);
color: var(--text-meta);
}
.bio-sheet {
padding-block: var(--space-8);
}
.sheet-head {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding-block-end: var(--space-5);
border-block-end: 2px solid var(--rule);
}
.sheet-name {
font-family: var(--font-serif);
font-size: var(--text-4xl);
line-height: var(--leading-tight);
letter-spacing: var(--tracking-tight);
}
.sheet-desigs {
margin-block-start: var(--space-3);
font-family: var(--font-mono);
font-size: var(--text-sm);
letter-spacing: var(--tracking-wide);
color: var(--accent);
}
/* Type comes from the global `.eyebrow` class on the element; only the margin is
here. This rule and `.block h2` below were byte-for-byte copies of
`.eyebrow`'s declarations at `--text-2xs` — the same escape the footer's
column headings were. The print block below sets both to 7pt, so the screen
size never reached the PDF. */
.sheet-strap {
margin-block-start: var(--space-2);
}
.sheet-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
gap: var(--space-6);
margin-block-start: var(--space-6);
}
.block-wide {
grid-column: 1 / -1;
}
/* See `.sheet-strap` above: type from `.eyebrow`, only the rule under it here. */
.block h2 {
padding-block-end: var(--space-2);
border-block-end: 1px solid var(--border);
}
.block h2:not(:first-child) {
margin-block-start: var(--space-5);
}
.block ul {
/* `global.css` strips the marker and padding from `ul[role='list']`. */
margin-block-start: var(--space-3);
display: grid;
gap: var(--space-2);
font-size: var(--text-sm);
line-height: var(--leading-snug);
}
.block p {
margin-block-start: var(--space-3);
font-size: var(--text-sm);
line-height: var(--leading-body);
color: var(--text-secondary);
max-inline-size: var(--width-prose);
}
.block p + p {
margin-block-start: var(--space-3);
}
/* `anywhere`, and the cause is ONE STRING: the last row prints the fee-card URL,
which has no break opportunity and sized this single-column grid track. The
URL has to stay — printed sheet, the reader cannot click it. `docs/02`
§Reflow, instrument finding 1. */
.rates-list {
max-inline-size: none;
overflow-wrap: anywhere;
}
.fine {
font-size: var(--text-xs);
}
.sheet-contact p {
font-family: var(--font-mono);
font-size: var(--text-xs);
line-height: var(--leading-relaxed);
max-inline-size: none;
}
/* --- One sheet of paper ------------------------------------------------ */
/* `global.css`'s print block already hides the header, footer and skip link,
neutralises the inverse grounds and disables the reveal. This is only what
makes the content FIT, and `scripts/bio-pdf.mjs` asserts the page count
rather than this comment claiming it. */
@media print {
.bio-sheet {
padding-block: 0;
}
.wrap {
max-inline-size: none;
padding-inline: 0;
}
.sheet-grid {
/* Two fixed columns rather than auto-fit: on paper there is no viewport
to fit to, and a print UA resolves `auto-fit` against the sheet width
inconsistently. */
grid-template-columns: 1fr 1fr;
gap: 10pt 18pt;
margin-block-start: 10pt;
}
.sheet-name {
font-size: 22pt;
}
.sheet-desigs {
font-size: 9pt;
}
.sheet-strap {
font-size: 7pt;
}
.sheet-head {
padding-block-end: 8pt;
}
/* ⚠️ `font-weight` IS FROZEN AT 400 BY RULING, 2026-09-01. The circulated
PDF's typography changes only when its CONTENT is deliberately revised,
never as a side effect of a screen refactor — so print keeps 400 while
screen takes the 500 every other eyebrow has. `docs/02` §Accessibility
floor carries the reasoning and the byte figures. */
.block h2,
.sheet-strap {
font-weight: var(--weight-normal);
}
.block h2 {
font-size: 7pt;
padding-block-end: 3pt;
}
.block h2:not(:first-child) {
margin-block-start: 9pt;
}
.block ul,
.block p {
margin-block-start: 5pt;
font-size: 8.5pt;
line-height: 1.35;
}
.block ul {
gap: 2pt;
}
.sheet-contact p {
font-size: 8pt;
}
/* A block must not be split across a page break — on a one-sheet document
that would mean a second sheet carrying two lines. */
.block {
break-inside: avoid;
}
}
</style>