This commit is contained in:
MaKarin
2026-04-03 20:54:37 +03:00
commit c89c23fd1d
50 changed files with 6716 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.

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

@@ -0,0 +1,154 @@
# 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 validator check (UseGuards present), add: verify that the guard
constructor arguments are non-empty and match the expected guard class names. A guard
call like `@UseGuards()` (empty) passes the current regex but 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 |
Last updated: 2026-04-03

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

@@ -0,0 +1,237 @@
# 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) + Tier 2 (context) + prompts/*.md ──► LLM Generation
prompts/general-prompt.md
prompts/backend-rules.md ──► server/src/modules/<entity>/
prompts/frontend-rules.md ──► client/src/resources/<entity>/
prompts/prisma-rules.md ──► server/prisma/schema.prisma
prompts/auth-rules.md ──► (auth seam reference)
prompts/runtime-rules.md ──► (env/docker reference)
prompts/validation-rules.md ──► (validation gate reference)
Tier 4 — Validation Gate
node tools/validate-generation.mjs --artifacts-only
```
---
## Prerequisites
Before any generation run:
1. `domain/*.api.dsl` is current and valid.
2. Refresh the Tier 2 intermediate context:
```bash
npm run generate:api-summary
```
3. Run `node tools/validate-generation.mjs --artifacts-only` to confirm the baseline passes.
---
## Standard generation workflow
### Step 1 — Refresh Tier 2 derived artifacts
```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. `api-summary.json §<entity>` — compact entity index (fast-path context anchor)
3. `domain/*.api.dsl §API.<EntityName>` — **only the api block + its referenced DTOs + enums** (entity-scoped)
4. **If needed:** `prompts/prisma-rules.md` (Prisma) or `prompts/auth-rules.md` (auth seams)
Before generating any DTO or component: **quote the relevant DSL field definitions verbatim first**, then generate from those quotes. This prevents training-data contamination.
### Step 3 — Generate Prisma schema
Generate `server/prisma/schema.prisma` from `domain/*.api.dsl` following `prompts/prisma-rules.md`.
If the schema changed, run Prisma migration in `server/`:
```bash
cd server
npx prisma migrate dev --name <description>
```
### Step 4 — 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`.
### Step 5 — 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`.
### Step 6 — Verify (two-stage gate)
**Stage 1 — Structural gate:**
```bash
node tools/validate-generation.mjs --artifacts-only
```
Checks file existence, field names, class-validator decorators, auth guards, and RA component types.
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.
---
## 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 and frontend artifacts (Steps 35).
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 and resources (Steps 46).
**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 46. 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` | `§validateApiDslCoverage` |
| `server/src/modules/<e>/dto/create-<e>.dto.ts` | `DTO.<E>Create` fields | `prompts/backend-rules.md §DTO-field-coverage` | `§validateApiDslCoverage` (field names) |
| `server/src/modules/<e>/dto/update-<e>.dto.ts` | `DTO.<E>Update` fields | `prompts/backend-rules.md §DTO-field-coverage` | `§validateApiDslCoverage` (field names) |
| `client/src/resources/<e>/<E>*.tsx` | `domain/*.api.dsl` | `prompts/frontend-rules.md` | `§validateApiDslCoverage` |
| `client/src/auth/keycloak.ts` | — (Tier 4 handwritten) | `prompts/auth-rules.md` | `§validateAuthChecks` |
| `toir-realm.json` | — (Tier 4 handwritten) | `prompts/auth-rules.md` | `§validateRealmChecks` |
| `docker-compose.yml` | — (Tier 4 handwritten) | `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,35 @@
# 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/`
- active backend target output
- `client/`
- active frontend target output
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`
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.

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

@@ -0,0 +1,96 @@
# 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`.
### `prompts/*.md` and `AGENTS.md`
**Authoritative for:**
- Agent generation workflow and reading order
- 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)
---
## 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)
| 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 |
To change these files: update `domain/*.api.dsl` and regenerate.
---
## Tier 4: Handwritten (not generated; not derived)
- Auth seams: `client/src/auth/`, `server/src/auth/`
- `toir-realm.json`
- `docker-compose.yml`
- `server/.env.example`, `client/.env.example`
- Framework config: `nest-cli.json`, `tsconfig*.json`, `vite.config.*`, etc.
- All `prompts/*.md`, `AGENTS.md`, `domain/dsl-spec.md`, `domain/*.api.dsl`
---
## Validation gate
### Stage 1 — Structural gate
```
node tools/validate-generation.mjs --artifacts-only
```
Verifies that generated artifacts satisfy the contracts declared in Tier 1 sources.
### Stage 2 — Eval harness
```
npm run eval:generation
```
Fixture-based semantic checks from `tools/eval/fixtures/`.
Both stages must pass before any generation task is considered complete.