---
name: ba-audit-cross-ref-code
description: >
  Cross-references the BA data model of a module against the EXISTING project
  code — detects entities that extend an existing class, reference a Core/existing
  entity as FK, duplicate one, collide with the SmartStack Core catalogue,
  re-implement a platform capability (file storage, search, …), or model a
  PERSON without the auth_Users extension link (CODE-007, err — fail-closed
  mirror of C-4/DM-018c), and — ownership at the APPLICATION grain (2026-10-03) —
  reads WHO owns a homonymous class (own module ok, same application other
  module err, another application warn → reuse) and whether the columns asked
  of another application are carried by `<APP>/demande-extension.md`
  (CODE-008, err; pending/refused request = development frozen, warn) — and,
  data sensitivity at the entity grain (lib/ba-sensitivity), whether REACHING
  a ≥ confidentiel entity of another application (cross-module FK, 360 tab,
  4-segment permission) is carried by an ACCESS request in the same document
  (CODE-009, err; pending/refused = frozen, warn)
  (CODE-001..009). Reads the module's `entité.md`, scans `src/*.Domain/**/*.cs`,
  checks the static Core + capability catalogues (no scan needed), writes a verdict to
  `_audit/cross-ref-code.md`. Run after `/ba-create-data-model` or as part of
  pre-dev readiness.
allowed-tools: [Read, Write, Glob, Grep, Bash]  # Bash: scan existing .cs code files
---

# ba-audit-cross-ref-code — BA data model × existing code cross-reference

You cross-reference the conceptual data model of one module (its `entité.md`)
against the **existing source code** of the project, then write a verdict file.
The rules are unchanged from the SmartStack convention; only the I/O is
file-based and the code scan now uses Grep/Bash on the project tree.

## Deterministic engine — how this audit runs

The MECHANICAL rules of this dimension are evaluated by the shared `audit-ba`
CLI (see `/ba-audit-run`) — **never by reading the corpus yourself, never by
spawning per-module subagents** (the 394M-token incident shape). Your only
job here is the judgment residue.

1. **Run the engine, scoped to this dimension**:

   ```bash
   npx --prefer-offline tsx skills/business-analyse/audit-run/cli/audit-ba/index.ts \
     --spec '{"baRoot":".smartstack/ba","scope":{"app":"<APP>","module":"<MODULE>"},"dimensions":["cross-ref-code"],"projectRoot":"."}'
   ```

2. **Exit 3 = parsing suspect -> STOP.** A control counter disagrees with the
   parser (`report.parseControl.perDoc`): fix the doc's form or report the
   parser bug, then re-run. Never « complete by hand » — no green verdict may
   be born from a silent parser.
3. **No judgment rules in this dimension** — a single engine run suffices; the verdict is final.
4. **Chat summary** (3-6 lines, business terms): the PARSE TOTALS (say the
   counts — that is how a « 0 erreur » stays verifiable), err/warn counts,
   remaining judgments, and the fix skill each finding names.

The CLI writes the verdict to `.smartstack/ba/<APP>/<MODULE>/_audit/cross-ref-code.md`
(existing format — anchor, `Verdict :` header, emoji sections; `0 err` =
pass for the downstream gate). The rule texts below remain the AUTHORITATIVE
spec — the CLI registry is drift-tested against them.

## Pre-check — greenfield project (client code only)

Before applying the code-scan rules, check whether the project has existing
domain code:

- `Glob src/*.Domain/**/*.cs` (then `**/Entities/**/*.cs`, `**/Domain/**/*.cs`).
- If **no** entity files are found, the project is greenfield **for client code
  only**: skip CODE-002..004 and emit the trivial-pass note for CODE-001.
- A client project is NEVER greenfield relative to SmartStack Core —
  `core.auth_Users`, `core.tenant_TenantOrganisations`, … always exist via the NuGet,
  invisible to the file scan — so **CODE-005, CODE-006 AND CODE-007 always run** (they
  need no source scan). Say so in the chat summary.

## Scope

- **Module scope** (the only scope): audit one module — apply CODE-001..008 to
  every entity in that module's `entité.md`. Don't scan sibling-module BA models
  except to know which FK targets are already BA-declared.
- **Ownership is read at the APPLICATION grain** (doctrine 2026-10-03). The code
  scan covers the whole project (`src/*.Domain/**/*.cs`), and the folder a class
  sits in — `Entities/<App>/<Module>/`, what `scaffold-entity` writes — is its
  act of ownership. Inside an application the modules SHARE its entities: one
  module declares an entity, the others reference it (`scope cross-module
  (APP/MOD)`), nobody recreates its table. The duplicate check is therefore
  against the OTHER applications of the project. Segments are compared
  letters-and-digits only (`GESTION_FLOTTE` ≡ `GestionFlotte`).
- The verdict file lives at the audited module's `_audit/cross-ref-code.md`.

## Rules

### CODE-001 — Entity already exists in code (ownership at the application grain)
- **Severity**: `err` / `warn` / `ok` per the OWNER of the homonymous class — the
  `Entities/<App>/<Module>/` folder it sits in (`lib/domain-scan`):
  - **own module** → `ok`: the entity's OWN generated class, the output of the
    previous `/ba-develop` run, regenerated from the MCD on the next one. Said as
    such in the evidence ("classe de l'application elle-même"). A module re-audited
    after its development is NOT a collision.
  - **own application, another module** → `err`: a duplicate inside the
    application. One module declares an entity; the others reference it through a
    `scope cross-module (APP/MOD)` relation. The Domain namespace is flat
    (`<ns>.Domain.Entities`): two homonymous classes do not compile. If the
    entity merely changed module, the old file is a ghost (DEV-DOM-006).
  - **another application** → `warn`: the entity is already carried by that
    application — REUSE it (relation `scope cross-module (APP/MOD)`), never
    redeclare it. The evidence names the owner and the attributes this module
    declares IN ADDITION to the class: those extra columns are what an
    **extension request** (`<APP>/demande-extension.md`, CODE-008) asks the
    owner for — the Studio instructs it, and the application's development is
    frozen until it is validated.
  - **no owner** (a class outside `Entities/<App>/<Module>/` — flat legacy
    layout or hand-written code) → `warn`: indeterminable; re-run `/ba-develop`,
    which moves its own classes under their folder, then re-audit.
- Check: for each BA entity, every `class {EntityName}` under `src/*.Domain/**/*.cs`
  with its owner segments; segments compared letters-and-digits only.
- → Fix: `/ba-create-data-model` — on `err`, keep ONE declaring module and turn the
  other declaration into a relation; on the other-application `warn`, replace the
  redeclared entity by a relation and let CODE-008 / `derive-extension-request`
  carry the extra attributes to the owner.

### CODE-002 — Entity references existing entity as FK
- **Severity**: `ok` (informational — confirms the FK target exists in code).
- Check: for each BA relationship whose FK target points to an entity **not** in
  the current module's BA data model, Grep for `class {targetName}` in the `.cs`
  files. If found, the FK target is a valid reference into existing/Core code.
- `ok`: note **`<EntityName>`** → **`<targetName>`** resolves to the existing class
  at `<path>` (a legitimate cross-reference, no action needed).

### CODE-003 — FK target not found in BA or code
- **Severity**: `err` (the FK target entity is not found anywhere).
- Check: for each BA relationship, if the FK target is **not** in the module's BA
  data model **and not** found via Grep in the `.cs` files, the FK is broken.
- `err`: state that the relationship target **`<targetName>`** (referenced by
  **`<EntityName>`**) is not found in the BA data model or in existing code. Why
  it matters: a dangling FK blocks entity/migration generation downstream.
- → Fix: `/ba-create-data-model` (either add the target entity to the data model
  or correct the target name).

### CODE-004 — Duplicate table prefix
- **Severity**: `warn` (a BA entity table prefix collides with existing code), else `ok`.
- Check: for each BA entity that declares a table prefix, Grep for
  `ToTable("{prefix}` in the `.cs` files (EF Core configurations). If found, there
  is a table-name collision risk.
- `warn`: state that the prefix **`<prefix>`** (used by **`<EntityName>`**) is
  already used in `<conflictFile>`. Why it matters: two entities mapping to the
  same table prefix collide at the database level.
- → Fix: `/ba-create-data-model` (choose a distinct table prefix for
  `<EntityName>`).

### CODE-005 — BA entity duplicates a SmartStack Core entity (catalogue check — no code scan)
- **Severity**: `err` on collision, else `ok`.
- Core entity classes ship inside the SmartStack NuGet — they are NOT under
  `src/*.Domain/**/*.cs`, so CODE-001..004 can never see them. This rule checks
  each BA entity name against the static Core catalogue below (drift-tested
  against `lib/core-catalog.ts`). It needs NO source scan and therefore applies
  to greenfield projects too.
- Check: compare each BA entity name (whole token, accent/case/plural-insensitive
  — never substring: `UserStory` is not a hit) against every catalogue name +
  alias, against the reserved table, and against the platform HR module entities
  (below).

<!-- core-catalog:v1 — drift-tested against lib/core-catalog.ts (edit BOTH or the suite fails) -->
| Core entity | Table | Tenant scope | Detect as duplicate (FR/EN aliases) |
|---|---|---|---|
| User | `core.auth_Users` | none | Utilisateur, Usager, AppUser, ApplicationUser |
| Role | `core.auth_Roles` | none | — |
| Tenant | `core.tenant_Tenants` | strict | Locataire |
| TenantOrganisation | `core.tenant_TenantOrganisations` | optional | Organisation, Organization, Société, Entreprise, Compagnie, Company |
| Department | `core.ref_Departments` | optional | Département |
| JobTitle | `core.ref_JobTitles` | optional | Fonction, Poste, JobFunction |
| Office | `core.ref_Offices` | optional | Bureau, Bureaux |
| Language | `core.loc_Languages` | none | Langue |
| Group | `core.auth_Groups` | none | Groupe |
<!-- /core-catalog:v1 -->

<!-- core-reserved:v1 — drift-tested against lib/core-catalog.ts (edit BOTH or the suite fails) -->
| Reserved Core name (aliases) | Not FK-able — use instead |
|---|---|
| Permission (Droit) | IPermissionService (permission resolution) |
| UserSession (Session) | security-internal — never modeled nor FK-ed |
| UserProfile (Profil) | ICoreDataService.GetUserBasicInfoAsync |
| UserPreference (Préférence) | ICoreDataService |
| Setting (Paramètre, Configuration) | platform settings (cfg_) — not a client entity |
| Notification | Core notifications feature (ntf_) |
| Ticket (SupportTicket) | Core support/ticketing feature (tkt_) |
| Workflow | Core workflow feature (wkf_) |
| EmailTemplate (ModèleEmail) | Core email templates (email_) |
| AuditLog (JournalAudit) | Core audit logs — read-only platform feature |
| License (Licence) | Core licensing (lic_) |
| Navigation (Menu) | INavigationService (menu / nav tree) |
<!-- /core-reserved:v1 -->

**Platform HR module (v3.63)** — the platform also ships a full HR module. Its
transactional entities are owned by the built-in `hr` app; a client re-modeling one
is (almost always) rebuilding a platform feature. Check each BA entity name against
this table (whole-token, same matching) and emit a **`warn`** (not `err` — a client
MAY run without the HR module, but should EXTEND rather than fork):

<!-- platform-hr-entities:v1 — drift-tested against lib/platform-catalog.ts (edit BOTH or the suite fails) -->
| Platform HR concept | Platform table | Detect as duplicate (FR/EN aliases) |
|---|---|---|
| Absence | `core.hr_AbsenceRequests` | Congé, AbsenceRequest, DemandeAbsence, LeaveRequest, Leave |
| AbsenceType | `core.hr_AbsenceTypes` | TypeAbsence, TypeCongé |
| TimeEntry | `core.hr_TimeEntries` | SaisieTemps, Imputation, Pointage, TimeLog |
| Timesheet | `core.hr_TimesheetWeeks` | FeuilleDeTemps, FeuilleTemps, TimesheetWeek |
| ContractType | `core.hr_ContractTypes` | TypeContrat |
<!-- /platform-hr-entities:v1 -->

- `warn`: **`<EntityName>`** overlaps the platform HR concept **`<Concept>`**
  (`core.hr_…`). Why it matters: the `hr` app already provides absences / time
  entries / timesheets; a parallel `extensions` table forks HR data. Prefer
  EXTENDING the `hr` app (new modules under it) or plugging projects/mandates into
  the time-entry-refs seam (`AddExtensionTimeEntryRefs`) — see the data-layer skill.
  `Employee` is deliberately NOT flagged here: it is a person-extension
  (`PERSON_TRIGGER`) — a client owns its own `Employee` FK-ing `core.auth_Users`.
- → Fix: `/ba-create-data-model` (drop/rename, or declare the HR extension intent).

- `err`: **`<EntityName>`** collides with Core **`<CoreEntity>`**
  (`core.<table>`). Why it matters: the Core table already exists (e.g.
  `tenant_TenantOrganisations` is the shared organisation directory); generating
  a parallel `extensions` table forks the data with no way to reconcile. The
  module must reference Core — `Relations … scope core` FK for the 9 whitelisted
  entities, `ICoreDataService` / the named service for reserved names — never
  recreate it.
- → Fix: `/ba-create-data-model` (remove or rename the entity; declare the
  `scope core` reference).

### CODE-006 — BA entity re-implements a platform CAPABILITY (capability-catalogue check — no code scan)
- **Severity**: `warn` (capability semantics without the canonical pattern),
  `ok` (metadata entity conforming to the pattern — say so).
- Platform capabilities are SERVICES shipped inside the NuGet (file storage,
  global search, time-entry refs, outbound email) — they carry no entity name
  CODE-005 could match and are invisible to any source scan. This rule checks
  each BA entity (name + attributes) against the static capability catalogue
  below (drift-tested against `lib/capability-catalog.ts`). It needs NO source
  scan and therefore applies to greenfield projects too — **always runs**, like
  CODE-005.
- Check: compare each BA entity name — whole-token OR the TRAILING word of a
  compound name (`InteractionDocuments` → `Documents` = hit; `Documentation` is
  a single word, never a hit) — against the entity/section triggers, and each
  attribute name/type against the attribute triggers.

<!-- platform-capabilities:v1 — drift-tested against lib/capability-catalog.ts (edit BOTH or the suite fails) -->
| Capability | Entity/section/tab triggers (FR/EN) | Attribute triggers (name or type) | Socle provides | Use instead — canonical extension pattern | Reference |
|---|---|---|---|---|---|
| file-storage | Document, Attachment, PièceJointe, Pièce jointe, Pièces jointes, Fichier, File, GED, DMS, Justificatif, Annexe, Scan, Upload, Téléversement, Media, Média, Photo | binary, blob, varbinary, byte[], image, filestream, fileContent, fileData, contenu, contenuFichier | IFileStorageService (SmartStack.Application.Common.Interfaces) — Scoped via AddSmartStack, injectable from any extension handler/controller; StorageType Normal/Legal; Local + Azure Blob (config shipped in every generated appsettings) | Client METADATA entity in extensions.* (FileName, StoredFileName, ContentType, FileSizeBytes + parent FK) + IFileStorageService for the bytes + dedicated AUTHENTICATED upload/download endpoints. NEVER binary content in the DB, never raw disk I/O outside the service. | `development/backend/data-layer/references/file-storage.md` |
| global-search | GlobalSearch, RechercheGlobale, Recherche globale, SearchIndex, SearchEngine, Moteur de recherche, Moteurs de recherche, Index de recherche | — | AddExtensionSearch<ExtensionsDbContext> (socle search seam) — extension entities plug into the platform global search | Register searchable entities through the search seam (scaffold-extension-search) — never a client-built search index or engine. | `development/backend/data-layer/references/global-search.md` |
| time-entry-refs | Bookable, Imputable, TimeEntryRef, TimeEntryTarget | — | AddExtensionTimeEntryRefs<ExtensionsDbContext> — client entities become bookable targets of the platform HR time module | Register the entity via the time-entry-refs seam (scaffold-time-entry-refs) — never re-model time entries (PLATFORM_HR_ENTITIES / CODE-005 covers those names). | `development/backend/data-layer/references/time-entry-refs.md` |
| code-generation | Sequence, Séquence, Sequences, Compteur, Counter, Numerotation, Numérotation, Numbering, CodePattern, Code pattern, Allocator, Allocateur, NumberSequence, CodeSequence | nextValue, nextNumber, nextSeq, lastValue, lastNumber, prochainNumero, dernierNumero, compteur | ICodedEntity + ICodeKeyDescriptor registered via AddSmartStackCodeKey<T>() — the shared CodedEntitySaveHandler allocates the Code atomically at insert on core.seq_Sequences (UPDLOCK/SERIALIZABLE = gapless by default), scope Tenant/Global, reset None/Yearly/Monthly/Daily; the key surfaces in Administration → Configuration → Code patterns, where a CodePattern DB row only OVERRIDES the built-in default. No seed, no migration | Declare the `**Code pattern**` on the entity itself (format with {SEQ:n} + scope + reset + gapless) and register the key through scaffold-coded-entity — the socle allocates. NEVER model a counter/sequence/allocator entity, a nextValue column or a client numbering service: the gapless guarantee and the admin-side retuning are the platform's. | `development/backend/data-layer/references/coded-entities.md` |
| email-sending | EnvoiEmail, EmailSortant, OutgoingEmail, EmailQueue, Mailing | — | IEmailService (Scoped) + Core email templates (email_) | Send mail through IEmailService with Core email templates — never a client SMTP client, outbound-mail table or template store. | — |
<!-- /platform-capabilities:v1 -->

- The client METADATA entity is **LEGITIMATE** — contrast CODE-005, where the
  duplicate is removed. Here the finding steers the SHAPE and the downstream
  implementation, never asks to delete the entity.
- `warn` when the capability semantics are matched but the pattern intent is
  absent — e.g. a binary-content attribute, no storage-metadata fields, or a
  generic "GED"/engine/index entity. Message shape: "**`<EntityName>`** relève
  du **stockage fichiers** : le socle fournit `IFileStorageService` — garder
  l'entité comme MÉTADONNÉES (`FileName`, `StoredFileName`, `ContentType`,
  `FileSizeBytes` + FK parent), octets via le service, endpoints dédiés
  authentifiés (jamais `varbinary`, jamais l'`api/files` anonyme)."
- `ok` when the metadata shape is present — note "conforme au pattern
  `IFileStorageService`" so the dev phase knows the intent is deliberate.
- → Fix: `/ba-create-data-model` (align the entity with the capability's
  `Use instead` pattern; reference doc in the table).

### CODE-007 — Every PERSON entity is an extension of `auth_Users` (fail-closed mirror of C-4/DM-018c)

- **Severity**: `err` (a person entity with no `**Personne**` declaration or no
  User link — and no sanctioned override), `ok` otherwise.
- The rationale (decision of 2026-08-27): as soon as an entity REFERENCES a
  person, it must be an extension of `core.auth_Users` — that FK is what wires
  the row to everything hanging off User (login, roles, tenant memberships,
  HR record, notification targeting, "Me" shortcuts). A person directory
  decorrelated from auth_Users produces accounts that cannot log in, do not
  appear in user administration and cannot receive alerts « au porteur ».
  C-4 (create-data-model) is a PROPOSE at authoring time and DM-018c audits
  the BA doc; THIS rule is the a-posteriori mirror on the CODE side — the net
  under both when the deterministic leg misses (a métier word absent from
  PERSON_TRIGGERS) and the model's judgment was the only cover.
- Check, for every BA entity of the module:
  1. **Person signal** — the entity name matches a `PERSON_TRIGGERS` word
     (whole-token, singular-tolerant, accent-insensitive —
     `lib/core-catalog.matchPersonTrigger`; mirrored in create-data-model's
     `person-triggers:v1` block) OR the entity declares ≥2
     `PERSON_IDENTITY_ATTRIBUTES` (`email`, `firstName`, `lastName`,
     `displayName`).
  2. **Declaration** — a triggered entity carries a `- **Personne** :` line
     (`mandatory` / `optional`), or the sanctioned decorrelation override
     `- **Personne** : none — décision client : <raison>`. Silence = `err`.
  3. **Link** — `mandatory`/`optional` modes carry the paired
     `Relations … *→1 User — FK UserId, scope core (auth_Users)` entry; when
     the backend exists, Grep the Domain `*.cs` for the generated leg
     (`public Guid`/`Guid?` `UserId` + a `User` navigation on the entity's
     class). A declaration whose code half is absent is `err` (the modeling
     was right and the scaffold dropped it — re-run scaffold-entity with the
     `scope core` relation).
- **ok** findings name the verified entities ("`Driver` — Personne mandatory,
  FK `UserId` → auth_Users présent en code").
- → Fix: `/ba-create-data-model` (author the `**Personne**` line + the User
  relation — or the explicit `none` override), then re-run Phase 1 for the
  entity.

## Output

Write `_audit/cross-ref-code.md` per the doc-templates skeleton:
- Header `# Audit cross-ref-code — <APP> / <MODULE>` + `_<date> · Verdict :
  <emoji> N warn · M err · K ok_`.
- `## ✅ Conforme`, `## ⚠️ Avertissements`, `## ❌ Bloquants` sections; one bullet
  per finding. For `warn`/`err`: what's wrong (offending entity/prefix codes
  **bold**), why it matters, and a `→` fix naming `/ba-create-data-model`.
- Re-Write the whole file each run (overwrite — it's a fresh verdict).

Then a 3–6 line chat summary in the user's language — business terms, not rule
codes. State whether the data model lines up with the existing code (extensions
correctly flagged, FK targets resolved) and call out any `err` (a broken FK that
must be fixed before development). If the project is greenfield (no existing
code), say so and that the audit passes trivially.

## Used by the readiness orchestrator

`/ba-audit-pre-dev` runs every dimension and aggregates the verdicts. When invoked
by it, still write `_audit/cross-ref-code.md` as usual — the orchestrator reads
these files.

### CODE-008 — Extra columns on another application's entity carry an extension request
- **Severity**: `err` when a module declares, on an entity ANOTHER application
  owns, attributes that application does not carry (its generated class under
  `Entities/<Other>/…`, or its `entité.md` when not developed yet) and
  `<APP>/demande-extension.md` carries NO block for the pair — or a PENDING
  block that lacks the newer columns; `warn` while a request of the
  application is « en attente » or « refusée » (the development of the WHOLE
  application is frozen — `ba-develop-plan/preflight-develop-plan` skips its
  modules; the Studio owns the `Statut`); `ok` when covered / validated, or
  when nothing is asked.
- The doctrine (2026-10-03): inside an application the modules SHARE its
  entities; across applications an entity is REUSED, never redeclared, and the
  extra columns a module needs are ASKED of the owner through the request —
  a projection of the requester's own MCD (`lib/ba-extension-requests`,
  grammar in `_workflow/doc-templates.md`): the attribute rows verbatim, the
  Description + Traçabilité as the motive, `Statut : en attente` at birth.
  The Studio instructs the request in the owning project and writes the
  Statut; the CLI never does. A collision with NO extra column is pure reuse
  (CODE-001 says so) and asks nothing. Two applications extending the same
  Core artefact is DM-034's judgment, never a mechanical request.
- The BA leg (the owner's `entité.md`) runs without `projectRoot`; the code
  leg (the owner's generated class) joins it when the app exists.
- → Fix: `derive-extension-request --mode write` (lane `derive` — /audit-fix
  executes it: it only transcribes); then the Studio. A refused request is
  never re-asked mechanically; a validated block lacking newer columns yields
  a NEW block.

### CODE-009 — Reaching another application's sensitive entity carries an access request
- **Severity**: `err` when a module of application A REACHES an entity of
  application B that resolves ≥ `confidentiel`
  (`lib/ba-sensitivity.resolveEntitySensitivity` on B's `entité.md` — the
  full tree, a scoped run still sees it) and `<APP>/demande-extension.md`
  carries NO block of nature `accès` for the (entity, B) pair; `warn` while
  such a request is « en attente » or « refusée » (the development of the
  WHOLE application is frozen — the same preflight gate as an extension
  request; the Studio owns the `Statut`); `ok` when covered / validated, or
  when the module reaches no sensitive entity elsewhere.
- Three triggers, all mechanical (`lib/ba-extension-requests.expectedAccessRequests`):
  (a) a `*→1` / `1→1` relation `scope cross-module (B/MOD)` onto the entity;
  (b) a declared 360 related tab onto it (`app` token, else the single foreign
  module declaring the entity — an ambiguous target is XD-005's business);
  (c) a HUMAN rbac row with an app-qualified 4-segment path whose
  (app, module, section) resolves to the entity's section (list screen of the
  owner application — read from disk when the scoped corpus did not load it —
  else the kebab-plural rung). Same application = the modules share the
  entity (doctrine 2026-10-03), never a request.
- The block is a PROJECTION like an extension's: `- **Nature** : accès`, the
  owner's `- **Sensibilité**` copied, the motive a statement of fact per
  trigger (`accès en lecture via FK …`, `via l'onglet lié « … » de SCR-…`,
  `via la permission \`…\``), no attribute table. The owner's CONSENT is the
  ask — nothing is added to its entity. Extension and access on the same
  entity are two blocks; CODE-008 reports the extension nature only.
- → Fix: `derive-extension-request --mode write` (lane `derive` — /audit-fix
  executes it: it only transcribes); then the Studio. Or remove the reach
  (the FK, the tab, the row) when the module does not need the data.
