---
name: voices-ssot-doc-sync
description: >-
  After code or config work in a thread: reconcile canonical docs with the new truth (SSOT + DRY),
  update or stub drift, run targeted doc gates. Owner slash: /ssot. Complements 355, contract-router, grill→PRD.
disable-model-invocation: true
---

# Doc SSOT sync (`/ssot`)

**Doel:** Aan het eind van een taak (of op expliciet **`/ssot`**) controleren of **`docs/`** en **gelinkte canon** nog **één waarheid** met de **repo** vormen: **geen dubbele of tegensprekende** “SSOT”-verhalen, **geen** verouderde paden/API-semantiek, **pointers** i.p.v. copy-paste van rules of stack-tabellen (**381**, **386**).

**Enige slash-entry:** **`.cursor/commands/ssot.md`** wijst hierheen.

## 1) Scope bepalen (dezelfde beurt)

1. **Gesprek:** welke routes, tabellen, features of files noemde de owner of de agent?
2. **Bewijs:** waar mogelijk **`git diff`**, **`git status`**, of expliciete padlijst uit de thread — geen brede “hele repo” zonder anker.
3. **Thema-cluster:** minstens één primaire as (Agency perf, orders, admin-route, Voiceglot, portfolio, …) zoals in **contract**-tabel — **`.cursor/skills/contract/SKILL.md`** §1 mag als **router** dienen zonder de hele skill te herhalen.

## 2) Waar is de SSOT voor dit thema?

**Leesroute (kort, in volgorde tot je het juiste anker hebt):**

- **Index + drift-guard:** `docs/01-FOUNDATION/00-INDEX.md` · `docs/01-FOUNDATION/BLAUWDRUK-EN-INDEX-DRIFT-PROTOCOL.md`
- **Doc-lagen (geen tweede woordenboek-inhoud dupliceren):** `docs/01-FOUNDATION/DOCUMENTATIE-LAAGEN-EN-TERMEN.md` §4–§6 (hub vs satelliet)
- **Worlds / hubs:** `docs/01-FOUNDATION/WORLD-ECOSYSTEM-SSOT.md` · `docs/01-FOUNDATION/WORLDS-AND-MARKETS-CANON.md`
- **Rollen, capabilities, account-zones, junction:** `docs/02-ARCHITECTURE/07-ROLES-CAPABILITIES-WORLDS.md` (o.a. `account_guest`, migraties 0472/0473, chips, `/account`-redirects)
- **Code-first waarheid:** de **gewijzigde** bestanden + **genummerde rule** die het domein lockt (bijv. **311** orders, **334** markt, **342** flight)

**Harde scheiding (geen doc-fout als “SSOT” framen):**

- **`docs/FORENSIC-*`**, **`docs/reports/`**, historische annexen = **bewijs / casuïstiek**; normatieve keten hoort in **canon**-docs of **rules** — zie **DOCUMENTATIE-LAAGEN-EN-TERMEN** §2 “Forensisch”.
- **Prijzen / BTW / tariefsemantiek:** normatief alleen **`docs/VOICES-PRICING-SSOT.md`** (**374**); andere `docs/**/*.md` blijven **€-vrij** behalve uitgezonderde paden in die rule. **Actor-basis (BSF / video / IVR / live) vs platform-JSON:** zelfde hub, plus **`/ssot`**-checklist **§8f** (geen tweede “platform baserates”-verhaal in andere docs).

## 3) DRY-check (wat je zoekt)

| Symptoom | Actie |
| :--- | :--- |
| Zelfde stack-semver of Next/React-claims in **twee** docs | Eén canoniek bestand; de andere wordt **stub + link** of verwijst naar **`apps/web/package.json`** + **005** §0 |
| Zelfde procedure (deploy, push, admin-bridge) volledig gekopieerd | **Stub** naar **AGENTS.md** / **runbook** / **skill** — geen derde volledige kopie |
| Oude **padnamen** (`/backoffice/…`, verkeerde admin-URL) | Vervang door canon uit **355**, **090**, **`docs/admin-paginas-localhost.md`** (regen na **`npm run docs:admin-localhost`** als admin-routes wijzigden) |
| Twee docs die elkaar **tegenspreken** op gedrag | Kies **één** leidende doc per **DOCUMENTATIE-LAAGEN** §6b; andere: **Update (datum)** + doorverwijs |

## 4) Wanneer wél editen

- **355:** routes, operator-flows, SSOT-paden, env- of scriptnamen die in **root `package.json`** staan — **dezelfde taak** meenemen wanneer code dat al raakte.
- **Kleine, veilige correcties:** kapotte interne links, verkeerde file-paden, “removed” stub naar vervanger.
- **Geen** nieuwe lange architectuur-essays in `docs/` als **alleen** een rule al canoniek is — voeg **pointers** toe.

## 5) Wanneer níet uitweiden

- **900 / locks:** geen **semantische** “doc-fix” die locked code of DB-contract anders **zou laten klinken** dan de implementatie; dan **issue + owner** i.p.v. stille herschrijving.
- **Stop & Overleg** (**002** §4): puur conceptuele vraag zonder uitvoerintentie — geen **`/ssot`**-doc-sweep als vervanging van antwoord.

## 6) Gates (proportioneel)

- Wijzigingen onder **`docs/01-FOUNDATION/`** of connecties-tabel in **AGENTS:** **`npm run verify:foundation-drift-trio`** of **`npm run verify:foundation-docs-ssot-gate`** (zwaarder) — namen SSOT: **root `package.json`**, **GATES-AND-NPM-SCRIPTS-LEGEND.md**
- **`apps/web`-pad of stack genoemd in docs:** **`npm run audit:cursor-rules`** als je stack-claims in `.cursor/` of docs raakt die met **006** botsen
- Admin-URL-lijst: **`npm run docs:admin-localhost`** wanneer **`/admin/**`** routes in dezelfde thread wijzigden (**355** §2)

## 7) Eindrapport (verplicht)

1. **Thema / scope** (kort).
2. **Canonieke doc(s)** die nu leidend zijn.
3. **Wat je wijzigde** (paden) of **“geen drift”** met **waarom** (één zin, bewijs: grep/snippet).
4. **Open punten** alleen als objectief buiten scope (lock, owner-besluit, ontbrekende canon).

## 8b) Thema-anker: webmail en postvak (operator UX)

Wanneer de thread **webmail-entry hosts**, **postvak-lijstfilters**, **sneltoetsen** of **welkomst op webmail** raakt:

| Wat | Waar (canon) |
| :--- | :--- |
| Menselijke SSOT + padentabel | `docs/03-LOGIC-AND-FLOWS/49-UNIFIED-ADMIN-CONTACT-CHANNELS.md` (sectie webmail / postvak) |
| Code-anker (grep, geen tweede prose) | `apps/web/src/lib/mailbox/mailbox-webmail-operator-ux-ssot-pointers.ts` (`MAILBOX_OPERATOR_UX_SSOT_DOC`, `MAILBOX_USER_LINK_AND_VAULT_DEDUPE_SSOT_DOC`, `MAILBOX_WEBMAIL_OPERATOR_UX_CODE_POINTERS`) |
| IMAP/agent-toegang (ander concern) | `docs/03-LOGIC-AND-FLOWS/MAILBOX-AGENT-EN-IMAP-TOEGANG.md` · rule **326** |

**`/ssot`-checklist (minimaal):** doc 49-tabelrijen ↔ pointers-bestand ↔ gewijzigde `apps/web`-files; geen nieuwe hardcoded hosts buiten `voices-webmail-entry-host.ts` + env.

## 8c) Thema-anker: admin order — klant-intake (tokenlink)

Wanneer de thread **admin orderdetail**, **klant-intake-accordion**, **tokenlink** of **`order-customer-intake`** raakt:

| Wat | Waar (canon) |
| :--- | :--- |
| Menselijke SSOT + operator-uitleg (wat wél/niet) | `docs/03-LOGIC-AND-FLOWS/ORDER-AND-COMMERCE-ECOSYSTEM-SSOT.md` §1 — rij **Admin orderdetail: klant-intake (tokenlink)** |
| Zichtbaarheid op detail (geen tweede “mand bewerkbaar”-set) | `apps/web/src/lib/admin/admin-order-customer-intake-visibility.ts` (`shouldShowAdminOrderCustomerIntake`) |
| Gedeelde NL-toelichting (kaart + voorgestelde mailzin) | `apps/web/src/lib/orders/order-customer-intake-copy.ts` (`ORDER_CUSTOMER_INTAKE_EXPLAIN_NL`) · mail: `apps/web/src/lib/mail/order-customer-intake-snippet.ts` |
| UI-ankers | `OrderCustomerIntakeCard` · `AdminOrderDetailRightColumnAccordion` (`#admin-order-sidebar-intake-panel` wanneer gerenderd) |
| Optionele E2E (twee order-id’s) | `apps/web/tests/e2e/admin-order-customer-intake-visibility.spec.ts` — env `PLAYWRIGHT_ADMIN_ORDER_ID_INTAKE_VISIBLE` / `PLAYWRIGHT_ADMIN_ORDER_ID_INTAKE_HIDDEN` naast `E2E_ADMIN_AK_TOKEN` |

**`/ssot`-checklist (minimaal):** hub-rij ↔ visibility-helper ↔ copy-module ↔ geen tegenstrijdige prose elders over “factuur-intake”.

## 8d) Copy-review keten (menselijke proza in `docs/`)

Wanneer de thread **inhoudelijke copy** in **`docs/`** raakt (nieuwe normatieve zinnen, hub-teksten, pitches):

| Stap | Wat |
| :--- | :--- |
| 1 | **`/copy`** (**`voices-copy-review`**) — toets tegen **303** / **336** / **367**, nut voor lezer, geen dubbele “marketing-SSOT”; zie **`.cursor/skills/copy-review/SKILL.md`**. |
| 2 | **`/ssot`** (dit document) — DRY met **DOCUMENTATIE-LAAGEN**, geen tweede stack-legenda, juiste hub-pointers (**`VOICEGLOT-AND-CUSTOMER-COPY-ECOSYSTEM-SSOT`** §0–§1 waar copy-SSOT ter zake is). |
| 3 | Foundation-rand: wijzigingen onder **`docs/01-FOUNDATION/`** → proportionele gate(s) in **§6** hierboven. |

**Prijs/BTW in docs:** **374** — normatieve €-proza alleen **`docs/VOICES-PRICING-SSOT.md`**.

## 8i) Thema-anker: etalage-juistheid / “100%” / owner ja-contract (geen nee-voorop)

Wanneer de thread **juistheid**, **garantie**, **100% dekking**, **NL-lek op voices.fr** of **“kan dit nooit meer?”** raakt:

| Wat | Waar (canon) |
| :--- | :--- |
| **Owner-antwoordvolgorde (agents)** | **`docs/02-ARCHITECTURE/VOICEGLOT-AND-CUSTOMER-COPY-ECOSYSTEM-SSOT.md`** **§0g** (ja + methode; geen nee-voorop) |
| **Technische keten** | Zelfde hub **§0–§1**, **§0c** (NL-lek, fingerprints), rij *Uniforme nultolerantie-keten* |
| **Programma 100% ambitie** | **`.cursor/plans/prd-voiceglot-100-percent-coverage-and-system.md`** |
| **Agents-start** | **`.cursor/AGENTS.md`** *(2026-05-27 voiceglot-ja-contract)* |

**`/ssot`-checklist (minimaal):** geen nieuw “garantie-essay” elders in `docs/`; verwijs naar **§0g**; geen tegenspraak met **353** (geen valse security/100%-belofte — wel operationeel ja-contract).

## 8h) Thema-anker: merkstem / tone of voice (owner vs agent)

Wanneer de thread **klantcopy**, **mail-/offerte-tekst**, **`defaultText`**, **anti-slop**, of **“klinkt dit als AI?”** raakt:

| Wat | Waar (canon) |
| :--- | :--- |
| Stijlwet (em dash, bingo, zinslengte) | rule **303** |
| Geen jargon op etalage | rule **336** |
| Voiceglot-only + `defaultText`-reserve | rule **367** · hub **`VOICEGLOT-AND-CUSTOMER-COPY-ECOSYSTEM-SSOT`** §0–§1 |
| **Owner-merkstem vs agent-drift** (relatie eerst, geen dubbele scripts in `quote_message`, voorbeeld paid-social) | **`docs/02-ARCHITECTURE/72-VOICES-BRAND-VOICE-OWNER-VS-AGENT-SSOT.md`** |
| Review-workflow | **`/copy`** → **`/tone-of-voice`** · skills **`copy-review`**, **`tone-of-voice`** |
| Gate | **`npm run check:ui-copy-hygiene`** |

**`/ssot`-checklist (minimaal):** geen nieuwe normatieve TOV-proza buiten **303** + **§72**; hub §1-rij *Tone of voice* ↔ **72** ↔ gewijzigde copy in code; paid-social offerte-bericht ↔ **`paid-social-telephony-quote-message.ts`**.

## 8e) Thema-anker: Pexels featured (FAQ + blog, ontbrekend beeld)

Wanneer de thread **featured image ontbreekt**, **Pexels**, **blog factory**, **`featured_image_id`**, **`pexels_id`**, of **admin blog images** raakt:

| Wat | Waar (canon) |
| :--- | :--- |
| Menselijke SSOT + Next Image wrapper-keten | **`docs/02-ARCHITECTURE/IMAGES-VOICES-NEXT-IMAGE-SSOT.md`** (remote/Pexels-paragraaf + § *Blog hub vs CMS article hero*) |
| Publieke artikelhero: zelfde `src`-keten als `/blog` hub | **`apps/web/src/lib/content/resolve-cms-blog-article-featured-image-url.ts`** (`resolveCmsBlogArticleFeaturedImageUrlForHubParity`) |
| Gedrag, env, FAQ-batch, blog-routes | **`.cursor/rules/324-ADMIN-PEXELS-FEATURED-TOOL.mdc`** |
| Implementatie (zoeken, ranken, Storage, `media`) | **`apps/web/src/lib/blog/pexels-featured-service.ts`** |
| Admin UI blog | **`/admin/blog/images`** · `BlogFeaturedImageManager` |
| One-shot server pipeline (query uit artikel → autopick → featured) | **`POST /api/admin/blog/pexels/attach-featured`** (`apps/web/src/app/api/admin/blog/pexels/attach-featured/route.ts`) |
| Forensische CLI FAQ+blog gaps (`npm run backfill:public-featured-gaps`) | **`scripts/forensic-backfill-public-featured-gaps.ts`** |
| Publieke blog-hero admin (ingelogd) | **`CmsArticleHeroSurface`** + **`BlogArticleHeroAdminFeaturedModal`**; API **`POST /api/admin/blog/[articleId]/featured-media`** |

**`/ssot`-checklist (minimaal):** doc **`IMAGES-VOICES-NEXT-IMAGE-SSOT`** (incl. hub-pariteit §) ↔ rule **324** ↔ lib (`pexels-featured-service`, `resolve-cms-blog-article-featured-image-url`); geen tweede “Pexels als algemene image-fill” buiten FAQ/blog-scope.

## 8g) Thema-anker: woordenboek / vindbaarheid (glossary + internal links)

Wanneer de thread **woordenboek**, **`faq_terms`**, **`article_terms`**, **findability-dekking**, **gap-queue**, **seed:faq-article-terms**, of **“handwerk” bij synoniemen** raakt:

| Wat | Waar (canon) |
| :--- | :--- |
| Menselijke SSOT + engine + geen owner-handwerk | **`docs/02-ARCHITECTURE/71-GLOSSARY-AND-INTERNAL-LINK-ENGINE-SSOT.md`** (§2.2, §8, **§9**) |
| Gap-triage + plafond uitleg | **`npm run report:findability-gap-triage`** · **`scripts/lib/findability-gap-triage-pg.ts`** |
| Admin snapshot | **`GET /api/admin/findability/coverage`** · `loadFindabilityGaps()` · etalage-hub |
| Seed / primary | **`scripts/lib/seed-faq-and-article-terms-pg.ts`** · **`glossary-term-text-match.ts`** |
| Link-injector (LOCK) | **`apps/web/src/lib/content/apply-internal-links-to-html.ts`** — geen doc-wijziging die UNLOCK vereist |
| npm-legenda | **`docs/01-FOUNDATION/GATES-AND-NPM-SCRIPTS-LEGEND.md`** (kop *Woordenboek / vindbaarheid*) |

**`/ssot`-checklist (minimaal):** hub §8–§9 ↔ `package.json` scripts ↔ geen doc die “owner moet seed/migratie zelf draaien” zegt; **handwerk = agent voert slim uit** (**014**/**019**/**021**).

## 8f) Thema-anker: acteur-basistarieven vs `pricing_config` (geen platform-BSF)

Wanneer de thread **`pricing_config`**, **`SlimmeKassaConfig`**, **`parsePricingConfigPayload`**, **`ResolvedRateCard`**, **`PricingResolver`**, admin **Tarieven** (`/admin/rates`), **IVR-basis**, **live-regie-toeslag**, of **strip actor-base keys** raakt:

| Wat | Waar (canon) |
| :--- | :--- |
| Menselijke SSOT + sleuteltabel + pipeline | **`docs/VOICES-PRICING-SSOT.md`** (o.a. § *Commercial mediatypes — Supabase ↔ app*, § *Strict acteur-context vs `pricing_config`*, § *`pricing_config` — sleutels*, § *Stroom*) |
| Masterconfig-rij `app_configs.pricing_config` (geen per-acteur basisprijzen in JSON) | **`docs/02-ARCHITECTURE/03-MASTER-CONFIG.md`** §1D |
| Canonieke platform-sleutels (geen `basePrice` / `videoBasePrice` / `telephonyBasePrice` / `liveSessionSurcharge`) | **`apps/web/src/lib/pricing/pricing-config-precedence.ts`** (`SLIMME_KASSA_CONFIG_PAYLOAD_KEYS`, `PRICING_CONFIG_ACTOR_EXCLUSIVE_JSON_KEYS`, `stripActorExclusiveKeysFromPricingConfigJson`) |
| Ratecard met centen + `sources` | **`apps/web/src/lib/engines/pricing-resolver.ts`** · **`apps/web/src/lib/engines/pricing-engine.ts`** (`ResolvedRateCard`, `SlimmeKassa`) |
| DB-strip legacy keys | migratie **`0549`** (npm: **`db:apply:0549-pricing-config-strip-actor-base-keys`** wanneer jullie runbook dat voorschrijft) |

**`/ssot`-checklist (minimaal):** geen doc of comment die beweert dat **bindende** acteur-basis-euro’s uit **`pricing_config`** komen; één verhaal: **platform = drempels/BTW/Johfrai/Portfolio-constanten uit canonieke sleutels**; **acteur-basis = `actor_rates` → `ResolvedRateCard`**. Bij twijfel: grep in `docs/` op `basePrice` / `videoBasePrice` / `telephonyBasePrice` / `liveSessionSurcharge` naast **`VOICES-PRICING-SSOT`** en fix drift met pointer naar deze hub.

## 8) Relatie tot andere workflows

- **`/contract`:** kiest **domein + skill** vóór bouw of bij twijfel; **`/ssot`** **harmoniseert docs** ná werk (of op verzoek).
- **`/grill-me` → PRD:** productbesluiten landen in **`.cursor/plans/`**; **`/ssot`** zorgt dat **bestaande** `docs/` niet blijven beweren iets anders.
- **`014`:** zelf uitvoeren; geen owner-homework om “even docs te lezen” als jij ze in de workspace kunt patchen. Bij **vindbaarheid/woordenboek:** ook geen owner-handwerk voor seed/migratie/SQL — **`71-GLOSSARY-AND-INTERNAL-LINK-ENGINE-SSOT.md`** §9.

**804:** geen `git push` zonder expliciete owner-opdracht.

Managed by **Chris** (integriteit) + **Consuela** (geen parallelle waarheid) + **Bob** (welke doc leidend is).
