Available specs
!find docs/use-cases -mindepth 1 -maxdepth 1 -type d -name 'UC-*' 2>/dev/null | sort
Empty above → none yet, run /use-case-design first. (find, not an ls glob: under zsh an unmatched glob
aborts the command before any fallback runs.)
Target
$ARGUMENTS
Persistence Architect
Designs how the aggregate reaches disk and comes back: what tables exist, what
columns and types, what indexes, what migration, what queries, and what the datasource
needs configured. What domain-modeling left as an output port, this skill gives body
to.
Entry rule: without 10-dominio.md, there's nothing to persist. This skill reads
docs/use-cases/UC-NNN-<slug>/00-caso-de-uso.md and 10-dominio.md and treats them as a
contract. Without the domain partial, it stops and tells you to run /domain-modeling —
designing tables before the aggregate has a boundary produces a schema that describes the
form, not the business.
Exit rule: it writes only under docs/. It emits 20-persistencia.md, and the
migration SQL goes inside it as a code block with its target file name and path —
never as a file under src/. The migration file and the Java classes come from the
executor agent, which reads the partial and the templates/ exemplars. Inherits D15 —
@.claude/decisions/0003-skill-domain-modeling.md.
Rule rule: the rules don't live here. Lazy fetch, ddl-auto: validate, migration
immutability, open-in-view: false, and the rest are
@.claude/rules/persistence.md. This skill applies them and cites them; it doesn't
reproduce them.
How it's invoked
Two paths, and both matter: /persistence-architect by hand, or chained by
/new-feature once that orchestrator exists. That's why it does not carry
disable-model-invocation — that field hides the skill from the model, and a skill the
model can't see is a skill the orchestrator can't call.
The guard against out-of-order firing isn't the frontmatter: it's the entry rule
above. Without the previous partial, the skill stops and says what needs to run first.
Recorded in @.claude/decisions/0007-pipeline-skills-invocation.md.
Why this isn't a subagent
It's a procedure whose step 3 goes back to the user to ask what no earlier spec fixes —
which database engine, which queries the use case actually makes, what row volume is
expected. A subagent doesn't see the conversation. Decision recorded in
@.claude/decisions/0005-persistence-rule-and-design.md.
Boundary with neighboring skills
The division is by moment and artifact, not technology:
| Piece |
When it acts |
What it produces |
use-case-design |
Before the domain exists |
00-caso-de-uso.md — boundary and canonical names |
domain-modeling |
After the mother spec |
10-dominio.md — aggregate, invariants, ports |
| this skill |
After the domain partial |
20-persistencia.md, migration SQL inside it |
rest-api-architect |
Before this one |
30-rest.md — transport, plus the schema requirements it creates |
test-architect |
After all of them |
40-testes.md |
If the aggregate has no written invariants yet, it isn't this skill. If the problem is a
slow query in code that already exists and there's no new use case, skip steps 1 and 2
and go straight to step 6 (diagnosis) — references/sql-tuning.md.
Procedure
Read the specs. 00-caso-de-uso.md and 10-dominio.md from the folder in
$ARGUMENTS. Without the second, stop. Extract: aggregate root, fields and types,
value objects, declared output ports, and the component table with NEW/CHANGE/REUSE
state. Also read 30-rest.md when the use case has an HTTP trigger — in /new-feature it
always exists by now, because REST runs first. Its block 4 Schema requirements list
is input to this pass: the shared idempotency table (step 4a) and anything else there
gets designed now, not in a second pass. An HTTP-triggered case whose 30-rest.md
doesn't exist yet → stop and tell the caller to run /rest-api-architect first.
Survey what already exists. Look for entities, repositories, and migrations in
the project. A table that already exists gets altered; it isn't recreated. The
mother spec's REUSE state overrides intuition. Same check for idempotency_keys: it's
shared by the whole application, not per use case — if a prior pass already created
it, this one reuses it and doesn't touch it again.
ls db/migration/ src/main/resources/db/migration/ 2>/dev/null
grep -rln "@Entity" --include='*.java' src/ 2>/dev/null
grep -rl "idempotency_keys" db/migration/ src/main/resources/db/migration/ 2>/dev/null
Interview — only what the specs don't fix. AskUserQuestion, at most 4 questions
per call. Don't re-ask what 00-caso-de-uso.md, 10-dominio.md, or 30-rest.md already answered —
the collection's growth, above all.
| Axis |
Decides |
| Engine and version (Postgres, MySQL, Oracle, H2 only in tests) |
Column types, migration syntax, whether CONCURRENTLY exists |
| Expected table volume in 12 months |
Whether the index is mandatory or premature, whether SEQUENCE beats IDENTITY |
| Queries the use case actually makes, and which fields it filters by |
Indexes, projections, what needs JOIN FETCH |
| Concurrency on the same aggregate |
Optimistic lock (@Version) or none |
| Retention and deletion |
Physical DELETE or a status column |
| Table already exists in production |
Mandatory expand/contract, destructive migration deferred |
Design the schema. One table per aggregate root; child entities of the same
aggregate go into their own tables with a foreign key to the root. A simple value
object becomes a column or @Embeddable, never its own table — if it needs its own
table and its own identity, it wasn't a value object
(@.claude/rules/value-objects.md). Fix in writing, column by column: name, type,
nullability, default, UNIQUE, foreign key. A type that isn't Hibernate's default
inference for the field (char(n), smallint) names its @JdbcTypeCode in the
same row, and an assigned id names Persistable — both per
@.claude/rules/persistence.md § Mapping and § Identity and keys.
4a. Idempotency, when 30-rest.md requires it and step 2 found no
idempotency_keys table yet. Not per-aggregate: one table, shared by every
endpoint that needs Idempotency-Key, modeled once and reused afterward. Shape in
templates/IdempotencyKeyTable.sql.example (schema) and
templates/IdempotencyKeyStore.java.example (entity, Spring Data repository, and the
adapter implementing the port), plus templates/IdempotentExecution.java.example (the
application component that owns the two-transaction shape) — the transactional half
of the mechanism whose structural half is rest-api-architect's IdempotencyKeyInterceptor.java.example.
Column set and TTL floor come from @.claude/rules/api-rest.md § Idempotency; don't
redecide them here.
Fix the migration in the partial. Name per @.claude/rules/persistence.md
§ Migrations, with <N> following the highest one found in step 2, and the target
path under the module the blueprint gives the persistence role. The SQL goes as a
fenced sql code block in block 4 of 20-persistencia.md, headed by that path. Shape in
templates/V1__create_table.sql.example. One migration per logical change; never
edit one already applied. Don't create the .sql file — anything under src/
belongs to the executor, which materializes it from this block.
Fix the queries and access plan. For each output port in 10-dominio.md: the
query, the fetch strategy, the index that serves it, and whether it's paginated.
Every collection read inside a loop is an N+1 and is resolved here, not in code
review — symptom catalog and fixes in references/sql-tuning.md.
Fix the configuration. The datasource and JPA properties for this project, from
templates/application-persistence.yml.example. The mandatory values are the rule
(@.claude/rules/persistence.md § Configuration); what this skill decides is sizing
— pool size, timeouts, batch_size — based on the volume answered in step 3.
Write the partial. docs/use-cases/UC-NNN-<slug>/20-persistencia.md, from
templates/persistence-spec.md.example. Five blocks, all mandatory.
Check the engine has a container. grep -A2 "^services:" docker-compose.yml
for the engine chosen in step 3. Missing (and the engine isn't H2) → invoke
docker-architect with this UC's folder, so the dev-time container matches the
schema just designed. Don't edit docker-compose.yml here — that skill is its
single owner.
Report and stop. Path of the partial written, divergences from 10-dominio.md,
whether docker-architect ran, and what's missing for the folder to be complete
(30-rest.md, 40-testes.md). Don't invoke anyone else.
What the partial contains
Five blocks. An empty block is written as "none" — deleting it hides a question nobody
asked.
| Block |
Fixes |
Form exemplar |
| Schema |
Tables, columns, types, nullability, UNIQUE, foreign keys, indexes |
V1__create_table.sql.example |
| Mapping |
Aggregate → persistence entity, field by field; what's @Embeddable; value object translation; @JdbcTypeCode and Persistable where they apply |
JpaEntity.java.example |
| Adapter and ports |
Each port from 10-dominio.md, the query serving it, the fetch strategy |
RepositoryAdapter.java.example · SpringDataRepository.java.example |
| Migrations |
New files, order, and the expand/contract pair when the table already exists |
V1__create_table.sql.example |
| Configuration |
Datasource and JPA properties, with the decided value and why |
application-persistence.yml.example |
Idempotency (only when 30-rest.md requires Idempotency-Key) |
The shared table, entity, repository, adapter, and application component — modeled once, reused by every later use case |
IdempotencyKeyTable.sql.example · IdempotencyKeyStore.java.example · IdempotentExecution.java.example |
The exemplars in templates/ are reference for form, not files to copy. It's the
executor agent that reads them when generating code.
Contract
Reads docs/use-cases/UC-NNN-<slug>/00-caso-de-uso.md and 10-dominio.md
(mandatory — stops without the second), @.claude/rules/persistence.md,
@.claude/rules/architecture-ddd.md (Adapters and Composition sections),
@.claude/rules/naming.md, @.claude/rules/error-handling.md,
@.claude/rules/lombok.md, @.claude/rules/value-objects.md,
@.claude/rules/api-rest.md § Idempotency (only when step 4a applies), and the active
blueprint's packages.map. Also reads 30-rest.md for an HTTP-triggered case —
mandatory then, its schema requirements are this pass's input.
Writes docs/use-cases/UC-NNN-<slug>/20-persistencia.md. Nothing else — the
migration SQL lives inside it.
Never writes under src/. Not a migration, not a class, not a property file. The
executor materializes every file there from this partial.
Does not write Java code. The entities, adapters, and repositories come from the
executor agent.
Does not edit docker-compose.yml. When step 9 finds the chosen engine has no
container yet, it invokes docker-architect instead of writing the service block
itself — single owner, see that skill's Contract.
Does not decide the use case boundary (00-caso-de-uso.md), the domain model
(10-dominio.md), the transport (30-rest.md), or the tests (40-testes.md). Doesn't
touch .claude/rules/**.
Does not collide with domain-modeling: that one declares the output port, this one
says how it's served. The port's signature belongs to the other; if it needs to change,
report the divergence instead of rewriting it.
1---2name: persistence-architect3description: Designs the persistence layer of an already-modeled use case — tables, aggregate mapping, migrations, queries, indexes, and datasource configuration — into the `20-persistencia.md` partial. Use when the request involves modeling the database, mapping an aggregate to JPA, writing or reviewing migrations, deciding indexes and keys, diagnosing N+1 or slow queries, or tuning the pool and datasource properties. Piece of the `/new-feature` pipeline: requires `10-dominio.md` in the given folder and stops without it.4---56## Available specs78!`find docs/use-cases -mindepth 1 -maxdepth 1 -type d -name 'UC-*' 2>/dev/null | sort`910Empty above → none yet, run `/use-case-design` first. (`find`, not an `ls` glob: under zsh an unmatched glob11aborts the command before any fallback runs.)1213## Target1415$ARGUMENTS1617---1819# Persistence Architect2021Designs **how the aggregate reaches disk and comes back**: what tables exist, what22columns and types, what indexes, what migration, what queries, and what the datasource23needs configured. What `domain-modeling` left as an output port, this skill gives body24to.2526**Entry rule: without `10-dominio.md`, there's nothing to persist.** This skill reads27`docs/use-cases/UC-NNN-<slug>/00-caso-de-uso.md` and `10-dominio.md` and treats them as a28contract. Without the domain partial, it stops and tells you to run `/domain-modeling` —29designing tables before the aggregate has a boundary produces a schema that describes the30form, not the business.3132**Exit rule: it writes only under `docs/`.** It emits `20-persistencia.md`, and the33migration SQL goes **inside it** as a code block with its target file name and path —34never as a file under `src/`. The migration file and the Java classes come from the35executor agent, which reads the partial and the `templates/` exemplars. Inherits D15 —36`@.claude/decisions/0003-skill-domain-modeling.md`.3738**Rule rule: the rules don't live here.** Lazy fetch, `ddl-auto: validate`, migration39immutability, `open-in-view: false`, and the rest are40`@.claude/rules/persistence.md`. This skill applies them and cites them; it doesn't41reproduce them.4243## How it's invoked4445Two paths, and both matter: `/persistence-architect` by hand, or chained by46`/new-feature` once that orchestrator exists. That's why it does **not** carry47`disable-model-invocation` — that field hides the skill from the model, and a skill the48model can't see is a skill the orchestrator can't call.4950The guard against out-of-order firing isn't the frontmatter: it's the **entry rule**51above. Without the previous partial, the skill stops and says what needs to run first.52Recorded in `@.claude/decisions/0007-pipeline-skills-invocation.md`.5354## Why this isn't a subagent5556It's a procedure whose step 3 goes back to the user to ask what no earlier spec fixes —57which database engine, which queries the use case actually makes, what row volume is58expected. A subagent doesn't see the conversation. Decision recorded in59`@.claude/decisions/0005-persistence-rule-and-design.md`.6061## Boundary with neighboring skills6263The division is by **moment and artifact**, not technology:6465| Piece | When it acts | What it produces |66|---|---|---|67| `use-case-design` | Before the domain exists | `00-caso-de-uso.md` — boundary and canonical names |68| `domain-modeling` | After the mother spec | `10-dominio.md` — aggregate, invariants, ports |69| **this skill** | After the domain partial | `20-persistencia.md`, migration SQL inside it |70| `rest-api-architect` | Before this one | `30-rest.md` — transport, plus the schema requirements it creates |71| `test-architect` | After all of them | `40-testes.md` |7273If the aggregate has no written invariants yet, it isn't this skill. If the problem is a74slow query in code that already exists and there's no new use case, skip steps 1 and 275and go straight to step 6 (diagnosis) — `references/sql-tuning.md`.7677## Procedure78791. **Read the specs.** `00-caso-de-uso.md` and `10-dominio.md` from the folder in80 `$ARGUMENTS`. Without the second, stop. Extract: aggregate root, fields and types,81 value objects, declared output ports, and the component table with NEW/CHANGE/REUSE82 state. Also read `30-rest.md` when the use case has an HTTP trigger — in `/new-feature` it83 always exists by now, because REST runs first. Its block 4 `Schema requirements` list84 is input to this pass: the shared idempotency table (step 4a) and anything else there85 gets designed now, not in a second pass. An HTTP-triggered case whose `30-rest.md`86 doesn't exist yet → stop and tell the caller to run `/rest-api-architect` first.87882. **Survey what already exists.** Look for entities, repositories, and migrations in89 the project. A table that already exists gets altered; it isn't recreated. The90 mother spec's REUSE state overrides intuition. Same check for `idempotency_keys`: it's91 shared by the whole application, not per use case — if a prior pass already created92 it, this one reuses it and doesn't touch it again.9394 ```bash95 ls db/migration/ src/main/resources/db/migration/ 2>/dev/null96 grep -rln "@Entity" --include='*.java' src/ 2>/dev/null97 grep -rl "idempotency_keys" db/migration/ src/main/resources/db/migration/ 2>/dev/null98 ```991003. **Interview — only what the specs don't fix.** `AskUserQuestion`, at most 4 questions101 per call. Don't re-ask what `00-caso-de-uso.md`, `10-dominio.md`, or `30-rest.md` already answered —102 the collection's growth, above all.103104 | Axis | Decides |105 |---|---|106 | Engine and version (Postgres, MySQL, Oracle, H2 only in tests) | Column types, migration syntax, whether `CONCURRENTLY` exists |107 | Expected table volume in 12 months | Whether the index is mandatory or premature, whether `SEQUENCE` beats `IDENTITY` |108 | Queries the use case actually makes, and which fields it filters by | Indexes, projections, what needs `JOIN FETCH` |109 | Concurrency on the same aggregate | Optimistic lock (`@Version`) or none |110 | Retention and deletion | Physical `DELETE` or a status column |111 | Table already exists in production | Mandatory expand/contract, destructive migration deferred |1121134. **Design the schema.** One table per aggregate root; child entities of the same114 aggregate go into their own tables with a foreign key to the root. A simple value115 object becomes a column or `@Embeddable`, never its own table — if it needs its own116 table and its own identity, it wasn't a value object117 (`@.claude/rules/value-objects.md`). Fix in writing, column by column: name, type,118 nullability, default, `UNIQUE`, foreign key. A type that isn't Hibernate's default119 inference for the field (`char(n)`, `smallint`) names its `@JdbcTypeCode` in the120 same row, and an assigned id names `Persistable` — both per121 `@.claude/rules/persistence.md` § Mapping and § Identity and keys.122123 **4a. Idempotency, when `30-rest.md` requires it and step 2 found no124 `idempotency_keys` table yet.** Not per-aggregate: one table, shared by every125 endpoint that needs `Idempotency-Key`, modeled once and reused afterward. Shape in126 `templates/IdempotencyKeyTable.sql.example` (schema) and127 `templates/IdempotencyKeyStore.java.example` (entity, Spring Data repository, and the128 adapter implementing the port), plus `templates/IdempotentExecution.java.example` (the129 application component that owns the two-transaction shape) — the transactional half130 of the mechanism whose structural half is `rest-api-architect`'s `IdempotencyKeyInterceptor.java.example`.131 Column set and TTL floor come from `@.claude/rules/api-rest.md` § Idempotency; don't132 redecide them here.1331345. **Fix the migration in the partial.** Name per `@.claude/rules/persistence.md`135 § Migrations, with `<N>` following the highest one found in step 2, and the target136 path under the module the blueprint gives the persistence role. The SQL goes as a137 fenced `sql` code block in block 4 of `20-persistencia.md`, headed by that path. Shape in138 `templates/V1__create_table.sql.example`. One migration per logical change; never139 edit one already applied. **Don't create the `.sql` file** — anything under `src/`140 belongs to the executor, which materializes it from this block.1411426. **Fix the queries and access plan.** For each output port in `10-dominio.md`: the143 query, the fetch strategy, the index that serves it, and whether it's paginated.144 Every collection read inside a loop is an N+1 and is resolved here, not in code145 review — symptom catalog and fixes in `references/sql-tuning.md`.1461477. **Fix the configuration.** The datasource and JPA properties for this project, from148 `templates/application-persistence.yml.example`. The mandatory values are the rule149 (`@.claude/rules/persistence.md` § Configuration); what this skill decides is sizing150 — pool size, timeouts, `batch_size` — based on the volume answered in step 3.1511528. **Write the partial.** `docs/use-cases/UC-NNN-<slug>/20-persistencia.md`, from153 `templates/persistence-spec.md.example`. Five blocks, all mandatory.1541559. **Check the engine has a container.** `grep -A2 "^services:" docker-compose.yml`156 for the engine chosen in step 3. Missing (and the engine isn't H2) → invoke157 `docker-architect` with this UC's folder, so the dev-time container matches the158 schema just designed. Don't edit `docker-compose.yml` here — that skill is its159 single owner.16016110. **Report and stop.** Path of the partial written, divergences from `10-dominio.md`,162 whether `docker-architect` ran, and what's missing for the folder to be complete163 (`30-rest.md`, `40-testes.md`). Don't invoke anyone else.164165## What the partial contains166167Five blocks. An empty block is written as "none" — deleting it hides a question nobody168asked.169170| Block | Fixes | Form exemplar |171|---|---|---|172| Schema | Tables, columns, types, nullability, `UNIQUE`, foreign keys, indexes | `V1__create_table.sql.example` |173| Mapping | Aggregate → persistence entity, field by field; what's `@Embeddable`; value object translation; `@JdbcTypeCode` and `Persistable` where they apply | `JpaEntity.java.example` |174| Adapter and ports | Each port from `10-dominio.md`, the query serving it, the fetch strategy | `RepositoryAdapter.java.example` · `SpringDataRepository.java.example` |175| Migrations | New files, order, and the expand/contract pair when the table already exists | `V1__create_table.sql.example` |176| Configuration | Datasource and JPA properties, with the decided value and why | `application-persistence.yml.example` |177| Idempotency (only when `30-rest.md` requires `Idempotency-Key`) | The shared table, entity, repository, adapter, and application component — modeled once, reused by every later use case | `IdempotencyKeyTable.sql.example` · `IdempotencyKeyStore.java.example` · `IdempotentExecution.java.example` |178179The exemplars in `templates/` are **reference for form**, not files to copy. It's the180executor agent that reads them when generating code.181182## Contract183184**Reads** `docs/use-cases/UC-NNN-<slug>/00-caso-de-uso.md` and `10-dominio.md`185(mandatory — stops without the second), `@.claude/rules/persistence.md`,186`@.claude/rules/architecture-ddd.md` (Adapters and Composition sections),187`@.claude/rules/naming.md`, `@.claude/rules/error-handling.md`,188`@.claude/rules/lombok.md`, `@.claude/rules/value-objects.md`,189`@.claude/rules/api-rest.md` § Idempotency (only when step 4a applies), and the active190blueprint's `packages.map`. Also reads `30-rest.md` for an HTTP-triggered case —191mandatory then, its schema requirements are this pass's input.192193**Writes** `docs/use-cases/UC-NNN-<slug>/20-persistencia.md`. Nothing else — the194migration SQL lives inside it.195196**Never writes under `src/`.** Not a migration, not a class, not a property file. The197executor materializes every file there from this partial.198199**Does not write Java code.** The entities, adapters, and repositories come from the200executor agent.201202**Does not edit `docker-compose.yml`.** When step 9 finds the chosen engine has no203container yet, it invokes `docker-architect` instead of writing the service block204itself — single owner, see that skill's Contract.205206**Does not decide** the use case boundary (`00-caso-de-uso.md`), the domain model207(`10-dominio.md`), the transport (`30-rest.md`), or the tests (`40-testes.md`). Doesn't208touch `.claude/rules/**`.209210**Does not collide with `domain-modeling`**: that one declares the output port, this one211says how it's served. The port's signature belongs to the other; if it needs to change,212report the divergence instead of rewriting it.