# Data sensitivity in the generated code — what the tag enforces, what it does not

The BA classifies an entity's DATA with one line in `entité.md`
(`- **Sensibilité** : <niveau> — <catégories>`, SSOT `lib/ba-sensitivity.ts`,
closed vocabulary `public | interne | confidentiel | restreint` × `personnel`,
`personnel-sensible`, `sante`, `juridique`, `rh`, `financier`, `commercial`,
`securite`; doctrine in `business-analyse/create-data-model/levels/attributes.md`
§ Sensibilité). This reference says, surface by surface, what the generated
code does with a ≥ `confidentiel` entity — and, just as loudly, what it does
NOT do, so no skill ever promises a protection the platform does not have.

## The one doctrine the code relies on — the SPLIT

The platform has **no field-level masking**: the generated `ListDto` /
`DetailDto` carry EVERY user field (`lib/field-read-surface.ts`), and the socle
itself protects HR data by SPLITTING the confidential attributes into their own
tables behind their own permission family (`HrEmployee` stays internal;
remuneration / payroll / contract sit behind fail-closed query filters —
`RemunerationUnlocked`, `PayrollUnlocked`, `ContractUnlocked`). A sensitive
attribute therefore lives in a DEDICATED entity, with its own section and its
own `rbac.md` rows (DM-037 asks for exactly that) — the tag then protects that
entity's surfaces. A tag on a column would be a promise the code cannot keep,
which is why none exists.

## What the generators apply (≥ `confidentiel`)

| Surface | Generator | Behaviour | Gate |
|---|---|---|---|
| List search (`GetAllAsync`) | `scaffold-business` (`sensitivity` field, threaded from `derive-sensitivity-specs`) | `LIKE` on the DISPLAY FIELD ONLY — never every string column (a free-text probe would read a salary through the search box); a `// Sensitivity: <niveau — catégories>` trace line precedes the search block | DEV-API-035 `list-search-wide` |
| Lookup label (`{E}RefDto.DisplayName`) | `scaffold-business` | unchanged contract `(Id, DisplayName)`; a display field that IS a sensitive attribute (IBAN, salary…) is REFUSED — validate error, then a fail-closed throw | DEV-API-035 `lookup-label-sensitive` |
| Global search | `scaffold-extension-search` (`searchIn` / `displayTitle`, derived by `build-spec` from `sensitivities`) | `confidentiel` → `.SearchIn(e => e.<Display>)` + `.DisplayTitle(e => e.<Display>)`; `restreint` → NOT registered (`excluded[]`, a warning says so) | DEV-API-035 `global-search-wide` / `global-search-restricted-registered`, DEV-API-029 exempts the restricted entity |
| Public API (`/api/v1/export`) | `external-api/cli/derive-external-api-spec` | `restreint` refused; `confidentiel` only with the dated `décision client` facet on the `**API externe**` line (controlled `success:false` otherwise) | DEV-XAPI-015, DM-038 |
| 360 related tabs | `ba-develop/cli/derive-related-tabs-data` | the « first 4 attributes of entité.md » fallback is refused on a sensitive target — the target's list pagespec decides the columns | XD-009 (BA side) |
| Anonymous endpoints | — | `[AllowAnonymous]` is legitimate ONLY on a `public` entity | DEV-API-035 `anonymous-on-non-public` |
| Pages | `scaffold-component` (pagespec `sensitivity` key) | a discreet « données confidentielles » badge on list / detail / form, theme tokens, i18n floor in the 4 locales — informative | — |
| Documentation | `documentation/extract-doc` | Section 2 « Accès & rôles » states the module's level and categories | — |

Upstream, the BA guardrails hold the ACCESS: a read-type `rbac.md` row on a
sensitive section needs a use case naming the actor (RBAC-011), a derived
lookup grant onto a sensitive producer waits for a human row (RBAC-012), and a
module of another application reaching the entity files an ACCESS request the
Studio validates (CODE-009).

## What is NOT done — never claim it

- **No field-level masking, no partial DTO.** Every read surface carries every
  column of the entity. Split the attribute (DM-037).
- **No audit of READS.** The platform traces writes (`CreatedBy` / `UpdatedBy`,
  the purpose-specific change logs), exports (`DataExportAuditLog`), access
  denials (`AccessDeniedAuditService`) and impersonation sessions — nothing
  records « who read which payslip ». The generated controllers log nothing.
- **No step-up re-authentication on read.** `MfaStepUpScope` has no read scope
  and `StepUpBehavior` only covers the MediatR pipeline — the generated
  controller calls its service directly, the behaviour never runs.
- **No log masking, no column encryption.** `IDataProtector` protects secrets
  (keys, connector credentials), not business columns; Serilog has no
  destructuring policy.
- **Sort and authored filters are unchanged.** The sort whitelist and the
  pagespec `contains` filters are authored by the BA; they are not the search box.
- **`ss upgrade` cannot infer a sensitivity** from existing code: an existing
  project keeps its wide search until the BA tags the entity and the seam is
  re-run.

## Platform evolutions this reference waits for (SmartStack.app — not CLI work)

1. A generic READ audit for sensitive entities — an extension seam
   (`AddExtensionReadAudit<TContext>(audit => audit.Entity<T>()…)`) writing a
   `SensitiveReadAuditLog`; model to generalise:
   `Platform/DataExport/DataExportAuditLog.cs`, service neighbour
   `Services/Security/AccessDeniedAuditService.cs`.
2. `MfaStepUpScope.SensitiveRead` + an MVC filter twin of `StepUpBehavior`
   (`Identity/Mfa/MfaStepUpScope.cs`, `Common/Behaviors/StepUpBehavior.cs`,
   `Common/Authorization/RequireStepUpAttribute.cs` — `AttributeTargets.Class`,
   MediatR only).
3. Serilog / OTel destructuring mask of tagged properties
   (`Api/Program.cs` — `Enrich.FromLogContext`, `SmartStackEnrichmentProcessor`).
4. `IAiSensitiveTermProvider` fed by the tag — an extension provider streaming
   the display values of sensitive entities to the AI pseudonymiser
   (`Common/Interfaces/AI/IAiSensitiveTermProvider.cs`, `AiVariableSensitivity`).
5. An extension seam for fail-closed locks — generalise
   `IHrRemunerationAccess.UnlockAsync(purpose)` into
   `AddExtensionSensitiveLock<TContext>(lock => lock.Entity<T>().Purpose(…))`
   mounted as a named EF filter (`Persistence/Extensions/SmartStackExtensionDbContext.cs`).
6. A default search policy — `ExtensionSearchProvider` discovers every string
   column when `SearchFields` is null; a `SearchPolicy.DisplayOnly` opt-in on
   `AddExtensionSearch` would make the safe default the default.

Until those land, the honest sentence in every skill is: *the tag restricts
the SEARCH surfaces and the DERIVATION paths; the protection of the data
itself is the split into a dedicated entity + permission family, and the
need-to-know in `rbac.md`.*
