This commit is contained in:
MaKarin
2026-04-06 12:50:46 +03:00
commit 73ddb1a948
155 changed files with 26688 additions and 0 deletions

120
docs/AID_EXPORT_README.md Normal file
View File

@@ -0,0 +1,120 @@
# AID: экспорт OpenAPI и генератор приложения
В репозитории добавлены **сервисы-экспортёры** для интеграции с **AID** (или любым другим клиентом по HTTP): автоматическое получение **OpenAPI 3.0** из доменного **api-format**.
---
## Что работает
| Компонент | Назначение |
|-----------|------------|
| **`POST /aid/export/openapi`** (NestJS) | На вход JSON **api-format** → на выход документ **OpenAPI 3.0** в поле `openapi`. |
| **`tools/api-format-to-openapi/`** | CLI и промпт для LLM: тот же конвертер, что вызывает Nest. |
| **`server/src/aid-export/`** | Модуль Nest: контроллер, сервис, краткая справка в `README.md` рядом с кодом. |
## Что временно не работает
| Компонент | Статус |
|-----------|--------|
| **`POST /aid/export/app`** (DSL → бандл/apply) | **Non-operative.** The backing script (`generation/generate.mjs`) was removed during the architecture migration to api.dsl-first LLM generation. The endpoint returns 500 with a descriptive error. |
---
## Требования к запуску
1. Репозиторий клонирован целиком (есть `tools/`, `server/`, `client/`).
2. Backend запускается из каталога **`server/`** (`npm run start` / `start:dev`), чтобы относительные пути `../tools/api-format-to-openapi/convert.mjs` были корректны.
3. Для режима OpenAPI через LLM на сервере нужны **`OPENAI_API_KEY`** (и при необходимости `OPENAI_MODEL`, `OPENAI_BASE_URL`).
---
## Переменные окружения (`server/.env`)
| Переменная | Зачем |
|------------|--------|
| `AID_EXPORT_API_KEY` | Если задана, к **`/aid/export/*`** нужен заголовок **`X-AID-Export-Key`** с тем же значением. |
| `OPENAI_API_KEY` | Для `POST /aid/export/openapi` с **`"mode": "llm"`**. |
Остальное как для обычного бэкенда (`DATABASE_URL`, `PORT` и т.д.).
---
## HTTP API (интеграция с AID)
Базовый URL: `http://<host>:<port>` (например `http://localhost:3000`).
### 1. OpenAPI из api-format
**`POST /aid/export/openapi`**
```http
Content-Type: application/json
X-AID-Export-Key: <если задан AID_EXPORT_API_KEY>
```
```json
{
"apiFormat": {
"apiFormatVersion": "1",
"info": { "title": "API", "version": "1.0.0" },
"server": { "basePath": "/api" },
"resources": []
},
"mode": "deterministic"
}
```
- **`mode`**: `deterministic` (по умолчанию) — маппинг в коде для схемы версии `1`; **`llm`** — вызов OpenAI по промпту из `tools/api-format-to-openapi/prompts/llm-system.md`.
**Ответ:** `{ "openapi": { "openapi": "3.0.3", ... } }`
Пример входа для теста: `tools/api-format-to-openapi/examples/api-format.example.json` (подставьте как значение `apiFormat`).
### 2. Генератор приложения из DSL (non-operative)
**`POST /aid/export/app`**
> **Non-operative.** This endpoint depended on `generation/generate.mjs` which was removed
> during the migration to api.dsl-first LLM generation. It currently returns HTTP 500
> with a descriptive error message. Restoring this endpoint requires implementing a new
> backing script compatible with the current `domain/*.api.dsl` pipeline.
---
## CLI (без Nest)
### api-format → OpenAPI
```bash
cd tools/api-format-to-openapi
node convert.mjs --in examples/api-format.example.json --out ../../openapi.generated.json
```
LLM:
```bash
set OPENAI_API_KEY=sk-...
node convert.mjs --mode llm --in your-api-format.json --out ../../openapi.llm.json
```
Подробнее: **`tools/api-format-to-openapi/README.md`**.
---
## Типичный сценарий для AID
1. AID уже сформировал **api-format** (как у вас принято после DTO).
2. AID вызывает **`POST /aid/export/openapi`** → получает **OpenAPI 3.0** → сохраняет в проект / отдаёт в Swagger / в реестр.
---
## Где смотреть код и короткую справку
- Полное описание эндпоинтов рядом с реализацией: **`server/src/aid-export/README.md`**
---
## Ограничения и дальнейшие шаги
- Пример **api-format** в репозитории — **учебный**; под ваш продакшен-формат может понадобиться расширить маппинг в `convert.mjs` или отточить промпт **`llm-system.md`**.
- **`/aid/export/app`** requires a new backing implementation compatible with the `domain/*.api.dsl` pipeline to be restored. See `docs/future-work.md` for planned future work.

162
docs/api-dsl-conventions.md Normal file
View File

@@ -0,0 +1,162 @@
# api.dsl Conventions
Grammar and authoring conventions for `domain/*.api.dsl` files.
See `domain/toir.api.dsl` as the canonical example.
---
## File location and naming
- All api.dsl files live in `domain/`.
- Naming: `<project>.api.dsl` (e.g. `toir.api.dsl`).
- Multiple api.dsl files are allowed but not required.
---
## Two top-level block types
An api.dsl file contains two types of top-level blocks:
1. `dto` — defines the shape of a data transfer object
2. `api` — declares a group of API endpoints for one resource
---
## dto block
```
dto DTO.<Name> {
description "Human-readable description";
attribute <fieldName> {
description "Field description";
type <type>;
is required; // or: is nullable;
map <Entity>.<field>; // links to a field in domain/*.api.dsl
}
}
```
### DTO naming convention
| DTO name | Purpose |
|----------|---------|
| `DTO.<Entity>` | Full response shape (GET by id, list items) |
| `DTO.<Entity>Create` | Create request body |
| `DTO.<Entity>Update` | Update request body (partial — all fields nullable) |
| `DTO.<Entity>ListRequest` | Paginated list request (filters + page) |
| `DTO.<Entity>ListResponse` | Paginated list response (content + page info) |
### Types
Same scalar types as the domain DSL:
`uuid`, `string`, `text`, `integer`, `decimal`, `date`, `boolean`, or an enum name from
`domain/*.api.dsl`.
Cross-DTO references: `DTO.<Name>` or `DTO.<Name>[]`.
Standard pagination types (not entity-specific; treated as well-known):
`DTO.Filter[]`, `DTO.PageRequest`, `DTO.PageInfo`.
### is required vs is nullable
Unlike the domain DSL (where these drive DB constraints), in api.dsl these drive the
TypeScript DTO property modifier:
- `is required``field!: type` in the generated class
- `is nullable``field?: type` in the generated class
- Neither modifier → treated as optional (`field?: type`)
### map directive
`map Entity.field` links the DTO attribute to a domain entity field.
Rules:
- The entity and field must exist in `domain/*.api.dsl`.
- The scalar type must match the domain DSL field type (nullability may differ).
- Omit `map` only for pagination helper types (`DTO.Filter[]`, `DTO.PageRequest`, etc.)
that have no direct entity field counterpart.
### Conflict resolution
If the type declared in a `dto` attribute conflicts with the domain DSL field type,
the domain DSL is correct. Fix the api.dsl.
---
## api block
```
api API.<Name> {
description "API group description";
endpoint <endpointName> {
label "METHOD /path";
description "Endpoint description";
// For endpoints with a request body:
attribute request {
type DTO.<RequestDto>;
}
// For endpoints with a response body:
attribute response {
type DTO.<ResponseDto>;
}
// For path parameters:
attribute <paramName> {
type <type>;
}
}
}
```
### api block naming
`API.<EntityName>` (e.g. `API.Equipment`, `API.RepairOrder`)
### endpoint label
`label "METHOD /path"` is the authoritative declaration of HTTP verb and route.
| Label pattern | NestJS decorator | Notes |
|---------------|-----------------|-------|
| `"GET /resource/{id}"` | `@Get(':id')` | |
| `"POST /resource"` | `@Post()` | Create |
| `"POST /resource/page"` | `@Post('page')` | Paginated list (body-based filter) |
| `"PUT /resource/{id}"` | `@Put(':id')` | Full or partial update |
| `"DELETE /resource/{id}"` | `@Delete(':id')` | |
Do not infer HTTP verbs from endpoint names. Always read the `label`.
### Path parameters
Declared as a plain `attribute` inside the endpoint block (not wrapped in `request`
or `response`):
```
attribute id {
type uuid;
}
```
### Standard 5-endpoint CRUD pattern
| Endpoint name | Label | Body |
|---------------|-------|------|
| `list<Entity>s` | `"POST /<path>/page"` | request: `DTO.<Entity>ListRequest`, response: `DTO.<Entity>ListResponse` |
| `get<Entity>` | `"GET /<path>/{id}"` | path param `id`, response: `DTO.<Entity>` |
| `create<Entity>` | `"POST /<path>"` | request: `DTO.<Entity>Create` |
| `update<Entity>` | `"PUT /<path>/{id}"` | path param `id`, request: `DTO.<Entity>Update` |
| `delete<Entity>` | `"DELETE /<path>/{id}"` | path param `id` |
---
## Constraints
1. Every `map Entity.field` must resolve to an existing entity + field in `domain/*.api.dsl`.
2. Types in api.dsl must be compatible with domain DSL field types (same scalar type).
3. api.dsl must not define entities or enums. Those belong in `domain/*.api.dsl`.
4. An api.dsl may omit domain entity fields from a DTO (e.g. no PK in Create DTO).
It must not add fields that don't exist in the domain model.

121
docs/completion-contract.md Normal file
View File

@@ -0,0 +1,121 @@
# Completion Contract
This document defines the definition of done for a KIS-TOiR generation run. It provides prioritized success criteria, failure modes, and recovery procedures to ensure consistent, production-ready outcomes.
These criteria apply to the post-generation repository state. In a repo-wide full regeneration driven by `prompts/general-prompt.md`, the run intentionally starts without relying on pre-existing generated workspaces or runtime artifacts.
## Prioritized Success Criteria
### Tier 1: Infrastructure Readiness (Must Pass)
- By the end of the run, `server/` exists and builds successfully (`npm run build`)
- By the end of the run, `client/` exists and builds successfully (`npm run build`)
- `node tools/validate-generation.mjs --artifacts-only` passes (stable infra/runtime/deploy checks)
- Required scaffold files present (NestJS/Vite essentials)
- Runtime/deploy baseline files are present and coherent: `docker-compose.yml`, `server/Dockerfile`, `client/Dockerfile`, `client/nginx/default.conf`, env templates, and the realm artifact
- scaffolded package manifests are normalized to the repository-approved exact version policy instead of floating on `latest` or caret ranges
### Tier 2: Domain Fidelity (Must Pass)
- `npm run eval:generation` passes (DSL fidelity, CRUD behavior, UX invariants)
- DTOs match DSL shapes exactly
- Controllers have correct guards and roles
- React Admin resources use type-correct components
- Natural-key handling correct
- Prisma schema reflects DSL without manual edits
- Reviewed eval contracts lead any new or changed entity behavior before regeneration; automation may scaffold candidate contracts, but it must not silently rewrite the committed eval corpus during every run
- Specialized generator outputs respect frozen-contract write-zones and do not leak unauthorized cross-layer edits
### Tier 3: Runtime/Deploy Readiness (Must Pass)
- Production Dockerfiles exist and are valid
- Reverse-proxy nginx config supports SPA routing and `/api` proxying
- Compose topology matches the repository contract: `postgres`, `server`, `db-seed`, and `client`, with Keycloak external to the compose stack
- Env examples align with auth/runtime contracts
- Realm artifact matches frontend/backend expectations
- Runtime/deploy artifacts are treated as first-class generation targets, not optional extras
### Tier 3A: Prisma Migration Policy (Current Explicit State)
- Current state: accepted temporary technical debt
- Immediate contract: `server/prisma/schema.prisma` must match the DSL
- Current runtime behavior: if committed migrations exist, runtime uses `prisma migrate deploy`; if not, bootstrap flows may fall back to `prisma db push`
- Preferred target: commit reviewed Prisma migrations for each DSL-driven schema change and remove the `db push` fallback from production/runtime paths
### Tier 4: Auth Seam Integrity (Must Pass)
- JWKS resolution works (no silent failures)
- Role extraction from `realm_access.roles`
- `/health` remains public
- 401/403 semantics correct
## Failure Modes and Recovery
### Failure Mode: Scaffold Degradation
**Symptoms**: Build fails, missing Nest/Vite files.
**Recovery**: Run official CLI repair (`nest new` or `vite create`), then regenerate domain code.
### Failure Mode: DSL Mismatch
**Symptoms**: Eval fails on DTO shapes or field mappings.
**Recovery**: Re-read DSL entity block, regenerate affected artifacts, re-run evals.
### Failure Mode: Delegated Output Drift
**Symptoms**: A specialized generator edits unauthorized zones, violates the frozen contract, or hands back output that is not integration-ready.
**Recovery**: Allow one bounded repair pass. If drift remains, reject the output explicitly and only then use parent manual fallback.
### Failure Mode: Guard/Header Issues
**Symptoms**: Eval fails on guards or Content-Range.
**Recovery**: Fix guard order (JwtAuthGuard first), ensure Content-Range format (`items`), re-run evals.
### Failure Mode: Relation Annotation Missing
**Symptoms**: Prisma schema invalid.
**Recovery**: Add explicit `@relation(fields: [...], references: [...])`, regenerate schema.
### Failure Mode: Runtime/Deploy Gaps
**Symptoms**: Docker build fails, nginx proxy broken.
**Recovery**: Update `prompts/runtime-rules.md`, regenerate the affected runtime/deploy artifacts, and verify the deploy baseline again.
### Failure Mode: Auth Seam Drift
**Symptoms**: JWKS fails, roles not extracted.
**Recovery**: Verify JWKS order, ensure `realm_access.roles`, regenerate auth artifacts.
## Recovery Procedures
1. **Partial Regenerate**: If only one entity fails, regenerate that entity only.
2. **Full Reset**: If scaffold broken or a repo-wide regeneration is requested, remove the generated Tier 3 app/runtime outputs (`server/`, `client/`, `db-seed/`, root runtime artifacts), re-scaffold from official CLIs, and regenerate all.
3. **Rollback**: If generation introduces breaking changes, revert to last passing commit.
4. **Debug Mode**: Run `node tools/validate-generation.mjs` with `--run-runtime` for deeper checks.
## Acceptance Protocol
Parent acceptance is explicit for all delegated generation outputs.
A delegated output is accepted only if:
- only allowed zones were modified
- the frozen contract is respected
- no unauthorized cross-layer edits occurred
- the result is integration-ready
- relevant checks were attempted where applicable
- unresolved issues are surfaced explicitly
Escalation rule:
- allow at most one bounded repair pass
- reject explicitly if the output still is not usable
- manual fallback is allowed only after rejection
## Definition of Complete
A generation run is complete when all Tier 1-4 criteria pass, the explicit
Prisma migration policy state is acknowledged, contract freeze and delegated
acceptance rules were respected, the reviewer signs off, the package manifests
follow the approved version policy, and the app is runnable/deployable with the
repository-managed runtime/deploy artifacts and documented external dependencies.

180
docs/future-work.md Normal file
View File

@@ -0,0 +1,180 @@
# Future Work — Deferred Items
This file tracks engineering improvements that are deliberately deferred due to the
current stage of the project. They are not forgotten — they are acknowledged technical
debt that should be addressed before scaling.
---
## Rule 7 — Tracing, Telemetry, Cost/Latency Observability
**Status:** Deferred. No LLM calls are instrumented.
**Why it matters (Anthropic / Google / Microsoft guidance):**
Without observability, you cannot:
- Know which prompts are expensive (token count, latency)
- Detect prompt regressions via cost drift
- Attribute generation failures to specific prompt versions
- Track improvement over time
**What needs to be built:**
### 7.1 — Generation log
Create `tools/generation-log.mjs` that wraps any LLM generation call and writes a
structured JSON entry to `logs/generation.jsonl`:
```json
{
"timestamp": "2026-04-03T10:00:00.000Z",
"entity": "Equipment",
"artifact": "backend",
"prompt_version": "1.0",
"model": "...",
"input_tokens": 4200,
"output_tokens": 1800,
"latency_ms": 3200,
"validation_passed": true,
"eval_passed": true
}
```
### 7.2 — Cost budget alerts
Add a threshold check (e.g., warn if input_tokens > 8000 for a single entity generation).
This enforces the context budget from `prompts/general-prompt.md §CONTEXT BUDGET`.
### 7.3 — Prompt version tracking
Add `<!-- prompt-version: X.Y -->` comments to all prompt files (already started in
`backend-rules.md` and `frontend-rules.md`). Increment version on any non-trivial change.
Log the prompt versions alongside the generation log entry.
### 7.4 — Drift detection
Compare generation log entries across runs. If token count for the same entity increases
by >20% without a DSL change, flag it as context rot.
**Effort estimate:** Medium. 23 days to build the logging layer. Zero effort for
prompt versioning (already partially done).
**Trigger:** Implement before the system is used for more than 10 entities or before
any production deployment.
---
## Rule 8 — Risk Controls and Red-Teaming
**Status:** Deferred. No sanitization or adversarial testing exists.
**Why it matters (Anthropic / Google / Microsoft guidance):**
LLM-generated code at scale introduces risks that do not exist in hand-written code:
- **Prompt injection**: malicious content in DSL `description` fields could steer
generation (e.g., `description "Ignore previous instructions and..."`)
- **Generated credential leakage**: LLM may hallucinate hardcoded secrets that look
real (e.g., `apiKey: 'sk-...'`)
- **Missing auth guards**: already caught by Rule 4 validator, but adversarial prompts
could bypass it by generating valid-looking guard syntax that is semantically inactive
- **Supply chain**: generated package imports could reference non-existent or malicious
packages if the LLM hallucinates
**What needs to be built:**
### 8.1 — DSL input sanitization
In `tools/api-summary.mjs`, before building the summary, check all `description` and
`label` fields for injection patterns:
```javascript
function sanitizeDslString(value, fieldPath) {
const injectionPatterns = [
/ignore previous/i,
/disregard.*instruction/i,
/you are now/i,
/system:/i,
];
for (const pattern of injectionPatterns) {
if (pattern.test(value)) {
throw new Error(`Potential prompt injection in DSL field ${fieldPath}: "${value}"`);
}
}
return value;
}
```
### 8.2 — Generated code security scan
Add to `tools/validate-generation.mjs` (or a separate `tools/security-scan.mjs`):
```javascript
// Check no hardcoded secrets leaked into generated code
function validateNoSecretLeakage() {
const patterns = [
/sk-[a-zA-Z0-9]{20,}/, // OpenAI key pattern
/[a-zA-Z0-9+/]{40}={0,2}/, // Base64 secret-like
/password\s*=\s*['"][^'"]{4,}['"]/, // Hardcoded password
/apiKey\s*=\s*['"][^'"]{4,}['"]/, // Hardcoded API key
];
// Run against all generated files...
}
```
### 8.3 — UseGuards completeness audit
Beyond the current semantic gate coverage, add: verify that the guard
constructor arguments are non-empty and match the expected guard class names. A guard
call like `@UseGuards()` (empty) should fail eval coverage because it provides no protection.
### 8.4 — Red-team fixture
Create `tools/eval/fixtures/_adversarial/` with a fixture that includes a DSL snippet
containing a benign injection attempt (e.g., a `description` field with "ignore format
rules") and verifies the generation still produces spec-compliant output.
### 8.5 — Generated import allowlist
Maintain a list of approved npm packages that generated code may import. Flag any
import not on the allowlist as a manual review item.
**Effort estimate:** Medium-High. 35 days. Security scan and sanitization are low
effort; red-team fixtures and import allowlisting are higher effort.
**Trigger:** Implement before any external user can influence `domain/*.api.dsl` content
(i.e., before a UI or API to edit the DSL is exposed).
---
## Tracking
| Rule | Status | Priority | Trigger |
|------|--------|----------|---------|
| Rule 7 — Telemetry | Deferred | Medium | Before >10 entities or production deployment |
| Rule 8 — Risk controls | Deferred | High | Before DSL editing is exposed to external users |
| Rule 9 — Eval corpus automation | Deferred | Medium | After the contract stabilizes for the current entity set |
---
## Rule 9 — Eval Corpus Automation
**Status:** Deferred. Reviewed eval fixtures are still the authoritative semantic gate.
**Why it matters:**
The repository already requires eval-first behavior for new or changed entity coverage, but the repo does not yet synthesize starter eval contracts from source-of-truth. That means humans still have to review and finalize the first failing semantic contract.
**What needs to be built:**
- a deterministic helper that can scaffold `tools/eval/fixtures/<entity>/` starters from the active source-of-truth slice
- a prompt-to-eval helper that emits backend and frontend assertion starters before regeneration
- a documented workflow where generated starters are reviewed and committed, instead of silently replacing the authoritative eval corpus on every run
**Status of current contract:**
- This is not implemented yet.
- The repo should not imply that evals are auto-generated today.
- The repo should also not imply that a step-0 LLM subagent should rewrite committed eval fixtures on every regeneration run; that would collapse the independence of the semantic gate.
- Until bounded automation exists, the reviewed eval-first rule in `prompts/general-prompt.md` and `docs/generation-playbook.md` remains required.
**Effort estimate:** Medium.
**Trigger:** After the contract stabilizes for the current entity set.
Last updated: 2026-04-03

333
docs/generation-playbook.md Normal file
View File

@@ -0,0 +1,333 @@
# Generation Playbook
Step-by-step instructions for generating and regenerating artifacts in this repository.
Read `AGENTS.md` and `docs/source-of-truth.md` first.
---
## Pipeline overview
```
Tier 1 — Single Source of Truth (hand-authored, never generated)
domain/toir.api.dsl ──┐ enums, DTOs, endpoints, HTTP methods,
│ entity field mappings, primary keys
Tier 2 — Deterministic Preprocessing (npm scripts, no LLM)
api-summary.json ←─ npm run generate:api-summary
openapi.json ←─ npm run generate:openapi (auxiliary)
Tier 1 (api.dsl) + prompts/*.md ──► LLM Generation
prompts/general-prompt.md
parent orchestrator ──► discovery, docs verification, contract freeze, shared scaffold, auth/runtime skeleton, integration
prompts/prisma-rules.md ──► generator_prisma ──► server/prisma/schema.prisma
prompts/backend-rules.md ──► generator_nest_resources ──► server/src/modules/<entity>/
prompts/frontend-rules.md ──► generator_react_admin_resources ──► client/src/resources/<entity>/
prompts/auth-rules.md ──► parent + generator_data_access ──► server/src/auth/, client/src/auth/, client/src/dataProvider.ts, toir-realm.json
prompts/runtime-rules.md ──► parent ──► docker-compose.yml, server/client Dockerfiles, nginx config, env templates, docker-entrypoint, db-seed artifacts
prompts/validation-rules.md ──► (validation gate reference)
Tier 4 — Validation Gate
node tools/validate-generation.mjs --artifacts-only
npm run eval:generation
```
---
## Prerequisites
Before any generation run:
1. `domain/*.api.dsl` is current and valid.
2. Read `AGENTS.md` and `prompts/general-prompt.md`.
3. If this is a repo-wide full regeneration, begin from Tier 1/Tier 2 inputs without relying on existing Tier 3 outputs such as `server/`, `client/`, `db-seed/`, `docker-compose.yml`, or `toir-realm.json`.
4. Refresh the Tier 2 intermediate context only when validator/tooling or a supporting workflow needs it:
```bash
npm run generate:api-summary
```
Do not require `node tools/validate-generation.mjs --artifacts-only` to pass before a clean-slate full regeneration. That command validates the post-generation repository state.
---
## Standard generation workflow
### Step 0 — Full-regeneration reset when requested
For a repo-wide full regeneration driven by `prompts/general-prompt.md`:
- do not assume `server/`, `client/`, `db-seed/`, Dockerfiles, env templates, `docker-compose.yml`, or `toir-realm.json` already exist
- recreate backend and frontend workspaces from the official NestJS and Vite React TypeScript CLIs
- regenerate Tier 3 runtime/deploy artifacts after scaffold recreation
- treat the validator and eval harness as end-state checks after generation
### Step 1 — Refresh auxiliary derived artifacts only when needed
```bash
# From repo root
npm run generate:api-summary
```
### Step 2 — Read generation inputs (context budget)
> **`prompts/general-prompt.md` is the master generation prompt.** It contains all core
> type mappings, naming conventions, DTO/controller/service/frontend rules, mutation
> boundaries, and the complete generation workflow. Load it as the single entrypoint.
>
> For artifact-specific details (Prisma FK rules, auth JWKS chain, detailed validation
> groups), load the relevant companion file: `prompts/prisma-rules.md`,
> `prompts/auth-rules.md`, `prompts/validation-rules.md`, or `prompts/runtime-rules.md`.
>
> See `prompts/general-prompt.md §CONTEXT BUDGET` for the full budget model.
1. `prompts/general-prompt.md` — master generation prompt (always load)
2. `domain/*.api.dsl §API.<EntityName>` — **only the api block + its referenced DTOs + enums** (entity-scoped)
3. `api-summary.json` — only when validator/tooling explicitly needs the auxiliary freshness artifact
4. **If needed:** `prompts/prisma-rules.md` (Prisma), `prompts/auth-rules.md` (auth seams), `prompts/runtime-rules.md` (deploy/runtime artifacts), or `prompts/validation-rules.md` (gate ownership)
Before generating any DTO or component: **quote the relevant DSL field definitions verbatim first**, then generate from those quotes. This prevents training-data contamination.
If a new or changed entity behavior is not already covered by an eval contract, write or review the failing eval first so the semantic gate leads the generation change. Helpers may scaffold candidate fixtures from source-of-truth, but committed evals remain reviewed artifacts rather than auto-regenerated outputs.
### Step 3 — Discovery and docs verification
- Use `explorer` first for repo discovery, scaffold inspection, and tracing shared seams.
- Use `docs_researcher` before planning framework-sensitive auth, runtime, Prisma, NestJS, or React Admin work.
- Prefer Context7 for official framework/library docs; use web fallback only for current or missing details.
### Step 4 — Freeze the contract
Before specialized generation, the parent must freeze a normalized structured handoff that captures:
- entities, fields, types, ids/composite keys, relations, and enums
- endpoint, route, and naming conventions
- auth surface expectations
- validator/eval compatibility constraints
- allowed write-zones for each delegated generator
This freeze is a parent-owned protocol. It does not replace the DSL as source of truth.
### Step 5 — Shared scaffold and parent-owned seams
- repair or recreate official Nest/Vite/Prisma scaffolds as needed
- parent owns auth platform skeleton, deploy/runtime skeleton, env conventions, and shared wiring
- do not start specialized generators before scaffold and frozen-contract inputs are ready
### Step 6 — Generate Prisma schema
Generate `server/prisma/schema.prisma` from `domain/*.api.dsl` following `prompts/prisma-rules.md`.
Preferred migration policy:
- if the DSL changes the schema, create or update Prisma migrations in `server/`
- keep `db push` only as a bootstrap fallback for fresh environments that do not yet have migration history
- treat schema changes shipped without a migration as temporary technical debt that must be called out explicitly
If the schema changed, follow the explicit repository migration policy:
```bash
cd server
npx prisma migrate dev --name <description>
```
If committed migrations do not yet exist, the current repository accepts `prisma db push` only as temporary bootstrap debt. Do not present that fallback as the target steady state.
Use `generator_prisma` for this bounded scope. It must not modify backend modules, frontend resources, auth, or runtime/deploy artifacts.
### Step 7 — Generate backend modules
For each `api` block in `domain/*.api.dsl`, generate:
1. `server/src/modules/<kebab>/<entity>.module.ts`
2. `server/src/modules/<kebab>/dto/create-<kebab>.dto.ts`
— fields from the `DTO.<Entity>Create` block in api.dsl
3. `server/src/modules/<kebab>/dto/update-<kebab>.dto.ts`
— fields from the `DTO.<Entity>Update` block in api.dsl
4. `server/src/modules/<kebab>/<entity>.service.ts`
— CRUD operations using Prisma; respect type mappings from `prompts/backend-rules.md`
5. `server/src/modules/<kebab>/<entity>.controller.ts`
— one method per `endpoint` in the `api` block; HTTP verb and path from the `label`
Register the module in `server/src/app.module.ts`.
Use `generator_nest_resources` for this bounded scope. It must stay within backend resource zones and must not redesign the shared auth or runtime platform.
### Step 8 — Generate frontend resources
For each `api` block in `domain/*.api.dsl`, generate:
1. `client/src/resources/<kebab>/<Entity>List.tsx`
— columns from `DTO.<Entity>` (response shape)
2. `client/src/resources/<kebab>/<Entity>Create.tsx`
— fields from `DTO.<Entity>Create`
3. `client/src/resources/<kebab>/<Entity>Edit.tsx`
— fields from `DTO.<Entity>Update`
4. `client/src/resources/<kebab>/<Entity>Show.tsx`
— fields from `DTO.<Entity>`
Register the resource in `client/src/App.tsx`.
Use `generator_react_admin_resources` for this bounded scope. It must stay within frontend resource zones and must not redesign shared data-access or auth strategy.
### Step 9 — Generate data-access and auth/runtime/deploy artifacts
Use `generator_data_access` for `client/src/dataProvider.ts` and only the narrowly delegated frontend integration seam. Parent retains ownership of the shared auth platform skeleton and all deploy/runtime skeleton artifacts.
Generate or repair:
1. `server/src/auth/`
2. `client/src/auth/`
3. `client/src/dataProvider.ts`
4. `toir-realm.json`
5. `docker-compose.yml`
6. `server/.env.example`, `client/.env.example`
7. `server/Dockerfile`, `client/Dockerfile`
8. `client/nginx/default.conf`
9. `server/docker-entrypoint.sh`
10. `db-seed/Dockerfile`, `db-seed/import.sh`
Preserve the proven-good runtime behaviors from `prompts/runtime-rules.md` unless the repository contract explicitly changes them.
### Step 10 — Accept delegated outputs and integrate
Parent acceptance is explicit. Accept a delegated output only if:
- only allowed zones changed
- the frozen contract is respected
- no unauthorized cross-layer edits occurred
- the result is integration-ready
- relevant checks were attempted where applicable
- unresolved issues are surfaced explicitly
Allow at most one bounded repair pass. If still unusable, reject explicitly.
Manual fallback is allowed only after rejection.
### Step 11 — Verify (two-stage gate)
**Stage 1 — Structural gate:**
```bash
node tools/validate-generation.mjs --artifacts-only
```
Checks scaffold/build readiness, auth/runtime/realm seams, required runtime/deploy artifacts, and other stable repository invariants.
Full structural verification (requires installed deps):
```bash
node tools/validate-generation.mjs
```
**Stage 2 — Eval harness:**
```bash
npm run eval:generation
```
Fixture-based semantic checks. See `tools/eval/README.md`.
Both must pass before the task is complete.
### Step 12 — Final review
Run `reviewer` only after integration and validation. Reviewer is the final
correctness/security/test-gap gate, not a substitute for parent validation.
---
## Adding a new entity
1. Add the entity's enums, DTOs, and `api` block to `domain/toir.api.dsl`.
2. Run `npm run generate:api-summary`.
3. **Before generating:** create `tools/eval/fixtures/<kebab>/meta.json`, `backend.assertions.json`, and `frontend.assertions.json` with expected patterns.
4. Run `npm run eval:generation` — it will fail (entity files don't exist yet). That's expected.
5. Generate backend, frontend, and runtime-facing artifacts (Steps 36).
6. Run `npm run eval:generation` again — now it should pass.
7. Run `node tools/validate-generation.mjs --artifacts-only` — both gates must pass.
> This is **eval-driven development**: write the failing eval first (step 3), then generate to make it pass (step 6). A passing eval confirms the LLM followed the rules.
---
## Changing an existing entity
**If the domain model changes** (new field, changed type, new FK, new enum):
1. Update `domain/toir.api.dsl` (enums, DTO attributes, `map` references).
2. Run `npm run generate:api-summary`.
3. Regenerate `server/prisma/schema.prisma`, run migration.
4. Regenerate the affected modules/resources and any affected runtime artifacts (Steps 47).
**If only the API contract changes** (different nullability, new endpoint, different HTTP method):
1. Update `domain/toir.api.dsl` only.
2. Run `npm run generate:api-summary` to refresh `api-summary.json`.
3. Run Steps 47. No Prisma migration needed.
---
## Artifact traceability matrix
| Artifact | DSL source | Prompt rule | Validator check |
| ---------------------------------------------- | ---------------------- | ---------------------------------------------- | --------------------------------------- |
| `server/prisma/schema.prisma` | `domain/*.api.dsl` | `prompts/prisma-rules.md` | `§validateBuildChecks` (file exists) |
| `api-summary.json` | `domain/*.api.dsl` | — (deterministic) | `§validateBuildChecks` (freshness) |
| `server/src/modules/<e>/*.ts` | `domain/*.api.dsl` | `prompts/backend-rules.md` | `npm run eval:generation` |
| `server/src/modules/<e>/dto/create-<e>.dto.ts` | `DTO.<E>Create` fields | `prompts/backend-rules.md §DTO-field-coverage` | `npm run eval:generation` |
| `server/src/modules/<e>/dto/update-<e>.dto.ts` | `DTO.<E>Update` fields | `prompts/backend-rules.md §DTO-field-coverage` | `npm run eval:generation` |
| `client/src/resources/<e>/<E>*.tsx` | `domain/*.api.dsl` | `prompts/frontend-rules.md` | `npm run eval:generation` |
| `client/src/auth/keycloak.ts` | auth/runtime contract | `prompts/auth-rules.md` | `§validateAuthChecks` |
| `toir-realm.json` | auth/runtime contract | `prompts/auth-rules.md` | `§validateRealmChecks` |
| `docker-compose.yml` | runtime contract | `prompts/runtime-rules.md` | `§validateRuntimeContractChecks` |
| `server/Dockerfile` | runtime contract | `prompts/runtime-rules.md` | `§validateRuntimeContractChecks` |
| `client/Dockerfile` | runtime contract | `prompts/runtime-rules.md` | `§validateRuntimeContractChecks` |
| `client/nginx/default.conf` | runtime contract | `prompts/runtime-rules.md` | `§validateRuntimeContractChecks` |
| `server/docker-entrypoint.sh` | runtime contract | `prompts/runtime-rules.md` | `§validateRuntimeContractChecks` |
| `db-seed/Dockerfile`, `db-seed/import.sh` | runtime contract | `prompts/runtime-rules.md` | `§validateRuntimeContractChecks` |
---
## Verification commands reference
| Command | When to use |
| ----------------------------------------------------- | ------------------------------------------ |
| `npm run generate:api-summary` | After any change to `domain/*.api.dsl` |
| `npm run generate:openapi` | To regenerate OpenAPI 3.0.3 documentation |
| `node tools/validate-generation.mjs --artifacts-only` | After every generation run (required) |
| `node tools/validate-generation.mjs` | Before committing; requires installed deps |
| `node tools/validate-generation.mjs --run-runtime` | End-to-end; requires running DB |
---
## OpenRouter invocation
Set environment variables before any LLM-mode tool call:
```
OPENAI_API_KEY=<openrouter-key>
OPENAI_BASE_URL=https://openrouter.ai/api/v1
OPENAI_MODEL=<model-id>
```
The `OPENAI_BASE_URL` variable is consumed by `tools/api-format-to-openapi/convert.mjs --mode llm`.
For agent-driven generation via Cursor or direct API calls, the standard workflow above
applies regardless of model provider.
---
## Auxiliary tools
### `tools/api-summary-to-openapi.mjs`
Generates `openapi.json` (OpenAPI 3.0.3) from `api-summary.json` deterministically.
This is a documentation/integration artifact, not part of the core generation pipeline.
```bash
npm run generate:openapi
```
### `tools/api-format-to-openapi/`
Auxiliary integration tool for external consumers using the `api-format` JSON schema.
Not connected to the main DSL pipeline. See `docs/AID_EXPORT_README.md` for details.

View File

@@ -0,0 +1,39 @@
# Repository Structure
`KIS-TOiR` keeps the existing LLM-first generation philosophy and organizes the repository by meaning:
- `domain/`
- canonical DSL inputs
- DSL specification
- `prompts/`
- active prompt corpus used to drive generation
- `docs/`
- overview and repository-level architecture notes
- `tools/`
- helper scripts for summary generation and validation
- `server/`
- generated backend target output after generation; may be absent at the clean-slate start of a full regeneration run
- `client/`
- generated frontend target output after generation; may be absent at the clean-slate start of a full regeneration run
- `db-seed/`
- generated runtime/bootstrap output after generation; may be absent before a full regeneration run
The repository keeps LLM-first generation orchestration, but framework bootstrap is CLI-first:
- `server/` must remain a valid NestJS workspace baseline
- `client/` must remain a valid Vite React TypeScript workspace baseline
- repair a broken workspace before applying more domain-derived generation changes
- future agents must treat forbidden generation patterns in `prompts/` as contract violations, not suggestions
Root-level files stay limited to repository-level artifacts such as:
- `README.md`
- `package.json`
- `docker-compose.yml`
- `api-summary.json`
- `toir-realm.json`
- `.gitignore`
Generated root runtime artifacts such as `docker-compose.yml` and `toir-realm.json` are end-state outputs, not clean-slate prerequisites for `prompts/general-prompt.md`.
The repository does not introduce a new generator engine or compiler platform. It keeps the current LLM-first pipeline and makes it cleaner, more explicit, and easier to navigate.

144
docs/source-of-truth.md Normal file
View File

@@ -0,0 +1,144 @@
# Source-of-Truth Hierarchy
This document is the authoritative reference for which files own which decisions.
---
## Tier 1: Authoritative sources (hand-authored; never generated)
### `domain/*.api.dsl`
**Single source of truth for the entire domain and API contract:**
- Entity names and structure
- Attribute names, scalar types, descriptions
- Primary keys (including natural string keys)
- Foreign keys and relations
- Enum definitions and their values
- Database-level constraints: `is required`, `is unique`, `default`
- DTO shapes per operation (Create, Update, Read, ListRequest, ListResponse)
- Which fields appear in each DTO and with what TypeScript modifier (`!` or `?`)
- HTTP method and path for each endpoint (via `label "METHOD /path"`)
- Endpoint names (camelCase identifiers)
- Pagination request/response contract
**Drives:** `server/prisma/schema.prisma`, `server/src/modules/`, `client/src/resources/`,
`server/src/app.module.ts`, `client/src/App.tsx`, and the generated auth/runtime/deploy
artifacts named by the companion prompt contracts.
### `prompts/*.md` and `AGENTS.md`
**Authoritative for:**
- Agent generation workflow and reading order
- Parent-versus-specialist orchestration ownership
- Approved stack versions and dependency pinning policy
- Auth seam patterns (Keycloak, JWT, PKCE S256, JWKS resolution chain)
- Runtime conventions (env examples, docker-compose topology)
- Framework scaffold baseline requirements (NestJS CLI, Vite React TypeScript)
- Filtering and sorting contract
- Naming conventions and implicit rules (pluralization, sort field priority, type mappings)
- Mutation boundaries (what agents must not overwrite)
- Runtime/deploy artifact classification and preservation rules
### `docs/completion-contract.md`
**Operational definition of done for generation runs:**
- prioritized success tiers
- failure modes and recovery procedures
- reviewer signoff requirement
- runtime/deploy readiness requirements
---
## Tier 2: Deterministic derivatives (never edit manually)
| File | Generated from | Command |
| ----------------- | ------------------ | ------------------------------ |
| `api-summary.json` | `domain/*.api.dsl` | `npm run generate:api-summary` |
These files are regenerated from their sources. Manual edits are overwritten on the
next generation run.
---
## Tier 3: LLM-generated artifacts (never edit manually after generation)
These are end-state outputs. A repo-wide full regeneration may begin with some or all Tier 3 artifacts absent and then recreate them from Tier 1 inputs plus official CLI scaffolding.
| Zone | Generated from |
| -------------------------------- | ------------------------------------------------ |
| `server/prisma/schema.prisma` | `domain/*.api.dsl` + `prompts/prisma-rules.md` |
| `server/src/modules/<entity>/` | `domain/*.api.dsl` + `prompts/backend-rules.md` |
| `client/src/resources/<entity>/` | `domain/*.api.dsl` + `prompts/frontend-rules.md` |
| `server/src/app.module.ts` | Module list derived from api.dsl `api` blocks |
| `client/src/App.tsx` | Resource list derived from api.dsl `api` blocks |
| `docker-compose.yml` | `prompts/runtime-rules.md` |
| `server/Dockerfile` | `prompts/runtime-rules.md` |
| `client/Dockerfile` | `prompts/runtime-rules.md` |
| `client/nginx/default.conf` | `prompts/runtime-rules.md` |
| `server/.env.example` | `prompts/runtime-rules.md` |
| `client/.env.example` | `prompts/runtime-rules.md` |
| `server/docker-entrypoint.sh` | `prompts/runtime-rules.md` |
| `db-seed/Dockerfile` | `prompts/runtime-rules.md` |
| `db-seed/import.sh` | `prompts/runtime-rules.md` |
| `server/src/auth/` | `prompts/auth-rules.md` |
| `client/src/auth/` | `prompts/auth-rules.md` |
| `client/src/dataProvider.ts` | `prompts/auth-rules.md` |
| `toir-realm.json` | `prompts/auth-rules.md` |
To change DSL-driven domain artifacts: update `domain/*.api.dsl` and regenerate.
To change auth/runtime/deploy artifacts: update the governing prompt contracts and regenerate or repair them through the orchestrator.
Ownership rule for Tier 3:
- parent owns shared scaffold, auth platform skeleton, deploy/runtime skeleton, env conventions, integration, and validation
- `generator_prisma` owns `server/prisma/schema.prisma`
- `generator_nest_resources` owns `server/src/modules/**` plus delegated backend registration touchpoints
- `generator_react_admin_resources` owns `client/src/resources/**` plus delegated frontend registration touchpoints
- `generator_data_access` owns `client/src/dataProvider.ts` and any parent-delegated narrow frontend integration seam
- specialized generators must not redesign shared auth/runtime/platform seams outside their delegated zones
Contract freeze rule:
- before specialized generation, the parent must freeze a structured handoff covering entities, fields, types, ids/composite keys, relations, enums, endpoint/path conventions, naming rules, auth surface expectations, validator/eval constraints, and allowed write-zones per generator
- this handoff is an orchestration protocol, not a replacement for the DSL
---
## Tier 4: Framework-managed support
- Prisma migrations under `server/prisma/migrations/` when created by the official Prisma CLI
- Framework config: `nest-cli.json`, `tsconfig*.json`, `vite.config.*`, etc.
- All `prompts/*.md`, `AGENTS.md`, `domain/dsl-spec.md`, `domain/*.api.dsl`
Runtime/deploy policy:
- Tier 3 runtime/deploy outputs are first-class generation targets and should be regenerated or repaired from the companion rules when they drift.
- Tier 4 support files are framework-managed rather than hand-authored sources of truth.
---
## Validation gate
### Stage 1 — Structural gate
```
node tools/validate-generation.mjs --artifacts-only
```
Verifies scaffold/build readiness, required runtime/deploy artifacts, auth/runtime seams,
realm structure, and other stable repository invariants.
This gate validates the post-generation state, not the clean-slate starting state for a full regeneration run.
### Stage 2 — Eval harness
```
npm run eval:generation
```
Fixture-based semantic checks from `tools/eval/fixtures/` for DSL fidelity, CRUD behavior,
natural-key handling, and UI invariants.
Both stages must pass before any generation task is considered complete.