feat: build step 4 — /mediation/, /arbitration/, /med-arb/; source ADRIC's rules
Build and deploy / build-and-deploy (push) Failing after 4s

Three pages, five in the build, zero JavaScript. /arbitration/ carries §4's
paired-disclosure condition on four surfaces and Q39's struck universal appears
in no form. /med-arb/ meets the procedural-fairness objection at the level of
process design and ships deliberately without Pouya's own protocol commitments,
which are Q54.

docs/01 directed the mediation page to name the "ADRIC Model Mediation Rules".
No such document exists — 0 occurrences across all four of ADRIC's rules pages
against 10 for "National Mediation Rules"; "Model" belongs to the Model Dispute
Resolution Clause inside the rules. Caught only because R14 requires the source
before the claim. docs/reference/adric-rules.md + adric-extract/ carry it, with
the digest drift measured rather than assumed: the HTML changes per request, the
text extracts are byte-stable, so the extracts are the artefact.

Four review passes, 21 defects, and the pattern was mine: I wrote the Q54 gate
into the page and then breached it four times, then round 2 found two survivors
of round 1's own fixes and one defect round 1's fix created. Also removed a
<title> naming a practised role §4 does not grant, a habitual presupposing awards
issued, and a claim about what ADRIC's rules permit that my own reference doc
says is unsupported.

Two instrument failures caught before they became conclusions: touch targets
measured over file:// with no CSS loaded (uniform 18px, including on a .btn with
a 44px floor), and a schema.org validator call that parsed nothing and returned
0 warnings for everything. Both re-run with the instrument validated first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148NztQskLKKApP5SzAA78e
This commit is contained in:
Pouya Lajevardi
2026-08-28 17:06:27 -04:00
co-authored by Claude Opus 5
parent 2282183b4b
commit f3138a0a79
18 changed files with 2496 additions and 18 deletions
+124 -2
View File
@@ -179,7 +179,21 @@ export function personNode(
*
* No `priceRange`, no `aggregateRating`, no `review` — the last two have no
* underlying data and §4 Forbidden bars the fabricated testimonial that the
* previous site carried.
* previous site carried. And no `availableLanguage`: it is not in this type's
* domain either (see `serviceGraph`), and the `Person` beside it in the graph
* carries `knowsLanguage`.
*
* ⚠️ **`serviceType` AND `provider` ARE STILL OUT OF DOMAIN ON THIS TYPE, AND
* THAT IS UNRESOLVED.** Measured 2026-08-28 against `validator.schema.org`:
* `/` returns `UNKNOWN_FIELD` for `serviceType`, `availableLanguage` and
* `provider` on `ProfessionalService`, which is a `LocalBusiness` and takes
* none of the three. `availableLanguage` is removed above because nothing is
* lost. The other two carry real information — the commercial scoping and the
* link to the Person — so removing them costs more than the warning does, and
* the modelled fix is a `Service` node with the business as `provider`, which
* is a change to the home page's structured data rather than a tidy-up. **The
* other four pages now validate with 0 warnings**; this node is the last one.
* Raised for the step-7 SEO pass, not left as folklore.
*/
export function professionalServiceNode(imageUrl?: string) {
return {
@@ -202,7 +216,6 @@ export function professionalServiceNode(imageUrl?: string) {
'Commercial arbitration',
'Mediation-arbitration (med-arb)',
],
availableLanguage: ['en', 'fa'],
email: `mailto:${CONTACT.email}`,
...(imageUrl ? { image: imageUrl } : {}),
};
@@ -250,3 +263,112 @@ export function aboutGraph(imageUrl?: string) {
'@graph': [personNode(imageUrl, { memberships: true })],
};
}
/**
* `Service` for the three process pages — `/mediation/`, `/arbitration/`,
* `/med-arb/` (build step 4). docs/04's structured-data table.
*
* THE PERSON NODE TRAVELS WITH IT, for the reason `homeGraph` gives: a `@graph`
* makes `provider: {'@id': …}` resolve inside this document rather than relying
* on a crawler fetching `/about/` and joining two. Same `@id` either way, so a
* consumer that does fetch both merges rather than duplicates.
*
* NO `BreadcrumbList`. These are one hop from the root and show no visible
* breadcrumb; docs/04 requires the markup to MATCH visible breadcrumbs, so
* emitting one would assert navigation the page does not show. Breadcrumbs
* begin at `/practice/<area>/` and `/insights/<slug>/`.
*
* NO `offers` AND NO `priceRange` until `/fees/` exists (build step 9) — same
* gate docs/04 puts on `ProfessionalService`.
*
* NO `availableLanguage` EITHER, AND THAT IS NOT AN OVERSIGHT. schema.org's
* `domainIncludes` for it is `ContactPoint`, `Course`, `LodgingBusiness`,
* `ServiceChannel`, `TouristAttraction` — **not `Service`**. It shipped here for
* one pass and `validator.schema.org` returned `UNKNOWN_FIELD` twice per page on
* all three, against **0 warnings** on `/about/`. The `Person` in the same
* `@graph` already carries `knowsLanguage: ['en','fa']`, so nothing is lost. The
* modelled alternative is `availableChannel → ServiceChannel → availableLanguage`,
* which is more machinery than the fact is worth.
*
* ⚠️ `serviceType` IS A UNION, NOT A STRING, AND THAT IS THE POINT. An
* unscoped `"Arbitration"` is Q39's struck universal — false as a universal,
* swept four times, reached a public page once — so it is not a value this
* function will accept. A comment asking a caller not to pass it is a warning; a
* union is a build error. Widening the union requires a §4 Offerings row, and
* the row is the only thing that should ever widen it.
*/
/** `docs/04`'s ratified `serviceType` strings — **not** §4's row labels, which
* read "Arbitration — sole arbitrator (commercial)" and "Med-Arb —
* mediation-arbitration". Adding a member needs a §4 Offerings row, but do not
* copy the row's wording: doing that "verbatim" yields the unscoped
* `Arbitration` this union exists to bar. */
export type PublishableServiceType =
'Mediation' | 'Commercial arbitration' | 'Mediation-arbitration (med-arb)';
export function serviceGraph(opts: {
path: string;
name: string;
serviceType: PublishableServiceType;
description: string;
imageUrl?: string;
}) {
return {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'Service',
'@id': `${SITE.url}${opts.path}#service`,
name: opts.name,
serviceType: opts.serviceType,
description: opts.description,
url: `${SITE.url}${opts.path}`,
provider: { '@id': PERSON_ID },
areaServed: [
{ '@type': 'City', name: 'Toronto' },
{ '@type': 'AdministrativeArea', name: 'Ontario' },
],
},
personNode(opts.imageUrl),
],
};
}
/**
* `/med-arb/`'s graph — the Service, the Person, and a `FAQPage`.
*
* docs/04 lists `FAQPage` for this page and for `/for-parties/`, with the
* condition that matters: *"Only where the visible page genuinely is Q&A. Never
* fabricate questions to farm a rich result."* So the node is BUILT FROM THE
* SAME ARRAY THE PAGE RENDERS — a question cannot enter the structured data
* without appearing on the page, and the two cannot drift.
*/
export function medArbGraph(opts: {
faq: ReadonlyArray<{ q: string; a: string }>;
imageUrl?: string;
}) {
const base = serviceGraph({
path: '/med-arb/',
name: 'Med-arb (mediation-arbitration)',
serviceType: 'Mediation-arbitration (med-arb)',
description:
'Mediation that converts to binding arbitration if the mediation does ' +
'not resolve the dispute. One neutral, both phases, agreed in writing ' +
'in advance. Commercial matters.',
imageUrl: opts.imageUrl,
});
return {
...base,
'@graph': [
...base['@graph'],
{
'@type': 'FAQPage',
'@id': `${SITE.url}/med-arb/#faq`,
mainEntity: opts.faq.map((item) => ({
'@type': 'Question',
name: item.q,
acceptedAnswer: { '@type': 'Answer', text: item.a },
})),
},
],
};
}
+11 -9
View File
@@ -50,18 +50,20 @@ export const CREDENTIALS = {
designations: ['Q.Med (ADRIC / ADRIO)'],
inProgress: ['Q.Arb — commenced August 2026'], // [verified 2026-08-26]
/**
* "Chartered Med-Arbitrator" — ADRIO's own term
* (docs/reference/adrio-designations.md). This read "Chartered
* Mediator-Arbitrator" until 2026-08-28, which was wrong; Pouya caught it and
* it was his own error, carried from the strategy brief and never sourced.
* "Chartered Med-Arbitrator" — ADRIO's own term, and ADRIC's navigation label
* too (docs/reference/adrio-designations.md; docs/reference/adric-rules.md
* Finding 3, which corroborates it from a second body). Never
* "Mediator-Arbitrator".
*
* UNCONSUMED AS OF 2026-08-28 — nothing imports `CREDENTIALS.goal`. `/about/`'s
* credential arc hand-types all three designations instead, which is the drift
* shape this repo keeps paying for (see ContactBand's 52ch/46ch divergence).
* It is corrected rather than deleted because `/med-arb/` at build step 4 is
* its natural consumer: either that page uses it, or this line comes out.
* `GOAL_NAME` IS THE STRING TO RENDER. This designation's name was wrong on a
* public page for the life of the file, and the reason it survived four review
* passes is that it existed in several hand-typed copies rather than one
* constant. `/arbitration/` and `/med-arb/` (step 4) render `GOAL_NAME`;
* `/about/`'s arc still hand-types it, which is the remaining copy.
*/
goal: 'C.Med-Arb (Chartered Med-Arbitrator)',
/** Just the expansion, for prose. §11 Glossary is the authority for it. */
goalName: 'Chartered Med-Arbitrator',
education: ['JD, Bond University'],
certifications: [
'Kompass Arbitration Certificate Program',