# Requirements Traceability Matrix (FINAL — Phase 8, 2026-10-03)

Final status per requirement, with evidence (file, test or report). "Verified" means covered by an automated test or a recorded check in 07-QUALITY.md; "Implemented" means built and exercised; anything that depends on the owner says so explicitly. Column headings keep their original Phase 2 names.
Abbreviations: A = `ARCHITECTURE.md` section; P = phase.

## 1. Functional requirements (PRD §7)

| ID | Requirement | Planned implementation | P | Planned verification | Status |
|---|---|---|---|---|---|
| FR-01 | Homepage: identity, positioning, capabilities, featured work, CTAs | Homepage `Page` seeded with §9 block order (A §5.5); `hero`, `capabilities_grid`, `featured_projects`, `final_cta` blocks | 4–5 | `HomepageTest`: blocks render in order, ≤3 hero CTAs, featured projects from the Projects source | Implemented: homepage renders all §9 blocks from CMS data; hero owns the H1, ≤3 CTAs with primary/secondary/tertiary weights; no-photo layout (SiteInvariantsTest, SectionRendererTest) |
| FR-02 | Project listing: filter, sort, featured, empty state | Locked `projects_listing` block; `ProjectQuery::publishedListing()`; `filters` JS module + query-string fallback; Load more | 4–5 | Feature: query filters + empty state; Playwright: no-reload filtering, Clear filters | Implemented: featured first + badge, category/tool filters client-side without reload + URL sync + query-string fallback, empty state + Clear filters, Load more (SiteInvariantsTest; Playwright check 2026-10-03) |
| FR-03 | Section-flexible case-study template | Project `content_sections` + `cs_*` blocks; project detail template (A §5.4) | 4–5 | `CaseStudyTest`: add/remove/reorder; absent sections render nothing; contribution callout | Implemented: section-driven case studies, contribution panels, absent sections render nothing, sticky section nav / mobile anchor bar (SiteInvariantsTest) |
| FR-04 | Experience timeline/cards with linked projects and tools | `experiences` + pivots/tags; `experience_timeline_full` | 4–5 | Feature: ordering, current/date validation, linked live projects only | Implemented: hybrid timeline with expandable cards, achievements emphasised, linked projects/tools, CV button (experience page) |
| FR-05 | About page: structured human narrative | `narrative` template + image_text / rich_text / pull_quote blocks | 5 | Feature: About renders the seeded narrative; snapshot review | Implemented: narrative template, image+text, pull quote (SiteInvariantsTest) |
| FR-06 | Categorised skills, no proficiency graphics | `skill_categories`/`skills` (no proficiency columns); `skills_full` | 4–5 | Feature + arch test: no proficiency fields or `%` bars in the skill views | Implemented: categorised tag groups; no proficiency fields/graphics (Arch + SiteInvariantsTest) |
| FR-07 | Certifications with verification links | `certifications`; `certification-card` (rel=noopener, `external_link_click`); placeholder mode | 4–5 | Feature: placeholder → no link + placeholder treatment | Implemented: certification cards with verify link (rel=noopener, external_link_click); placeholder treatment, no link (SiteInvariantsTest) |
| FR-08 | Articles with categories, tags, related | `articles`, `article_categories`, topic tags, `RelatedArticlesResolver`, `ReadingTimeCalculator` | 4–5 | Unit: reading time + override; Feature: related fallback; share links | Implemented: listing with category/topic filters + empty state, category URLs, detail with meta/reading time/author/tags/share (LinkedIn first)/related (PublicPagesTest, SiteInvariantsTest) |
| FR-09 | Recommendations with attribution and sourcing | `recommendations`; consent-gated `PublishRecommendation`; source attribution | 4–5 | Feature: publishing without consent blocked; 0/1/many preview | Implemented: attributed cards, carousel/cards, hidden at zero, consent enforced (SiteInvariantsTest, PublishingActionsTest) |
| FR-10 | Booking | `BookingSettings` + provider adapters + `booking_embed`/`booking_cta` + fallback | 6 | Feature: enabled / disabled / embed-off; CSP domains; Playwright fallback | Implemented: BookingSettings + provider adapter (Calendly/Cal.com iframe; others link-out), lazy embed, meeting types, postMessage readiness + 8s fallback (hosted link + email), CSP frame sources, CTAs hidden when disabled (BookingTest; live check 2026-10-03). Positive embed path needs the owner's real booking URL |
| FR-11 | Contact form: validation, spam, states | `StoreContactSubmissionRequest`, `SubmitContactMessage`, honeypot, CAPTCHA contract, throttle, `contact-form` module | 6 | Feature: validation, honeypot/timing, rate limit, mail failure persists; Playwright: all states | Implemented: validation with friendly messages, honeypot + 3s timing + mandatory fields, pluggable CAPTCHA (null/Turnstile/reCAPTCHA v3), rate limits 5/min + 20/day, storage with hashed IP, queued mail, all §19.2 states incl. non-JS path (ContactFormTest; live browser check) |
| FR-12 | CV in 5 locations, replaceable without redeploy | `documents` + private disk; `/cv`, `/cv/download`; `cv-button` with location prop | 6 | Feature: headers, replacement at the same URL, old versions blocked, 5 locations present, fallback | Implemented: /cv inline + /cv/download attachment with location tracking, no-store, same URL after replacement, old versions never served, fallback page; buttons in all 5 locations switch to live links (CvEndpointsTest) |
| FR-13 | All content managed through a structured, decoupled source | Filament resources + settings pages; `Queries/*` read layer | 3–4 | Arch test: controllers/views don't query models directly; hardcoded-content audit (Companion Prompt 3) | Implemented (admin side): Filament resources for every content type + settings; read layer `Queries/*` + `SectionRenderer` (AdminScreensTest across roles). Public consumption P5 |
| FR-14 | 404 and empty states | `not_found` system page + `errors/404`; `empty-state` component | 5 | Feature: 404 renders CMS content + links; empty filters | Implemented: CMS-editable 404 with Home/Projects/Contact, /404, DB-failure fallback; 500/503 static; empty states (PublicPagesTest) |

## 2. Non-functional requirements (PRD §8)

| ID | Requirement | Planned implementation | P | Planned verification | Status |
|---|---|---|---|---|---|
| NFR-01 | Core Web Vitals "Good"; optimised images, lazy loading | `media.image`, WebP/AVIF conversions, self-hosted fonts, code-split JS, view-model cache | 3, 5, 7 | Lighthouse on Home / Project / Article (documented results) | Implemented and verified: Lighthouse Performance 99 and LCP 2.0–2.1 s / CLS 0 / TBT ≤ 20 ms on Home, Project and Article (07-QUALITY §3); responsive WebP/AVIF images, lazy loading, self-hosted fonts, 4.6 KB core JS |
| NFR-02 | WCAG 2.1 AA | Semantic landmarks, skip link, focus-visible tokens, labelled forms, aria-live, verified contrast (A §11.2) | 3–7 | Playwright + axe; manual keyboard checklist | Implemented and verified: axe WCAG 2.1 A/AA on 12 pages × 2 viewports, 0 violations (Playwright a11y.spec); Lighthouse Accessibility 100 |
| NFR-03 | Responsive, no horizontal scroll | Mobile-first Tailwind; 3→2→1 grids; 44px targets | 5, 7 | Playwright at 375/768/1024/1440: `scrollWidth <= clientWidth` | Implemented and verified: no overflow at 375/768/1024/1440, 3→2→1 grids, 44×44 targets on mobile (responsive.spec) |
| NFR-04 | SEO metadata, structured data, sitemap, clean slugs | `SeoResolver`, `<x-seo.head>`, `StructuredDataBuilder`, sitemap job, redirects | 3, 6 | Feature: unique titles, one H1, JSON-LD validity, sitemap exclusions, 301 on slug change | Implemented: SeoResolver + head, JSON-LD (WebSite/Person/Article/BreadcrumbList), sitemap.xml (live + indexable only), dynamic robots.txt, clean slugs, 301 on slug change (AnalyticsSeoTest, SiteInvariantsTest, RedirectsTest) |
| NFR-05 | HTTPS, validated input, rate limiting, dependency hygiene | Security headers/HSTS, Form Requests, throttles, `composer audit`/`npm audit` | 3, 6, 7 | Feature: headers present, throttles; audit reports | Implemented: security headers + nonce CSP with integration sources, Form Requests, throttles (contact/cv/preview/login), sanitiser; composer/npm audit clean 2026-10-03. HTTPS/HSTS is deployment config (DEPLOYMENT.md, P8) |
| NFR-06 | Confidentiality / anonymisation | `OrganisationPresenter` gate; `is_anonymised` + `confidentiality_cleared` | 4–5 | Feature: org name/logo absent from HTML, meta, JSON-LD, alt, sitemap | Implemented (rendered surfaces): OrganisationPresenter used by cards, detail header, logo grid; leak test over homepage, listing and detail HTML incl. meta/JSON-LD (SiteInvariantsTest). Sitemap check P6 |
| NFR-07 | Event analytics behind a swappable layer | `AnalyticsEvent` enum, server `AnalyticsTracker` drivers, client `track()` adapters | 6 | Unit: driver selection; Feature: CV/contact events recorded; catalogue sync test | Implemented: AnalyticsEvent catalogue + generated JS (sync test), server drivers (database/log/Plausible/GA4), client adapters (Plausible/Umami/GA4), delegated data-track listener, consent for GA4 (AnalyticsSeoTest) |
| NFR-08 | Content fully decoupled | Page builder + settings; `lang/` only for brand-neutral microcopy | 3–5 | Phase audits for hardcoded content | Implemented: professional copy only in CMS/settings; UI microcopy in lang/; hardcoded-content audit 2026-10-03 moved the last literals to lang/ and an arch test forbids literal labels in public templates |

## 3. Error and empty states (PRD §33)

| Context | Planned implementation | Planned verification | Status |
|---|---|---|---|
| 404 page | `not_found` system page + `errors/404` with DB-failure fallback | `NotFoundTest` | Implemented (PublicPagesTest; a11y.spec) |
| Empty project filter results | `empty-state` + Clear filters (JS and query-string paths) | Feature + Playwright | Implemented (SiteInvariantsTest; projects.spec) |
| Empty article filter results | Same pattern on `insights_listing` | Feature + Playwright | Implemented (ContentLifecycleTest) |
| Contact form error | Error panel with §19.2 copy from settings + mailto fallback | Feature (JSON error path) + Playwright | Implemented: error panel with settings copy + mailto fallback (ContactFormTest, browser check) |
| Missing optional content | `shouldRender()` per block; optional fields wrapped in conditionals; `media.image` null-safe | `CaseStudyTest`, component tests with minimal factories | Implemented: shouldRender() per block, null-safe images, absent sections render nothing (SectionRendererTest, SiteInvariantsTest) |
| CV temporarily unavailable | `cv-button` fallback + friendly endpoint page | `CvTest::missing_cv` | Implemented: fallback buttons + friendly /cv page (CvEndpointsTest) |
| No recommendations | `recommendations_preview::shouldRender` false at 0 | `RecommendationsPreviewTest` 0/1/many | Implemented (SectionRendererTest 0/1/many; SiteInvariantsTest) |
| Booking widget fails | Loader timeout/error → fallback panel; `<noscript>` link | Feature (markup) + Playwright (blocked script) | Implemented (booking-carousel.spec with provider blocked and with JS off; ADR-038) |

## 4. Acceptance criteria (PRD §34)

| # | Criterion | Planned verification | Status |
|---|---|---|---|
| 34.1 | All sitemap pages exist and are reachable via primary, footer, and mobile nav | Feature: every seeded nav item resolves 200; routes for all §6.1 pages | Partially implemented: all §6.1 pages served and linked from primary, footer and mobile nav (PublicPagesTest); /book appears once booking is configured |
| 34.2 | Homepage renders all §9 sections with correct CTA behaviour | `HomepageTest` | Verified: all §9 sections from CMS data in PRD order; hero ≤3 CTAs with distinct weights (EssentialSeederTest, SiteInvariantsTest, SectionRendererTest) |
| 34.3 | Project Card and Case Study render with placeholder and real content | Demo seeder + factory-based "realistic" content tests; visual review | Verified with placeholder content (demo seeder, screenshots, Playwright) and factory content (Site tests). Real-content review is an owner step |
| 34.4 | Add/edit/remove Project, Experience, Certification, Article, Recommendation without code | Filament resource tests (Livewire testing) for CRUD + publish | Verified: Projects (AdminFlowsTest), Experience/Certification/Article (ContentLifecycleTest), Recommendation (AdminFlowsTest) created, edited, published and removed through the admin |
| 34.5 | Contact form handles all §19.2 states | Feature + Playwright | Implemented (ContactFormTest + browser check of empty/validation/loading/success/error) |
| 34.6 | CV works from 5 locations; swappable without redeploy | `CvTest` | Implemented (CvEndpointsTest) |
| 34.7 | "Book a Call" opens a working embed from every placement, with fallback | Feature + Playwright | Partially implemented: embed + fallback on /book and booking CTAs on contact/final CTA; positive embed path awaits a real booking URL |
| 34.8 | Recommendations preview with one, several, zero | `RecommendationsPreviewTest` | Verified (SectionRendererTest, SiteInvariantsTest: 0 hidden, 1 card, many carousel) |
| 34.9 | WCAG 2.1 AA with no critical axe/Lighthouse violations | Playwright + axe | Verified: 0 axe violations (12 pages × 2 viewports); Lighthouse Accessibility 100 |
| 34.10 | CWV "Good" on Home, Project detail, Article detail | Lighthouse (documented) | Verified: Lighthouse 'Good' on Home (LCP 2.1 s), Project (2.0 s), Article (2.0 s); CLS 0 (07-QUALITY §3; re-run on the production host recommended) |
| 34.11 | All §29 events fire and are verifiable | Catalogue test + Playwright event spy + provider dashboard check (owner) | Partially implemented: every §29 event wired through the abstraction (catalogue + template tests); verification in the provider dashboard is an owner step once a provider is configured |
| 34.12 | Responsive, no horizontal scroll | Playwright viewport suite | Verified (responsive.spec at 4 widths + touch targets) |
| 34.13 | 404 and empty states render as designed | See §3 above | Implemented: 404 + listing/filter empty states (PublicPagesTest, SiteInvariantsTest) |

## 5. Definition of Done (PRD §35)

| Item | Planned evidence | Status |
|---|---|---|
| Matches PRD structure and function | This matrix + FINAL-REPORT | Verified: see FINAL-REPORT.md |
| Uses shared design system components | `DESIGN-SYSTEM.md`; Phase audits | Verified: all public views compose the documented components (DESIGN-SYSTEM.md §6–7); no one-off pages |
| Pulls content from the content architecture | Hardcoded-content audit | Verified: hardcoded-content audit + arch tests (NFR-08) |
| Passes accessibility, performance, responsive checks | Phase 7 reports | Verified 2026-10-03 (07-QUALITY.md) |
| Reviewed against realistic placeholder content | Demo seeder walkthrough | Verified with demo seed (bracketed placeholders) in browser screenshots and the Playwright suite |
| Approved by site owner before production | **Owner action. Will not be claimed by the build.** | Pending owner |

## 6. Master prompt §0.4 invariants

| Invariant | Planned implementation | Planned verification | Status |
|---|---|---|---|
| §6.2/§6.3 Download CV button in header at all breakpoints; Contact persistent/sticky on long pages | `header` component renders `cv-button` at every breakpoint; `sticky-contact` on project/article detail | Feature: header markup; Playwright at 375px | Implemented: header CV button at all breakpoints, Let's Connect (lg+), sticky contact on project/article detail (SiteInvariantsTest). CV endpoint P6 |
| §6.4 CV + Contact within one tap on mobile | `nav.mobile-cta-bar` below 1024px | Playwright mobile | Implemented: persistent mobile CTA bar (SiteInvariantsTest) |
| §6.6/§6.7 Internal linking; breadcrumbs ONLY on project and article detail | `breadcrumbs` rendered only by those two templates | Feature: present on detail pages, absent elsewhere | Implemented: breadcrumbs + BreadcrumbList only on detail pages; cards → case studies; related; experience → projects; articles → related (SiteInvariantsTest) |
| §9.1 ≤3 hero CTAs, distinct weights, graceful without photo | `hero` rules `max:3`; style enum; no-photo layout variant | Unit (rules) + Feature (no-photo render) | Implemented: validation max:3, view slices to 3, style enum, single-column layout without photo |
| §9.3 Capabilities grid supports 5+ | Auto-fit grid | Playwright with 4/5/7 items | Verified: 7-item grid lays out without breaking (responsive.spec) |
| §9.4 Featured projects from the same source | `featured_projects` resolves `Project` models | Feature | Implemented (data): `featured_projects` resolves Project models (auto or manual, live only) (SectionRendererTest). View P5 |
| §9.8/§17.4 Recommendations hidden at zero; no placeholder testimonials | `shouldRender`; demo seeds zero | Feature 0/1/many | Implemented (data): `recommendations_preview::shouldRender` false at 0; 1/many shown; unconsented never shown (SectionRendererTest); demo seeds zero. View P5 |
| §10.2 Client-side filtering, featured distinguished, empty state, Load more | `filters` module, featured badge/size, `empty-state`, `load-more` | Playwright + Feature | Implemented (Playwright check 2026-10-03; SiteInvariantsTest) |
| §10.3/§11.4 Prev/Next, Back, Related, in-page section nav | Project detail template + `AdjacentProjectResolver` + section nav | Feature | Implemented (SiteInvariantsTest) |
| §11.2 My Contribution vs team | `cs_contribution` two-panel block | Feature | Implemented (data/admin): `cs_contribution` two-panel block with PRD default labels. Styling P5 |
| §11.3 Sections addable/removable/reorderable; absent render nothing | Repeater + `shouldRender` | Feature | Implemented (data/admin): relationship repeater + SaveSections + shouldRender (PageBuilderTest, SectionRendererTest). View P5 |
| §12.2/§12.3 Hybrid timeline, expandable cards, achievements emphasised | `experience.timeline` + `experience.card` (disclosure, `aria-expanded`) | Feature + Playwright keyboard | Implemented: <details> cards (keyboard accessible natively) |
| §13.2 Cohesive narrative About, photo stacking, pull-quote | `narrative` template + blocks | Visual review + Feature | Implemented: narrative template + image_text (stacks) + pull_quote |
| §14.2 No percentage bars or proficiency graphics | No proficiency fields; arch test on views | Arch test | Implemented (schema): `skills` has no proficiency columns; `tests/Arch/ArchitectureTest.php` asserts it. Views pending (P5) |
| §15.2/§17.3/App. A Placeholders obvious; recommendations genuine, consented, attributed | `is_placeholder` treatment; consent gate; source attribution | Feature | Implemented: placeholder certifications render with the dashed placeholder treatment and no link (`Certification::verificationUrl()` null); recommendations need consent to publish (`PublicationConsentMissing`, ContentRulesTest), with the publish action gated in the admin (`ContentActions`); source attribution on cards; demo seeds zero recommendations (SiteInvariantsTest) |
| §16.1 Reading time, related, author, LinkedIn-first sharing | `ReadingTimeCalculator`, resolver, `authors`, `share-links` | Unit + Feature | Implemented (ContentRulesTest, SiteInvariantsTest) |
| §18 External embed, meeting types, fallback, alongside contact form | Booking adapters + `contact_form` block layout | Feature + Playwright | Implemented (BookingTest, ADR-038) |
| §19.2 All states with PRD default copy (CMS-editable) | `ContactSettings` defaults = PRD strings | Feature | Implemented (ContactFormTest) |
| §19.3/§19.4 Honeypot/CAPTCHA, rate limit, labels, aria-live | honeypot, `CaptchaVerifier`, throttle, form components | Feature + axe | Implemented (ContactFormTest; axe clean) |
| §20.1 CV in 5 places; opens for viewing in a new tab first; fallback | `cv-button` → `/cv?from=…` `target=_blank`; download link on the viewer route; fallback | Feature: 5 locations, inline headers | Implemented (CvEndpointsTest, SiteInvariantsTest) |
| §21 Structured-editorial; avoid §21.2 list | Design tokens + component library | Owner design review | Implemented (tokens from the PRD palette, editorial type, no decorative effects). Owner design review pending |
| §25 Mobile-first, breakpoints, 3→2→1, no h-scroll, 44px targets | Tailwind config + components | Playwright viewport suite | Verified (responsive.spec) |
| §29 Every analytics event, location-tagged | `AnalyticsEvent` enum + `data-track` | Catalogue test + Playwright spy | Implemented through the abstraction (AnalyticsSeoTest); provider-side verification is an owner step |
| §31 Anonymised mode; organisation omittable | `OrganisationPresenter` | Leak test across all surfaces | Verified: `OrganisationPresenter` gate (ContentRulesCmsTest) and a leak test across rendered HTML, meta tags, JSON-LD and alt text (SiteInvariantsTest "never leaks an anonymised organisation anywhere"); admin confidentiality tab with warnings |
| §33 Every error/empty state | See §3 | See §3 | Implemented (see §3) |
| §34/§35 Acceptance baseline and DoD | See §4–§5 | FINAL-REPORT | See FINAL-REPORT.md (owner approval pending) |

## 7. Additions after Phase 8 (owner requests and the 2026-10-05 review)

| Requirement | Implementation | Verification | Status |
|---|---|---|---|
| Owner manages colours (site, sections, buttons, cards), fonts, backgrounds, light and dark mode (ADR-047) | Colour roles + `ThemeSettings` / `ThemeStylesheet`, Appearance screen, `SectionAppearance`, `FontCatalogue` | ThemeTest (contrast enforcement, modes, fonts, tamper resistance, section backgrounds, preview); arch test bans raw palette colours; axe on 12 pages in light **and** dark at 2 widths; admin preview e2e | Verified |
| Appearance changes can be previewed before going live | `ThemePreview` (session-scoped, permission-checked, public pages only) | ThemeTest preview tests; `admin-appearance.spec.mjs` (preview visible to the editor only, then exit) | Verified |
| WCAG AA holds for owner-chosen colours (NFR-02) | `ThemePalette::pairs()` checked live and on save; section colour/overlay minimums | ThemeTest; default palettes pass every pair | Verified |
| Backups automated and restorable (NFR, ops) | spatie/laravel-backup nightly, encrypted, retention + monitor (ADR-048) | OperationsTest (schedule, sources); manual run + restore with matching row counts (DEPLOYMENT §9) | Verified locally; production needs `BACKUP_DISKS` with an off-server disk |
| Operator is told about failures | `OperationalAlerts` (errors, failed jobs), backup notifications → `ALERT_EMAIL` | OperationsTest (sent once per window, no 404 alerts, failed jobs) | Verified; needs `ALERT_EMAIL` and an external uptime monitor on `/up` in production |
| Logs and job tables do not grow without limit | `activitylog:clean`, `queue:prune-failed` scheduled; daily logs in production | OperationsTest (schedule) | Implemented |
| Privacy information for contact-form data | Draft Privacy page with prompts + footer link hidden until published | OperationsTest | Structure in place; wording and publishing are the owner's |

