KeiSeiKit-1.0/skills/api-design/phase-2-resource-model.md
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

93 lines
3.8 KiB
Markdown

# Phase 2 — Resource model (entities → resources / types)
Turn the app description into a list of entities, their relationships, and
the actions on them. This is the second and last typed phase — the user
types a short entity list in 2a, then one click in 2b locks the shape.
## 2a — Ask for entities + relationships (typed)
Emit a regular message (NOT AskUserQuestion):
> List the core entities (one per line) with an optional `owns→` arrow for
> relationships. Example:
> ```
> User
> Invoice owns→ InvoiceItem
> Customer owns→ Invoice
> Tag
> Invoice many-to-many→ Tag
> ```
> Keep it to ≤10 entities. Anything beyond core can be added after launch.
Store the parsed list as `RESOURCES`. Each entry is
`{name, owns: [child...], many_to_many: [peer...]}`. If parsing fails,
re-ask with the exact syntax rules shown above.
## 2b — Shape click (AskUserQuestion, single-select)
Reference: `_blocks/api-rest-conventions.md` (REST resources),
`_blocks/api-graphql.md` (types + connections).
```json
{
"questions": [
{
"question": "How should the resources surface?",
"header": "Shape",
"multiSelect": false,
"options": [
{"label": "Flat REST (one resource per entity, ≤2 levels nesting)",
"description": "`/invoices`, `/invoices/{id}/items`. Deeper nesting flattened via query filters. Default for REST."},
{"label": "REST + sub-resources for every owns→ relation",
"description": "`/customers/{id}/invoices`, `/invoices/{id}/items`. Readable, curl-friendly; nesting budget 2 levels."},
{"label": "GraphQL types + Relay Connections",
"description": "Each entity → `type Foo { ... }`, each list → `FooConnection` with cursor pagination. See api-graphql.md"},
{"label": "GraphQL federation (subgraph per entity cluster)",
"description": "Apollo Federation 2 @key directives. ONLY pick when you truly have multiple teams / subgraphs."},
{"label": "Mixed: REST for public CRUD, GraphQL for dashboards",
"description": "Record both — this skill will generate primary surface; rerun for secondary."}
]
}
]
}
```
Store as `SHAPE`. Gate on `STYLE` from Phase 1:
- `STYLE = REST` → accept Flat REST or sub-resource REST; reject GraphQL
options (re-ask).
- `STYLE = GraphQL` → accept GraphQL types or federation.
- `STYLE = tRPC / gRPC` → SHAPE defaults to "flat" (procedures per entity);
skip the click, record `SHAPE = flat-procedures`.
- `STYLE = Hybrid` → emit a warning that both shapes will be skeleton'd in
Phase 3.
## 2c — Emit resource-to-action matrix (inline, no AskUserQuestion)
Print a table the user can tweak before Phase 3 generates the contract.
Example for a notes + tags API:
```markdown
| Entity | Create | Read | Update | Delete | List | Search |
|---------------|:------:|:----:|:------:|:------:|:----:|:------:|
| User | - | ✓ | ✓ | - | ✓ | ✓ |
| Note | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Tag | ✓ | ✓ | - | ✓ | ✓ | - |
| NoteTag (m2m) | ✓ | - | - | ✓ | - | - |
```
- Rows = entries from `RESOURCES`.
- Columns = CRUD + List + Search (drop columns that don't apply per
entity).
- Cells = `✓` (endpoint exists) / `-` (intentionally absent).
- Admin-only actions marked `admin✓`.
User ackowledges the table (no click — implicit); Phase 3 uses it as input.
## Verify-criterion
- `RESOURCES` has ≥1 entry; parsed shape `{name, owns, many_to_many}` valid.
- `SHAPE` is compatible with `STYLE` (gate above).
- Resource-to-action matrix printed with every entity as a row.
- Non-trivial m2m relations surfaced as explicit join entities OR
GraphQL edge types — no implicit joins hidden in handlers.