feat: build step 4 — /mediation/, /arbitration/, /med-arb/; source ADRIC's rules
Build and deploy / build-and-deploy (push) Failing after 4s
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:
co-authored by
Claude Opus 5
parent
2282183b4b
commit
f3138a0a79
+124
-2
@@ -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
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user