Build and deploy / build-and-deploy (push) Failing after 4s
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
412 lines
15 KiB
Plaintext
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>
|