Files
adr-sml/src/pages/fees.astro
T
Pouya LajevardiandClaude Opus 5 3c3ba5dc6e
Build and deploy / build-and-deploy (push) Failing after 4s
feat: price med-arb by phase, attest the conflicts undertaking, and answer the first real spam
Pouya's rulings of 2026-09-03 (the last two D20 findings) and 2026-09-04 (the
spam observation and four mitigations), in one change set.

D20 finding 10 — med-arb is billed BY PHASE, each phase at the rates already
published, so /fees/'s "Every figure is on this page" is true as written rather
than narrowed. FEES.medArb is the single source; docs/07 §Med-arb carries the
rule INTERIM against R5, and R5 now carries it back, because a derived price
moves silently when a rate moves.

D20 finding 13 — conduct undertaking (g), attested 2026-09-03, published as his
wording verbatim on /legal/privacy/ and /contact/. The clause that raised the
finding promised to DISCLOSE a conflicts check's outcome, which the attestation
does not cover; it is struck. D20 now partitions 17 fixed / 2 refuted / 1 owed.

Spam, 2026-09-04 — recorded in docs/05 §Observed abuse with the date and
signature. A second honeypot (a decoy checkbox, own class, `hidden`, a label
that tells a human not to tick it) and scoring that LABELS and never rejects:
nothing is dropped, nothing new is stored, and only the operator notification
changes. Q65 opens the WAF cost call.

The timing floor could not be built: there is no timing check and never has
been. docs/05 carries it struck, and every mechanism that would give a real
per-visitor clock breaks zero-JS, handler-and-form-only, or D1. Q66.

configure.mjs gains section 5 — a custom origin request policy forwarding
CloudFront-Viewer-Address on /api/*. Written, dry-run against the live
distribution, NOT applied. It reads the handler's own header reads and refuses
to run if the whitelist omits one.

And reading the live account to do it found four AGENTS.md §7 rows saying the
intake backend was undeployed, two days after it went live — corrected against
get-function-configuration, get-routes, get-stage, get-policy and the deployed
zip, which was downloaded and read.

Review: adversarial-reviewer only (claims-auditor is D20's cutover pass and has
run). Round 1 five lenses, 56 findings, 7 blocking, 4 refuted by an independent
refuter; round 2 four lenses, 36 findings, 33 of them defects in round 1's own
repairs. Stopped at two per D19.

Gates, exit status read for each: check 0 · build 0 (23 pages) · check:claims 0
· check:intake 0 · og:proof 0 · lint 0 · spam-score.test 39/39 with 6/6 mutations
killed · router.test 30/30 · minifier grep 1 (clean) · lighthouse 0, no category
below 95 · configure.mjs dry run 0, nothing written.

Nothing deployed and nothing applied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
2026-09-04 10:06:37 -04:00

515 lines
20 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
/**
* `/fees/` — build step 9. Spec: docs/01 §`/fees/`, docs/03 §Fees,
* **docs/07-fees.md is the authority on every number here.**
*
* ⚠️ **EVERY FIGURE IS INTERPOLATED FROM `FEES`. NOT ONE IS TYPED.** This is the
* page D8 commits to and the one where a hand-typed number would be an incorrect
* price rather than an untidy fact. `docs/07` §All parameters confirmed sets the
* publication rules the interpolation has to satisfy, and two of them are
* wording rather than value:
*
* 1. **The preparation allowance is CAPPED and must READ as capped** —
* *"including **up to** 2 hours of preparation"*. Never "including 2 hours",
* which sells an entitlement, and never "preparation included", which sells
* an uncapped one. `/for-parties/` shipped the flat form for one pass, on the
* one page written for a reader with no counsel to catch it.
* 2. **The session cap and the preparation allowance are DIFFERENT THINGS with
* different nouns** — `hours` is time in the session, `prepIncluded` is
* preparation bundled into the fee. Folding them into one figure is the
* ambiguity Q58 was opened to fix, and it was an ambiguity in `docs/07`
* itself rather than in any copy.
*
* ✅ **Q59 IS RULED AND THIS PAGE IS WHY IT MATTERED — Pouya, 2026-08-31.**
* Overtime runs from the **session cap**: the fourth hour of a half day, the
* seventh of a full day. Until that ruling this page could not publish the
* $500 rate at all, because a rate printed beside "up to 3 hours" defines its
* own trigger by adjacency and there was no other quantity for it to attach to.
*
* ⚠️ **AND THE RULING'S SECOND HALF IS NOT DECORATION — IT IS WHAT KEEPS THE
* PAGE FROM READING AS AN ARITHMETIC MISTAKE.** `FEES.mediation.reservation`:
* *a full day reserves the day; half-day overtime is subject to availability.*
* Without it a reader adds up `2000 + 500 × 3 = 3500` against `4000` and
* concludes the full-day rate is simply worse — which is a real feature of D14's
* figures (§12 R5 carries it, with the table in `docs/07` §Recorded dissent) and
* is answered by what the full-day fee actually buys. So the reservation
* sentence ships **adjacent to the overtime row**, not in a footnote. Same
* structural rule as `PROCESS_FRAMING` beside the five timings under Q43.
*
* ✅ **MED-ARB IS PRICED HERE AS OF 2026-09-03, AND IT CARRIES NO FIGURE.**
* Pouya's ruling closing D20 finding 10: it is billed **by phase**, each phase
* at the rates already on this page. The finding was that the hero promises
* *"Every figure is on this page"* while §4 Offerings carries a Med-Arb row
* that `docs/07` priced nowhere — a promise wider than the card. It is closed by
* pricing the offering, not by narrowing the promise, so the hero sentence is
* unchanged and is now true as written. **Do not give the section a rate row:**
* a med-arb figure would be a fourth price for a process priced twice, and the
* first thing it would do is disagree with one of them. `FEES.medArb` holds the
* three sentences; `docs/07` §Med-arb holds the rule. INTERIM, reviewed at R5.
*
* ⚠️ **NO TRIBUNAL-SECRETARY RATE AND NO SETTLEMENT COUNSEL.** Both are struck
* rows in §4 Offerings — the first removed by Pouya from D14, the second by him
* as his own error in `docs/01`. **A rate on a fee page is an offer**, which is
* exactly why they are struck here rather than merely unpriced.
*
* ⚠️ **ARBITRATION IS SCOPED COMMERCIAL, MEDIATION IS NOT.** The asymmetry is
* designed (Q39, Q56): family arbitration in Ontario carries prescribed training
* and is separately NOT OFFERED, so the scope on the arbitration rows is a legal
* gate. Mediation has no equivalent gate and is unscoped on purpose. A later
* editor tidying these into a matching pair would reintroduce the defect.
*/
import BaseLayout from '../layouts/BaseLayout.astro';
import Button from '../components/Button.astro';
import ContactBand from '../components/ContactBand.astro';
import Eyebrow from '../components/Eyebrow.astro';
import SectionHeading from '../components/SectionHeading.astro';
import { getImage } from 'astro:assets';
import ogDefault from '../assets/og-portrait.jpg';
import { pageGraph } from '../data/schema';
import { FEES } from '../data/site';
const ldImage = await getImage({
src: ogDefault,
format: 'jpeg',
width: 1200,
height: 630,
});
/* No `Service` node, and no `Offer` node either. The Person alone — the
`/practice/` and `/process/` shape. `/mediation/` and `/arbitration/` already
carry the `Service` nodes for what is priced here, and a second one on this
path would put a duplicate `@id` in the graph. An `Offer` with `price` would
be the obvious addition and is deliberately not made: schema.org's `Offer`
models a single price for a single item, and every row below is conditional on
session length, party count and format — a machine-readable $2,000 with none
of those conditions attached is a worse claim than no claim.
⚠️ AND `/`'s NODE CARRIES NO PRICE EITHER — this comment said
`ProfessionalService.priceRange` "carries the range instead", and that field
was removed the same day for mixing units and understating its own floor.
Nothing on this site states a price in machine-readable form, deliberately:
every figure here is conditional on session length, party count or format,
and a number without those conditions is a worse claim than no number.
Found by `adversarial-reviewer` round 2 — a justification resting on a field
that no longer exists is how an `Offer` node gets added by the next reader. */
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 two mediation rows, built from the constants so the noun and the "up to"
travel with the number rather than being retyped beside it. */
const MEDIATION_ROWS = [
{
item: `Half day — up to ${halfDay.hours} hours of session`,
detail: `Fee includes up to ${halfDay.prepIncluded} hours of preparation.`,
fee: money(halfDay.amount),
},
{
item: `Full day — up to ${fullDay.hours} hours of session`,
detail: `Fee includes up to ${fullDay.prepIncluded} hours of preparation.`,
fee: money(fullDay.amount),
},
{
item: 'Each party beyond two',
detail: 'Per party, added to the session fee.',
fee: money(FEES.mediation.additionalParty),
},
{
item: 'Overtime, per hour',
/* Q59: the trigger is the SESSION cap, and the reservation point ships in
the same cell as the rate. See the header for why it is not a footnote. */
detail: `Charged beyond the ${halfDay.hours} or ${fullDay.hours} session hours the fee covers. ${FEES.mediation.reservation}`,
fee: money(FEES.mediation.overtimePerHour),
},
];
const ARBITRATION_ROWS = [
{
item: 'Hourly',
detail: 'Procedural work, document review, award writing.',
fee: money(FEES.arbitration.perHour),
},
{
item: 'Hearing day',
detail: 'In person or by video, at the same rate.',
fee: money(FEES.arbitration.hearingDay),
},
{
item: 'Documents-only or expedited — simple',
detail: 'Flat fee.',
fee: money(FEES.arbitration.documentsOnlySimple),
},
{
item: 'Documents-only or expedited — complex',
detail: 'Flat fee.',
fee: money(FEES.arbitration.documentsOnlyComplex),
},
];
---
<BaseLayout
title="Fees · Mediation and Arbitration Rates · Pouya Lajevardi"
description="The full rate card: half-day and full-day mediation, arbitration, cancellation terms and what an overrun costs. Published in full, with no ranges."
jsonLd={graph}
>
{/* ---- 1. Hero -------------------------------------------------------- */}
<section class="section hero">
<div class="wrap">
<Eyebrow dot>Fees</Eyebrow>
<h1 class="display hero-h">
Published in full, including what overruns cost.
</h1>
{
/* ⚠️ THIS SENTENCE QUOTED A PHRASE THE SPEC BARS, AND THE QUOTATION WAS
THE PROBLEM. It read: *No ranges, no "starting from", and nothing that
has to be asked for.* Two defects in one clause. (1) It defines the
page against an unnamed practice — an implied comparative, which Q41(b)
answers: assert his capability, never the field's. (2) It plants the
literal string `starting from` in `dist/`, where a future sweep for
`docs/03`'s "no 'starting from' evasions" would hit it and read a
negation as a breach — the `I aLSO practise` / `the pLEADINGs` shape,
manufactured on purpose by the copy. Stating what the page DOES needs
no comparison and leaves nothing to trip over. */
}
<p class="hero-lede">
One rate for all mediation matters, whatever the subject. Every figure
is on this page, and none of it has to be asked for. {FEES.taxNote}
</p>
</div>
</section>
{/* ---- 2. Mediation --------------------------------------------------- */}
<section class="section section-alt reveal">
<div class="wrap">
<div class="section-head">
<SectionHeading
eyebrow="Mediation"
level={2}
lede="One rate for every matter. Preparation is bundled into the fee and is capped."
>
<span slot="heading">Half day or full day.</span>
</SectionHeading>
</div>
<dl class="rates">
{
MEDIATION_ROWS.map((row) => (
<div class="rate">
<dt>
<span class="rate-item">{row.item}</span>
<span class="rate-detail">{row.detail}</span>
</dt>
<dd>{row.fee}</dd>
</div>
))
}
</dl>
</div>
</section>
{/* ---- 3. Arbitration ------------------------------------------------- */}
<section class="section reveal">
<div class="wrap">
<div class="section-head">
<SectionHeading
eyebrow="Arbitration"
level={2}
lede="Sole, party-appointed and co-arbitration appointments, in commercial matters."
>
<span slot="heading">Hourly, by hearing day, or flat.</span>
</SectionHeading>
</div>
<dl class="rates">
{
ARBITRATION_ROWS.map((row) => (
<div class="rate">
<dt>
<span class="rate-item">{row.item}</span>
<span class="rate-detail">{row.detail}</span>
</dt>
<dd>{row.fee}</dd>
</div>
))
}
</dl>
{
/* The Q39 scope, stated on the page rather than left to the lede. §4
Offerings carries a NOT OFFERED row for family arbitration, and
`/practice/shareholder/` makes the same exclusion in one sentence on
Pouya's instruction — "one sentence, not a section", because a
disclaimer that grows reads as defensive. */
}
<p class="scope-note">
Family arbitration under the <em>Family Law Act</em> is not offered, and family
law matters are not accepted.
</p>
</div>
</section>
{/* ---- 4. Med-arb ------------------------------------------------------ */}
<section class="section section-alt reveal">
<div class="wrap">
<div class="section-head">
<SectionHeading
eyebrow="Med-arb"
level={2}
lede="One appointment, two processes. Each phase is charged at the rates for that process."
>
<span slot="heading">Billed by phase.</span>
</SectionHeading>
</div>
{
/* NO `<dl class="rates">` HERE, AND THE ABSENCE IS THE POINT — see the
header. Every other section on this page pairs an item with a figure;
this one has no figure of its own, and giving it a row would mean
inventing one. The three sentences come from `FEES.medArb` so the rule
lives beside the numbers it points at rather than in this template. */
}
<ul class="notes" role="list">
<li>{FEES.medArb.rule}</li>
<li>{FEES.medArb.noSeparateFee}</li>
<li>{FEES.medArb.termsApply}</li>
</ul>
</div>
</section>
{/* ---- 5. Other services ---------------------------------------------- */}
<section class="section reveal">
<div class="wrap">
<div class="section-head">
<SectionHeading
eyebrow="Also offered"
level={2}
lede="Charged hourly, with an estimate agreed in the terms of appointment."
>
<span slot="heading">Three things beside the two processes.</span>
</SectionHeading>
</div>
<dl class="rates">
<div class="rate">
<dt>
<span class="rate-item">Early neutral evaluation</span>
{
/* §4's ENE row and `docs/01` §`/practice/` both require this
framing, and `docs/07` repeats it for this page in terms:
"nothing on `/fees/` may read as a rate for advising one of
them." ENE is the offering nearest §4's NOT-NEGOTIABLE boundary,
because a neutral assessment of the merits sits closest to
providing legal services. */
}
<span class="rate-detail">
A reasoned assessment of the merits, delivered to both parties
together. Never advice to one of them.
</span>
</dt>
<dd>{money(FEES.hourly)}<span class="per"> / hour</span></dd>
</div>
<div class="rate">
<dt>
<span class="rate-item">Dispute-system design</span>
<span class="rate-detail">
Advising an organisation on how its future disputes should be
handled, before there are any.
</span>
</dt>
<dd>{money(FEES.hourly)}<span class="per"> / hour</span></dd>
</div>
<div class="rate">
<dt>
<span class="rate-item">Pre-dispute technical advisory</span>
{
/* THE CONFLICT CAUTION IS NOT OPTIONAL. §4's row: "no copy may
imply the offering is free of that tension", and it names
`/practice/`'s strip as where the temptation would arise. A fee
page is the second such place, because a priced line reads as a
product. Advisory work for one organisation can conflict against
a later appointment in the same matter. */
}
<span class="rate-detail">
Technical review before a dispute exists. Taking it on can rule me
out of a later appointment in the same matter, and that is settled
in writing before the work starts.
</span>
</dt>
<dd>{money(FEES.hourly)}<span class="per"> / hour</span></dd>
</div>
</dl>
</div>
</section>
{/* ---- 6. Cancellation ------------------------------------------------ */}
<section class="section section-alt reveal">
<div class="wrap">
<div class="section-head">
<SectionHeading
eyebrow="Cancellation"
level={2}
lede="A reserved date is time that cannot be given to another matter. The schedule is published so it is never a surprise."
>
<span slot="heading">If a date is cancelled.</span>
</SectionHeading>
</div>
<dl class="rates">
{
FEES.cancellation.map((row) => (
<div class="rate">
<dt>
<span class="rate-item">{row.window}</span>
</dt>
<dd class="dd-text">{row.fee}</dd>
</div>
))
}
</dl>
<ul class="notes" role="list">
{FEES.cancellationNotes.map((note) => <li>{note}</li>)}
</ul>
</div>
</section>
{/* ---- 7. Terms -------------------------------------------------------- */}
<section class="section section-inverse reveal">
<div class="wrap">
<div class="section-head">
<SectionHeading eyebrow="Terms" level={2}>
<span slot="heading">How the account works.</span>
</SectionHeading>
</div>
<ul class="notes" role="list">
<li>{FEES.taxNote}</li>
{FEES.terms.map((term) => <li>{term}</li>)}
<li>
Travel outside the Greater Toronto Area is billed separately, or
bundled at a day rate stated in the terms of appointment.
</li>
<li>
Everything above is confirmed in the terms of appointment before an
engagement begins. Nothing on this page is an appointment.
</li>
</ul>
<div class="cta">
<Button href="/contact/" variant="gold"
>Request a consultation &rarr;</Button
>
<Button href="/process/" variant="ghost"
>How an engagement runs &rarr;</Button
>
</div>
</div>
</section>
<ContactBand />
</BaseLayout>
<style>
.hero {
padding-block-start: var(--space-9);
}
.hero-h {
margin-block: var(--space-4) var(--space-5);
font-size: var(--text-6xl);
}
.hero-lede {
max-inline-size: 58ch;
font-size: var(--text-lg);
line-height: var(--leading-body);
color: var(--text-secondary);
}
/* --- The rate rows ---------------------------------------------------- */
/* A `<dl>`, not a `<table>`. Each row is one item and its price — a
term-and-value pair — and a two-column table of eight rows reflows badly on
a phone, where the price ends up under a wrapped item name with no
alignment left to carry the association. The `<dt>`/`<dd>` pair keeps that
association semantically whatever the layout does. */
.rates {
display: grid;
gap: 0;
max-inline-size: 56rem;
border-block-start: 1px solid var(--border);
}
.rate {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: baseline;
gap: var(--space-3) var(--space-5);
padding-block: var(--space-5);
border-block-end: 1px solid var(--border);
}
.rate dt {
display: flex;
flex-direction: column;
gap: var(--space-2);
/* Leaves room for the fee on one line at tablet width and up, and wraps
under it below that. */
flex: 1 1 22rem;
}
.rate-item {
font-size: var(--text-lg);
line-height: var(--leading-snug);
}
.rate-detail {
font-size: var(--text-sm);
line-height: var(--leading-body);
color: var(--text-meta);
max-inline-size: 52ch;
}
.rate dd {
font-family: var(--font-serif);
font-size: var(--text-2xl);
line-height: var(--leading-tight);
white-space: nowrap;
}
/* The cancellation column is a sentence, not a figure, so it takes body type
and is allowed to wrap. */
.rate .dd-text {
flex: 1 1 16rem;
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--leading-body);
color: var(--text-secondary);
white-space: normal;
}
.per {
font-family: var(--font-sans);
font-size: var(--text-sm);
color: var(--text-meta);
}
.scope-note {
margin-block-start: var(--space-6);
max-inline-size: var(--width-prose);
font-size: var(--text-base);
line-height: var(--leading-body);
color: var(--text-secondary);
}
.notes {
/* No `list-style: none` or `padding: 0` — `global.css` applies both to
`ul[role='list']`, and a second copy is a second thing to keep true. */
display: grid;
gap: var(--space-4);
margin-block-start: var(--space-6);
max-inline-size: var(--width-prose);
}
.notes li {
padding-inline-start: var(--space-5);
border-inline-start: 1px solid var(--rule);
line-height: var(--leading-body);
}
.cta {
display: flex;
flex-wrap: wrap;
gap: var(--space-3) var(--space-4);
margin-block-start: var(--space-8);
}
</style>