# kinds/attribute.md — one attribute (a column), after the fact

The playbook `/ba-change` loads for `kind: attribute`. The frame is in
`SKILL.md`; this file carries what is specific to an **attribute** of an
existing entity — the change that reaches the database.

Owner document: `<APP>/<MODULE>/entité.md`, the ONE entity block
(`writeDiscipline: entity-block-splice` — the attribute row inside the table
and the `Traçabilité` line; every other block verbatim). Grammar:
`ba-create-data-model/levels/attributes.md` (type vocabulary, constraints,
`Calculé`), `levels/relationships.md` for a FK. No code to allocate — the
attribute name is its identity within the entity.

## 1. Analyse the request

One grouped AskUserQuestion:

| Question | Why it matters |
|---|---|
| **Which entity**, and **what** is the attribute — its business name, one sentence on what it holds? | the entity must exist (else kind=entity); the name is searched upstream |
| **Type and constraints** — text (length), decimal (precision), date, boolean, enum (its values), Guid to another entity (a FK)? Required, unique, ≥ 0? | a FK drags a relation + lookup grants; an enum with state semantics drags a rule, a UC and a screen |
| **Stored or computed** — is it typed by a user / a job, or derived from other columns (`Calculé` formula)? | a computed column stored is a DM-005 err; a formula is not a column |
| **Where does it show** — list column, detail field, form field, filter, sort? In which phase of the lifecycle is it typed? | the screens and the pagespec deltas; a filtered column wants an index |

## 2. Challenge it

- **Who upstream asks for it?** `report.trace` — the CLI searched every form
  of the name (accents folded, whole word) in the module's use cases and
  rules. **No hit = BLOCKED**: the data model only carries what the upstream
  names (create-data-model prohibition 1 — « the set of entity sources is
  CLOSED »). The honest answer is the missing use case or rule
  (`/ba-change` kind=use-case | kind=business-rule), then this change again.
  The search is lexical — a synonym it cannot see blocks wrongly; the fix is
  the same door.
- **Does it already exist?** `existing.exact` (same name, folded) → nothing to
  add, or an op=modify; `existing.similar` (`ClientId` vs `Client`) → is it
  the same concept?
- **Is it a column or a relation?** « Le client de l'opportunité » is a FK
  `ClientId` + a `**Relations**` entry (`*→1 Client — FK ClientId, scope …`),
  not a text column; `/ba-change` then runs the MANDATORY
  `derive-lookup-grants --mode derive` (RBAC-008).
- **Is it a column or a state?** An enum whose values move (« relancé »,
  « perdu ») is a state: it needs the transition rule (`state-transition` /
  `workflow` with `Flow` lines), the UC that moves it and the screen that shows
  it (XD-001..003, DM-026).
- **Is it a column or a formula?** « Le montant pondéré » = `Amount *
  Probability` is a `Calculé` cell, never stored.
- **Is it a Core / capability concern?** A person's email / name belongs to
  `auth_Users` through the `Personne` link (CODE-007); a file is the
  file-storage capability, never a `varbinary` (DM-019).
- **Will the list still be readable?** ≤ 7 default-visible columns, ≤ 3
  primary filters (PRD-109/110) — decide `priority` / `tier`, not « add
  everywhere ».

## 3. Author

The row, inside the entity's attribute table, and the trace:

```markdown
| DueDate | date | requis | — |
```

- `- **Traçabilité**` += the UC / BR that names it.
- A FK: `- **Relations** :` += `Entity *→1 Target — FK TargetId, scope
  same-module | cross-module (APP/MOD) | core (table), onDelete restrict`.
- A frequently filtered column: `- **Index** :` += `(DueDate)`.
- Splice the ONE block; every other entity block byte-for-byte.
- Then the verify: `expectCode` = the attribute name, `target.entity` = the
  entity, `baselineCount` = `existing.count` (the entity's attribute count).

## 4. Verify

`report.verify.ok` true: the attribute is found on THAT entity, its attribute
count moved by +1 (add) / 0 (modify), no duplicate name, nothing `lost` (a
refused attribute row, an unreadable `**Index**` value, a `**Relations**`
entry the grammar could not read — each is a column or a FK that would vanish
from the schema).

## 5. Propagate — what a new column drags along

1. **FK** — the relation entry, then `derive-lookup-grants --mode derive`
   (the actors need the producer section as a dropdown). DM-013 / RBAC-008.
2. **State** — rule + UC + screen; **computed** — formula; **filtered** —
   index. XD-001..003, DM-026, DM-024, DM-023.
3. **Screens** of the entity — `Colonnes` / `Champs` / `Filtres` where the
   attribute belongs, within the budgets (`data.screens` lists them with
   their pagespec). SCR-016/017/018.
4. **Pagespec delta** of every view of the entity — `columns[]` / `fields[]`
   / `filters[]` in the json block; a FK filter needs `fkTo`
   (`derive-filter-fks --mode check`, then `derive` — it writes); a phased
   field its lifecycle phase (`derive-lifecycle --mode check`); a sectioned
   form its `sections[]`; a summary field the `summary` band. PRD-113 / 111 /
   120 / 134. Never `/ba-create-prd`.
5. **Hand-off** — Phase 1 of `/ba-develop` re-enters on DEV-DOM-002 (the
   attribute is missing on the `.cs`): scaffold-entity regenerates the class
   and the **EF migration goes through `/efcore`** — an additive column is a
   🟢 verdict; a FK / an index is checked by DEV-DAT-008/009.

## 6. Audit

`audit-ba` with dimensions `data-model`, `screens`, `cross-dimension`,
`cross-ref-code` (with `projectRoot`). What an err means here: DM-002 (no
attribute), DM-005 (computed stored), DM-007 (naming), DM-013 (bare Guid),
DM-023 (index on an unknown column), DM-025 (dead column — the trace was
lost), DM-026 (unreachable state), CODE-007 (a person without the auth link).

## Modify variant (op=modify)

- **A retyped or renamed column is a DESTRUCTIVE migration** — `/efcore`
  renders a 🟡/🔴 verdict (narrowing, drop + add): the user decides, you
  announce and never run it. DEV-DOM-002 catches the class drift.
- **Changed enum values** — every old value must still be reachable or
  migrated (DM-026); the kanban board and the form lifecycle read them
  (`derive-kanban-spec`, `derive-lifecycle --mode check`).
- A changed constraint or `Calculé` formula is additive.
- Verify expects delta **0**.
