Files
apf_portal/docs/decisions/0027-portal-side-organisational-hierarchy.md
T
julien 04ee535de9
CI / check (push) Successful in 3m28s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 3m3s
CI / a11y (push) Successful in 4m21s
CI / perf (push) Successful in 6m6s
Docs site / build (push) Successful in 5m59s
docs(adr-0028): propose CI/CD + git hosting migration Gitea -> GitLab (#226)
## Summary

Drafts [ADR-0028](docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) as `proposed`: migrate CI/CD + git hosting from Gitea (`git.unespace.com`) to GitLab CE self-hosted on `vm-gitlab` (`10.100.201.10`). The migration was anticipated by [ADR-0015](docs/decisions/0015-cicd-gitea-actions.md) ("level-2 implementation; will be superseded by a GitLab migration ADR within 6-18 months") — that window opens now. **Decision-only PR** — the actual 4-phase migration ships across follow-up PRs after acceptance.

ADR-0028's number was previously a placeholder reference in ADR-0026 and ADR-0027 for the Pléiades + Acteurs+ sync ADR. **Renumbering**: that future sync ADR shifts to `ADR-0029`, and the placeholder links in ADR-0026, ADR-0027 and `CLAUDE.md` update to match — included in the same PR so the chain stays consistent.

## What lands

| File | Change |
| --- | --- |
| `docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md` | **New.** MADR 4.0.0 ADR, `proposed`. Decision = Option B (GitLab CE on `vm-gitlab`). Considered options A (status quo Gitea), C (Forgejo), D (cloud SaaS). Documents what carries over from ADR-0015 (architectural principles unchanged — thin YAML, trunk-based, all-gates-blocking, on-prem runners), what changes (host, pipeline file, runner type, scan tooling), the 4-phase migration sequence, and the signed-commits revisit. |
| `docs/decisions/0026-person-user-portal-data-model.md` | All 11 `ADR-0028` references → `ADR-0029` (sync + facets shifts to 0029). |
| `docs/decisions/0027-portal-side-organisational-hierarchy.md` | All 14 `ADR-0028` references → `ADR-0029`. |
| `docs/decisions/README.md` | New row for ADR-0028 (`proposed`, tags `infrastructure`, `process`, 2026-05-26). |
| `CLAUDE.md` | Roll-up updated: `ADRs 0001 → 0027 accepted; ADR-0028 + ADR-0029 proposed`. ADR-0028's relationship to ADR-0015 spelled out inline ("supersedes ADR-0015's Gitea Actions platform choice — the rest of ADR-0015's architectural principles carry over unchanged"). ADR-0026 + ADR-0027 architecture bullets renumbered 0028 → 0029 to track. |

## Key choices in the ADR

- **What carries over from ADR-0015 vs what changes** — explicit table so future readers see immediately that the migration is **platform-only**, not a re-litigation of CI principles. Trunk-based + squash, all-gates-blocking, thin YAML over portable scripts (`pnpm ci:check` etc. — unchanged), on-prem runners, Conventional Commits in CI + hook (defense in depth) — all carried over. Host, pipeline-file grammar, runner type, and scan tooling are the only things that move.
- **Native security scanning replaces the manual Trivy + gitleaks setup.** GitLab CE's built-in `Dependency-Scanning.gitlab-ci.yml` + `Secret-Detection.gitlab-ci.yml` includes consolidate the ~30 lines of inline `curl + tar` install dance currently in `.gitea/workflows/ci.yml`. Same blocking thresholds (CRITICAL+HIGH dependency vulns, any secret).
- **`vm-gitlab` is already provisioned.** No infra wait — the only sequencing constraint is operator-driven, not infrastructure-driven.
- **4-phase migration, parallel pipelines for ~1 week before cutover.** Phase 1 mirrors repos and bootstraps GitLab side (no `.gitlab-ci.yml` yet — Gitea still gates). Phase 2 lands `.gitlab-ci.yml` alongside `.gitea/workflows/ci.yml` so both pipelines run per PR until parity is confirmed. Phase 3 flips the remote URLs and deletes `.gitea/workflows/` + `infra/ci-runners.compose.yml`. Phase 4 sweeps stale references.
- **Gitea moves to read-only / archive, not decommissioned** at cutover. Existing references to Gitea PRs (`#213`, `#217`, `#219`, …) in commit messages and ADR bodies stay resolvable as historical artefacts. One VM at idle is a low long-term cost.
- **Signed commits revisit.** ADR-0015 noted "signed commits recommended, revisited at GitLab migration". ADR-0028 makes the recommendation: enable required signed commits on `main` once GitLab is live, paired with GnuPG agent forwarding (already documented in `docs/setup/01-dev-debian-vm-setup.md` §8.5). `apf-portal-bot` (Renovate) gets a dedicated signing key at PR 1.

## Renumbering — what moved and why

Before this PR, ADR-0026 and ADR-0027 used `ADR-0028` as a placeholder link for the Pléiades + Acteurs+ sync ADR. That sync ADR hasn't been drafted yet — the number was reserved.

This PR claims `ADR-0028` for the GitLab migration (the immediately-actionable decision), and shifts the sync placeholder to **ADR-0029**. All 25 link references across ADR-0026 (11) and ADR-0027 (14) update in lockstep — replace_all is safe here because in those files `ADR-0028` consistently meant "the sync ADR".

Content of the sync ADR is unchanged — only the number. When that ADR is eventually drafted as `0029-…md`, it gets the existing content reserved for it in the placeholder text.

## Test plan

- [x] `pnpm exec prettier --check` clean on the touched files.
- [x] All `ADR-0028` references in `0026-…md` / `0027-…md` / `CLAUDE.md` now read `ADR-0029` (grep confirms zero remaining references to the old number in those files).
- [x] The new `0028-…md` self-references (status frontmatter, title, internal anchors) are consistent — no leftover `0029`.
- [ ] **Review focus** — drivers / consequences / migration sequence in the ADR; the "what carries over from ADR-0015" table; the renumbering rationale.

## What's next

Per ADR-0028 §"Migration sequence", post-acceptance:

1. **ADR-0028 acceptance PR** — small status-flip, same pattern as #219 (ADR-0026 + ADR-0027 acceptance).
2. **`mirror-and-bootstrap` PR** — `git push --mirror` Gitea → GitLab; GitLab side groups / projects / branch protection / MR templates / deploy keys / Renovate reconfig. No `.gitlab-ci.yml` yet, Gitea pipelines still gate.
3. **`gitlab-ci-pipeline` PR** — `.gitlab-ci.yml` alongside the existing `.gitea/workflows/ci.yml`. Parallel runs ~1 week for parity. GitLab Runner registered on `vm-gitlab`.
4. **`cutover` PR** — remotes flip across docs, `.gitea/workflows/` + `infra/ci-runners.compose.yml` deleted, Gitea read-only.
5. **`cleanup` PR** — stale references sweep, signed-commit policy finalised on `main`.

In parallel — once the dev VM (#220 / #221 / #222 / #223 / #224) is fully bootstrapped — the paused **ADR-0027 PR 1** (Region / Delegation / Structure Prisma schema + inline seed) and **ADR-0026 PR 1** (Person / User / UserScope schema + provisioner) can ship on Gitea; they have no dependency on the GitLab migration and don't need to wait.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #226
2026-05-26 10:30:19 +02:00

212 lines
20 KiB
Markdown

---
status: accepted
date: 2026-05-24
decision-makers: R&D Lead
tags: [data, backend]
---
# Portal-side organisational hierarchy — `Structure` with kind discriminator and nullable FINESS / SIRET
## Context and Problem Statement
[ADR-0025](0025-authorization-model-privileges-roles-scopes.md)'s scope axis dereferences three real-world hierarchy levels: `etablissement:<code>`, `delegation:<dept>`, `region:<insee>`. The BFF guard `principalCoversResource` walks `etablissement → delegation → region` to decide whether a scope covers a resource — that walk needs persisted rows.
[ADR-0026](0026-person-user-portal-data-model.md)'s first draft included those three tables (`Region`, `Delegation`, `Etablissement`) with `Etablissement.finess` as the primary key, on the assumption that every APF structure has a FINESS code and the FINESS doubles as the natural identifier on the wire (URL paths, scope literals).
That assumption is wrong, and was caught before merge. The two APF systems that already model the organisational hierarchy at scale — **`cascade`** (medico-social structure registry + Pléiades/Talentia HR integration) and **`acteurs_plus`** (member + governance registry) — were audited for their source-of-truth shapes. The findings:
- **Cascade explicitly carries seven types of `Structure`** via a discriminator column: `medico_social`, `antenne`, `dispositif`, `entreprise_adaptee`, `mouvement`, `administratif`, `sanitaire`. Three of those (`antenne`, `dispositif`, parts of `entreprise_adaptee`) **do not have a FINESS** by construction — they are branches, programmatic services, or businesses without medico-social registration. Cascade's `Structure` PK is an internal auto-increment; FINESS / SIRET / SIREN / Pléiades `codePaie` / Talentia `codeCompta` are all carried on **separate per-source enrichment rows** (`StructureSourceFiness`, `StructureSourceSirene`, `StructureSourcePleiades`, `StructureSourceTalentia`), nullable and many-to-one against `Structure`.
- **Acteurs_plus does not store FINESS / SIREN / SIRET at all** on its org-hierarchy entities. It uses a portal-internal `code` (unique string) plus an `externalId` integer pointing at upstream registries.
- **Neither system uses INSEE region codes as primary keys** — both have auto-PK rows with `code` carried as a unique attribute. (The portal's choice to make INSEE / dept / FINESS doubly-meaningful as PKs is defensible — only one Region table exists, externally-keyed is cleaner here — but it must not assume the same identifier strategy works for every structure.)
A portal model that pins `Etablissement.finess` as PK therefore **excludes ≥ 30 % of APF's real structure inventory** by construction. The fix is not "make FINESS nullable on Etablissement" — that just smuggles the discriminator into the absence-of-value semantics. The fix is to adopt cascade's discriminator-based model directly.
This ADR specifies the `Region` / `Delegation` / `Structure` schema that the portal persists, and the seeding posture for v1. The portal-side **sync** of these rows from cascade / Pléiades / Acteurs+ is [ADR-0029](#)'s territory.
## Decision Drivers
- **All APF structures must be representable**, not just the medico-social ones. Antennes, dispositifs, entreprises adaptées, mouvement, administratif, siège — every category needs a row.
- **Externally-meaningful identifiers must round-trip** through URLs (`/api/structures/<code>`) and scope literals (`etablissement:<code>`) without server-side translation. For medico-social structures the FINESS already plays this role; for the rest we adopt the cascade/acteurs_plus posture of a portal-internal `code` string.
- **No FK from `UserScope.value` to `Structure.code`.** ADR-0026 already commits to this — the value is an opaque string at the type level so historical scope rows survive structure decommissioning. Validation happens at the admin-UI write path, not at the database level.
- **Cascade is the long-term source of truth**, not the portal. v1 ships a small inline seed (the structures the test tenant exercises) precisely because ADR-0029's cascade sync will overwrite it.
- **Do not replicate cascade's full model in v1.** Cascade carries `Pole` (lateral org grouping), `Service` (sub-units inside a structure), arbitrary self-referencing `structureParent`, and four per-source enrichment tables. None of those are required by the v1 scope axis. Defer to ADR-0029 (which will need them for the sync) or beyond, and accept that the portal's v1 schema is a subset.
## Considered Options
- **Option A — `Etablissement` keyed on FINESS only** (the original ADR-0026 first draft). Discarded — see Context above.
- **Option B — `Structure` with internal `code` PK + `kind` discriminator + nullable FINESS/SIRET (chosen).** Cascade-aligned but simplified: no Pole, no Service, no self-reference, no per-source enrichment tables. One row per structure, with the upstream identifiers as nullable attributes carrying unique indexes when present.
- **Option C — Full cascade replication.** Structure + Pole + Service + arbitrary parent chain + four per-source enrichment tables. Faithful to cascade but the join cost on every scope check is non-zero, and v1 has no consumer for Pole / Service / arbitrary nesting.
- **Option D — No portal-side Structure table; resolve scope literals via a remote call into cascade.** Defers all schema work but adds a remote call to every guarded request — latency, cascade-availability coupling, and audit-log enrichment all degrade.
## Decision Outcome
Chosen option: **B — `Structure` with internal `code` PK + `kind` discriminator + nullable FINESS / SIRET**, because:
1. it covers 100 % of APF's structure categories without smuggling a discriminator into nullable FINESS;
2. it keeps the scope-check hot path local (no remote call to cascade);
3. it leaves a clean extension surface for ADR-0029 — adding `Pole`, `Service`, or per-source enrichment tables is additive, not destructive;
4. it preserves the externally-meaningful round-trip property: for medico-social structures we set `Structure.code = FINESS` at seed time, so `etablissement:0330800013` stays a readable scope literal where it can.
### Schema
```prisma
model Region {
// INSEE region code (2 digits — '11' for Île-de-France,
// '75' for Nouvelle-Aquitaine). Doubles as primary key:
// INSEE codes are short, stable, and externally meaningful.
code String @id
name String
delegations Delegation[]
}
model Delegation {
// French department code (2-3 chars — '33', '2A', '971').
// Same rationale as Region.code.
code String @id
name String
regionCode String
region Region @relation(fields: [regionCode], references: [code])
structures Structure[]
}
model Structure {
// Portal-internal stable code, externally meaningful.
// - For medico-social structures: code = FINESS (9 digits).
// The round-trip from URL (/api/structures/0330800013) and
// scope literal (etablissement:0330800013) stays readable.
// - For non-medico-social structures: code is an APF-internal
// string assigned at seed / admin-UI time. Examples:
// 'siege', 'apf-bdx-merignac', 'ea-toulouse', 'mvt-national'.
// - The code is opaque at the type level; matching against it
// is string equality, not parsing.
code String @id
name String
// Discriminator aligned with cascade's Structure.type. Closed
// set, drift-gated alongside the ADR-0025 catalogues. Legal
// values:
// 'medico_social' — has FINESS, governed by the FINESS registry
// 'antenne' — APF branch office without FINESS
// 'dispositif' — programmatic service without establishment status
// 'entreprise_adaptee' — EA — usually has SIREN/SIRET, no FINESS
// 'mouvement' — APF mouvement (national-level org)
// 'administratif' — administrative entity (e.g. siège regional)
// 'siege' — APF national headquarters
// Extensions ('sanitaire', 'autre', …) are an ADR amendment.
kind String
// FINESS code (9 digits). NULL for non-medico-social structures.
// Unique when present. Cascade's StructureSourceFiness is the
// long-term authoritative carrier; the portal keeps it inline
// on Structure as a denormalised attribute for the v1 scope-axis
// checks. ADR-0029's sync owns the write path.
finess String? @unique
// SIRET (14 chars: 9 SIREN + 5 NIC). NULL when the structure is
// not registered with SIRENE (most antennes, dispositifs).
// Unique when present.
siret String? @unique
// Pléiades payroll code (6 chars). NULL in v1 — populated by
// ADR-0029's Pléiades sync once it ships.
codePaie String? @unique
// Parent delegation. NULL for structures not attached to a
// delegation (siège, mouvement national, …).
delegationCode String?
delegation Delegation? @relation(fields: [delegationCode], references: [code])
@@index([kind])
@@index([delegationCode])
}
```
### Scope-literal interpretation
The ADR-0025 catalogue defines the scope kind name `etablissement`. With ADR-0027's schema, `etablissement:<value>` carries a **`Structure.code`** as `<value>` — not a FINESS specifically. For medico-social structures the two are the same digit-string; for non-FINESS structures the value is the portal-internal code.
The vocabulary mismatch ("scope name `etablissement` referencing any kind of `Structure`") is a known wart inherited from ADR-0025's catalogue, which shipped before the cascade audit reshaped the model. Renaming the scope kind to `structure` is plausible but invasive (catalogue, drift gate, decorator names, all references). The cost is not paid today; the rename is an ADR-0025 amendment if and when a maintainer trips over it. Until then, the matcher contract is documented in code (`apps/portal-bff/src/auth/scope-matcher.ts`).
The `principalCoversResource` matcher receives a `ScopableResource` that has been pre-resolved against the Structure / Delegation / Region tables — it does not consult the database itself. Resolution happens once at request-handler entry: the handler reads `Structure` by code, joins `Delegation` and `Region`, and passes the populated `ScopableResource` to the guard.
### Seeding posture
`Region`, `Delegation`, `Structure` are **reference data**: slow-moving, every environment needs them, none of the rows are environment-specific (until the test-tenant scope-seeding step in ADR-0026 PR 2, which seeds `UserScope` rows pointing at these structures).
**Chosen: inline in the Prisma migration.** The first migration carries the schema + an INSERT block populating the codes APF actually operates on. Every environment that runs `prisma migrate deploy` gets the same starting set; nothing to forget at deploy time; the schema and the rows that make the schema usable land in one atomic step.
The v1 seed is intentionally small — only what the ADR-0026 test-tenant flow needs:
- **Region** Nouvelle-Aquitaine (INSEE `75`) — sole region for v1.
- **Delegation** Gironde (`33`) — sole delegation for v1.
- **Structure** rows: a handful of medico-social établissements (Bordeaux + complexe-merignac with `kind='medico_social'`, FINESS = code), one `siege` (no delegation, no FINESS), one `entreprise_adaptee` placeholder if the test matrix needs it.
The full APF inventory ships once ADR-0029's cascade sync is live. The v1 inline seed is **superseded**, not extended, by the sync — the migration that adds the sync is also responsible for the cleanup truncation if needed.
### What ADR-0027 ships vs adjacent ADRs
| Concern | ADR-0026 (identity) | ADR-0027 (this) | ADR-0029 (sync + facets) |
| ----------------------------------------------------------- | ------------------- | ---------------------- | ------------------------ |
| `Region`, `Delegation`, `Structure` schema | — | ✅ | — |
| Inline reference-data seed for the test tenant | — | ✅ | — |
| Full APF inventory (~ ten thousand structures) | — | — | Cascade-sync population |
| `Pole`, `Service`, arbitrary nesting, per-source enrichment | — | deferred | as ADR-0029's sync needs |
| `Structure.codePaie` populated | — | nullable (column only) | ✅ |
### Consequences
- Good, because every APF structure category fits the schema — antennes and dispositifs are first-class, not absence-of-FINESS edge cases.
- Good, because the scope-axis check stays local (no remote call to cascade on the guard hot path).
- Good, because cascade's eventual sync (ADR-0029) writes into the existing column set — no schema churn at sync time, just a write strategy.
- Good, because the medico-social FINESS round-trip (`/api/structures/0330800013`) and scope-literal readability (`etablissement:0330800013`) are preserved for the structures that have a FINESS.
- Bad, because `Structure.code` semantics differ across kinds — sometimes it's a FINESS, sometimes a portal-internal slug. Operators reading the table need to consult `kind` to interpret `code`. Mitigation: the admin-UI scope-seeding screen ([ADR-0026](0026-person-user-portal-data-model.md) PR 2) shows `(code, name, kind)` together, not just the code.
- Bad, because the schema is a strict subset of cascade's — `Pole` (lateral org grouping) and `Service` (sub-units) are missing. If a v1 consumer asks "show me everyone in the Pôle Santé" the portal cannot answer. Mitigation: documented as deferred; ADR-0029 picks it up alongside the sync.
- Bad, because the scope kind name `etablissement` no longer matches its value semantics (a `Structure.code` of any kind, not just `medico_social`). The matcher contract is documented; rename deferred to a possible ADR-0025 amendment.
- Neutral, because `UserScope.value` has no FK to `Structure.code`. ADR-0026 already commits to this; stale codes are tolerated at runtime and surface as a failed resource match, not a crash.
### Confirmation
- **Migration tests.** Prisma migration emits a SQL file; the `infra/local` dev stack runs it on fresh boot. Verified by seeing the seed rows under `prisma studio` after `prisma migrate dev`.
- **`Structure.kind` enum-as-string is single-sourced.** A constant array in `apps/portal-bff/src/structures/structure-kind.ts` lists the legal values; the catalogue-drift gate ([ADR-0025 §"Confirmation"](0025-authorization-model-privileges-roles-scopes.md)) extends to assert every string written to `Structure.kind` is in the catalogue. Same posture as the `Privilege` / `FunctionalRole` / `Person.source` catalogues.
- **PrismaScopeResolver integration test (jointly with ADR-0026 PR 2).** A persona with `kind='etablissement', value='0330800013'` (a medico-social Structure) and a persona with `value='apf-bdx-merignac'` (a non-FINESS Structure) both successfully cover their respective `ScopableResource` after a single Structure read + Delegation/Region join. Same matcher, both `code` values.
## Pros and Cons of the Options
### Option B — `Structure` with kind discriminator + nullable FINESS / SIRET (chosen)
- Good, because every APF structure category is representable.
- Good, because the round-trip property is preserved for medico-social structures.
- Good, because cascade's eventual sync writes additively, not destructively.
- Bad, because `code` semantics depend on `kind` — operators need to read both.
- Neutral, because the schema is a subset of cascade's. Acceptable for v1; revisit when a consumer needs Pole / Service.
### Option A — `Etablissement` keyed on FINESS only
- Good, because the schema is the smallest possible thing.
- Bad, because it excludes ≥ 30 % of APF's structure inventory (antennes, dispositifs, entreprises adaptées, …) by construction.
- Bad, because making FINESS nullable to fix this smuggles the discriminator into absence-of-value semantics, which is harder to reason about than an explicit `kind` column.
- Bad, because cascade's seven-type discriminator model demonstrably exists upstream — the portal not reflecting it just means the sync (ADR-0029) has to compress / discard information.
### Option C — Full cascade replication
- Good, because the schema is a perfect mirror — no information is lost at sync time, no consumer is ever blocked by an absent table.
- Bad, because the v1 scope-axis check does not need Pole, Service, or self-referencing nesting — the join cost is paid every request for unused capability.
- Bad, because the four per-source enrichment tables (`StructureSourceFiness`, `StructureSourceSirene`, …) reflect cascade's role as the multi-source aggregator; the portal is downstream of cascade, not parallel to it. Owning four sync feeds the portal does not consume is misallocation.
- Neutral, because additive extension from Option B's schema toward Option C's is straightforward — ADR-0029 picks up what its consumers need.
### Option D — Remote read against cascade
- Good, because the portal owns zero structure schema — single source of truth, no drift.
- Bad, because every guarded request becomes a remote call. Latency adds at the 95p; cascade availability is now in the portal's hot path.
- Bad, because audit-log enrichment (which structure was the resource scoped to?) requires the remote call to succeed, on the audit-write hot path that ADR-0013 keeps blocking.
- Bad, because the AI-relay's `PrincipalProjector` ([ADR-0024](0024-ai-service-relay-grpc-sse-bridge.md)) needs to emit structure codes in the AI-side `roles[]` payload — a remote call there means cascade is on the AI-chat hot path too.
## More Information
**Phasing.** This ADR is decision-only. Implementation is sibling to [ADR-0026](0026-person-user-portal-data-model.md) PR 1 — both schema migrations can ship in one PR or split, but the test-tenant scope-seed (ADR-0026 PR 2) needs both to be live.
1. **PR — Prisma schema + inline reference-data seed.** `Region` / `Delegation` / `Structure` models + the v1 inline migration carrying the codes APF's test tenant operates on + `Structure.kind` constants + drift-gate extension for `Structure.kind`. Can ship alongside or after [ADR-0026](0026-person-user-portal-data-model.md) PR 1; the two PRs do not share schema, so order is flexible.
2. ADR-0026 PR 2 then lands on top — the `PrismaScopeResolver` + admin UI scope-seeding screen seeds `UserScope` rows pointing at this ADR's `Structure.code` values.
**Follow-up ADRs.**
- **[ADR-0029](#) — Pléiades + Acteurs+ + cascade syncs + facet schemas.** Specifies the structure-catalogue sync (cascade → portal), the person-catalogue sync (Pléiades + Acteurs+ → portal), the facet shapes (Salarié, Élu, Adhérent, Bénévole, Bénéficiaire, PartenaireExterne), the reconciliation policy between sync-owned and admin-UI-owned fields, and the schema extensions (Pole, Service, per-source enrichment) the sync may need.
- **Possible future ADR-0025 amendment — rename scope kind `etablissement` to `structure`.** Triggered only if the vocabulary mismatch causes confusion in code review or operator-facing UIs; carries a closed-set catalogue rename + drift-gate update + all decorator literals.
**Source-of-truth investigations.** The cascade and acteurs_plus audits that drove this ADR live alongside the project lead's working notes (gitignored). The audit findings summarised in §"Context" are the durable artefacts — the raw notes are not part of the repo.