chore: install toolchain, wire lint, add local deploy path

Answers four questions and starts build step 1.

Q22 — the scoped deploy user does not exist: aws iam get-user returns
NoSuchEntity. Recorded in §7 as NOT PROVISIONED and swept so that no file
describes it as existing. §10 records that user/pouya, the broadly-
permissioned personal user that has been authenticating to this account,
must never be used in CI; scripts/deploy-local.sh refuses to run as it.

Q23 — the Gitea instance reports 1.27.2, well above the vars-context floor,
so the first-step guard is belt-and-braces rather than load-bearing. What
remains is not a fact but a dependency: the instance is jointly administered,
so enabling Actions and registering a runner both need a second admin. Hence
npm run deploy (scripts/deploy-local.sh), which performs exactly what the
workflow performs — same guard, same three passes, same headers, same
invalidation. Documented as the current path, not as a workaround.

§10 gains the risk that follows: the deploy secret will live on jointly
administered infrastructure, where an instance admin can reach repo secrets.
That does not change the plan, but it makes the scoped IAM policy the actual
control between a shared Gitea instance and an AWS account holding another
business's client-database backups. Never widen it.

Q27 — response time is two business days, in site.ts with a derived short
form so the confirmation email cannot drift from the page.
Q28 — OBA sections confirmed, stamped "for now"; membership renews yearly,
tracked as R10.

Build step 1: dependencies installed and package-lock.json created, closing
the npm ci blocker. ESLint flat config and Prettier config added; npm run
lint, check and build all pass. Prettier deliberately excludes *.md and
tokens.css — reasons recorded in .prettierignore.

npm audit reports 7 high-severity advisories, all requiring an Astro major
upgrade. Not applied; escalated in AGENTS.md entry (s) as a decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XquaEq4BgWMCwUqLEyNkF
This commit is contained in:
Pouya Lajevardi
2026-08-26 11:54:04 -04:00
co-authored by Claude Opus 5
parent 2b6176e4d7
commit 0d8b63380a
15 changed files with 13962 additions and 141 deletions
+51 -37
View File
@@ -32,21 +32,20 @@ export const CREDENTIALS = {
'Stitt Feld Handy — negotiation and ADR workshop series',
],
languages: ['English', 'Farsi'],
/** The three bodies are [verified 2026-08-26]; **which** OBA sections is
* [assumed] — §4 marks it so, the names come from the strategy brief and
* Pouya has not confirmed them. NOT OCNI (lapsed) and NOT the Law Society —
/** [verified 2026-08-26 — Pouya, AGENTS.md Q28] — and FOR NOW: OBA section
* membership renews yearly, so this is a fact with a shelf life (AGENTS.md
* R10). Re-confirm at renewal. NOT OCNI (lapsed) and NOT the Law Society —
* listing the LSO implies licensure, which D13 bars. Do not add either. */
memberships: [
'ADR Institute of Canada (ADRIC)',
'ADR Institute of Ontario (ADRIO)',
// TODO(pouya): AGENTS.md Q28 — confirm the section list before publishing it.
'Ontario Bar Association — Construction & Infrastructure, ADR, and Civil Litigation sections',
],
} as const;
/** The three credential slots. Never matter counts — AGENTS.md §4. */
export const CREDENTIAL_ROW = [
{ value: 'Q.Med', label: 'ADRIC / ADRIO designation' },
{ value: 'Q.Med', label: 'ADRIC / ADRIO designation' },
{ value: 'JD + ML', label: 'Law and engineering' },
{ value: 'EN · FA', label: 'Bilingual practice' },
] as const;
@@ -64,16 +63,19 @@ export const ANALYTICS = {
} as const;
export const CONTACT = {
email: 'info@smlcompany.ca', // [verified 2026-08-26]
email: 'info@smlcompany.ca', // [verified 2026-08-26]
/** No public phone by choice. Render 'By scheduled call' wherever a number
* would go — do not leave the field visually empty. */
phone: null as string | null, // [verified 2026-08-26]
phone: null as string | null, // [verified 2026-08-26]
phoneFallback: 'By scheduled call',
location: 'Toronto · Ontario · By appointment',
/** TODO(pouya): AGENTS.md Q27 — is a one-business-day response time a
* commitment you want published? It is not in the §4 register, and it is a
* service level a reader can hold you to. Left null until confirmed. */
responseTime: null as string | null,
/** [verified 2026-08-26 — Pouya, AGENTS.md Q27]. A PUBLIC COMMITMENT: this
* wording must match /contact/, the inquirer confirmation email, and any
* bio. Change it here and sweep — never edit one copy. */
responseTime: 'Inquiries are answered within two business days.',
/** The same fact in sentence-fragment form, for the confirmation email and
* any inline use. Derived, so the two cannot drift. */
responseTimeShort: 'within two business days',
linkedin: 'https://www.linkedin.com/in/pouyalajevardi/', // [verified 2026-08-26]
/** Booking parked 2026-08-26 (AGENTS.md R6). Build /contact/ with the intake
* form and a reserved slot so an embed drops in later without a rebuild. */
@@ -82,8 +84,8 @@ export const CONTACT = {
/** Portrait assets. Astro derives AVIF/WebP variants from the master at build. */
export const PORTRAIT = {
master: 'src/assets/pouya-lajevardi.jpg', // 1600x1600
og: 'src/assets/og-portrait.jpg', // 1200x630, cropped high
master: 'src/assets/pouya-lajevardi.jpg', // 1600x1600
og: 'src/assets/og-portrait.jpg', // 1200x630, cropped high
alt: 'Pouya Lajevardi',
} as const;
@@ -102,25 +104,25 @@ export const FEES = {
taxNote: 'All fees are plus HST.',
mediation: {
/** Prep is bundled AND stated on the page — [verified 2026-08-26].
* Do not hide it: at these rates, saying preparation is included is the
* point, not a detail. */
* Do not hide it: at these rates, saying preparation is included is the
* point, not a detail. */
halfDay: { amount: 2000, hours: 3.5, prepIncluded: 2 },
fullDay: { amount: 4000, hours: 7, prepIncluded: 3 },
additionalParty: 500, // each party beyond two
overtimePerHour: 500, // [verified 2026-08-26]
fullDay: { amount: 4000, hours: 7, prepIncluded: 3 },
additionalParty: 500, // each party beyond two
overtimePerHour: 500, // [verified 2026-08-26]
},
arbitration: {
perHour: 500,
hearingDay: 4000,
documentsOnlySimple: 6500, // flat
documentsOnlyComplex: 9500, // flat
documentsOnlySimple: 6500, // flat
documentsOnlyComplex: 9500, // flat
// No tribunal-secretary rate — removed by Pouya 2026-08-26.
},
// ENE, settlement counsel, dispute-system design, pre-dispute technical advisory
hourly: 500, // [verified 2026-08-26]
hourly: 500, // [verified 2026-08-26]
cancellation: [
{ window: 'More than 30 days before', fee: 'No fee. Disbursements only.' },
{ window: '15 to 30 days before', fee: '50% of the booked fee.' },
{ window: '15 to 30 days before', fee: '50% of the booked fee.' },
{ window: 'Fewer than 15 days before', fee: '100% of the booked fee.' },
],
cancellationNotes: [
@@ -135,33 +137,45 @@ export const FEES = {
} as const;
export const PRACTICE_AREAS = [
{ slug: 'construction', name: 'Construction & Infrastructure', chip: 'Construction' },
{ slug: 'technology', name: 'Technology, AI & Data', chip: 'Technology' },
{ slug: 'energy', name: 'Energy, Grid & Regulatory', chip: 'Energy' },
{ slug: 'insurance', name: 'Insurance, SABS & LAT', chip: 'Insurance' },
{ slug: 'shareholder', name: 'Shareholder & Family Business', chip: 'Shareholder' },
{ slug: 'cross-cultural', name: 'Cross-Border & Diaspora', chip: 'Cross-cultural' },
{
slug: 'construction',
name: 'Construction & Infrastructure',
chip: 'Construction',
},
{ slug: 'technology', name: 'Technology, AI & Data', chip: 'Technology' },
{ slug: 'energy', name: 'Energy, Grid & Regulatory', chip: 'Energy' },
{ slug: 'insurance', name: 'Insurance, SABS & LAT', chip: 'Insurance' },
{
slug: 'shareholder',
name: 'Shareholder & Family Business',
chip: 'Shareholder',
},
{
slug: 'cross-cultural',
name: 'Cross-Border & Diaspora',
chip: 'Cross-cultural',
},
] as const;
/** Seven items is the ceiling before a nav stops being scannable. */
export const PRIMARY_NAV = [
{ href: '/about/', label: 'About' },
{ href: '/mediation/', label: 'Mediation' },
{ href: '/about/', label: 'About' },
{ href: '/mediation/', label: 'Mediation' },
{ href: '/arbitration/', label: 'Arbitration' },
{ href: '/practice/', label: 'Practice', children: PRACTICE_AREAS },
{ href: '/fees/', label: 'Fees' },
{ href: '/insights/', label: 'Insights' },
{ href: '/contact/', label: 'Contact' },
{ href: '/practice/', label: 'Practice', children: PRACTICE_AREAS },
{ href: '/fees/', label: 'Fees' },
{ href: '/insights/', label: 'Insights' },
{ href: '/contact/', label: 'Contact' },
] as const;
/** Linked contextually rather than from the primary nav. */
export const SECONDARY_NAV = [
{ href: '/process/', label: 'How I work' },
{ href: '/med-arb/', label: 'Med-Arb' },
{ href: '/process/', label: 'How I work' },
{ href: '/med-arb/', label: 'Med-Arb' },
{ href: '/for-parties/', label: 'For parties' },
] as const;
export const LEGAL_NAV = [
{ href: '/legal/privacy/', label: 'Privacy' },
{ href: '/legal/terms/', label: 'Terms' },
{ href: '/legal/terms/', label: 'Terms' },
] as const;