/nacl-sa-architect --- Архитектурная декомпозиция (Graph)
SkillDev toolsDecomposes the system into modules (Bounded Contexts), builds a Context Map, and defines NFRs. Reads BA data from the Neo4j graph and writes Module/Requirement nodes to the graph. Use when the user asks to: design architecture in the graph, split into modules, define bounded contexts, create bo
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the /nacl-sa-architect --- Архитектурная декомпозиция (Graph) skill
What this skill tells your AI
The instructions your AI receives, as published by itsalt/nacl in nacl-sa-architect/SKILL.md and read by ahel’s review.
Назначение
Декомпозиция информационной системы на функциональные модули (Bounded Contexts), определение межмодульных зависимостей и высокоуровневых нефункциональных требований. Все данные читаются из Neo4j графа (BA-подграф) и записываются в Neo4j граф (SA-подграф).
Shared References
Read nacl-core/SKILL.md for:
- Neo4j MCP tool names and connection info (
mcp__neo4j__read-cypher,mcp__neo4j__write-cypher) - ID generation rules
- Schema files location (
graph-infra/schema/sa-schema.cypher) - Query library location (
graph-infra/queries/)
Key Schema (SA Layer)
From graph-infra/schema/sa-schema.cypher:
| Node | Key Properties | Description |
|---|---|---|
Module | id, name, description, uc_range_start, uc_range_end | Functional module (Bounded Context) |
Requirement | id, description, type, priority | Functional or non-functional requirement |
Key SA relationships used by this skill:
(:Module)-[:CONTAINS_UC]->(:UseCase)--- module owns a use case(:Module)-[:CONTAINS_ENTITY]->(:DomainEntity)--- module owns a domain entity(:Module)-[:DEPENDS_ON {type, description}]->(:Module)--- inter-module dependency(:ProcessGroup)-[:SUGGESTS]->(:Module)--- BA-to-SA handoff edge
Режимы работы
Режим full (по умолчанию)
Полная декомпозиция системы с нуля: 5 фаз интерактивного диалога.
Когда: Новый проект, Module узлы ещё не созданы в графе.
Режим module
Добавление одного модуля в существующую архитектуру.
Когда: Модули уже существуют в графе, нужно расширить.
Параметр: module_name --- имя нового модуля (snake_case).
Workflow
+----------+ +----------+ +----------+ +----------+ +-----------+ +----------+
| Phase 0 | | Phase 1 | | Phase 2 | | Phase 3 | | Phase 3.5 | | Phase 4 |
| BA Ctx |--->| Бизнес- |--->| Модули |--->| Context |--->| External |--->| NFR и |
| Import | | контекст | | (в граф) | | Map | | Contracts | | constr. |
| (граф) | | | | | | (в граф) | | (артефакт | | (граф) |
| | | | | | | | | + граф) | | |
+----------+ +----------+ +----------+ +----------+ +-----------+ +----------+
Каждая фаза завершается:
- Резюме --- что понято
- Подтверждение --- запрос верификации у пользователя
- Артефакт --- создание/обновление узлов и рёбер в Neo4j графе
Не переходи к следующей фазе без явного подтверждения пользователя!
Предварительная проверка
Режим full
- Проверь наличие Module узлов в графе:
// mcp__neo4j__read-cypher
MATCH (m:Module) RETURN count(m) AS module_count
Если module_count > 0 --- предупреди о возможной перезаписи.
- Проверь наличие BA-данных в графе:
// mcp__neo4j__read-cypher
MATCH (gpr:ProcessGroup) RETURN count(gpr) AS pg_count
Если pg_count = 0 --- предупреди, что BA-подграф пуст; Phase 0 будет пропущена.
Режим module
- Загрузи существующие модули:
// mcp__neo4j__read-cypher
MATCH (m:Module)
RETURN m.id AS id, m.name AS name, m.uc_range_start AS uc_start, m.uc_range_end AS uc_end
ORDER BY m.uc_range_start
Если модулей нет --- предложи /nacl-sa-architect в режиме full.
- Определи занятые имена и диапазоны UC.
- Определи свободный диапазон UC для нового модуля.
Phase 0: Импорт BA-контекста из графа
Цель: Прочитать BA-подграф и извлечь контекст для архитектурного проектирования.
Шаг 0.1: Загрузить все ProcessGroup и их BusinessProcess
// mcp__neo4j__read-cypher
MATCH (gpr:ProcessGroup)-[:CONTAINS]->(bp:BusinessProcess)
RETURN gpr.id AS gpr_id, gpr.name AS gpr_name,
collect({id: bp.id, name: bp.name, description: bp.description}) AS processes
Шаг 0.2: Загрузить все BusinessEntity (бизнес-объекты)
// mcp__neo4j__read-cypher
MATCH (be:BusinessEntity)
OPTIONAL MATCH (be)-[:HAS_ATTRIBUTE]->(a:EntityAttribute)
RETURN be.id AS id, be.name AS name, be.type AS type, be.description AS description,
collect({id: a.id, name: a.name, data_type: a.data_type}) AS attributes
Шаг 0.3: Загрузить automation scope (шаги для автоматизации)
// mcp__neo4j__read-cypher
MATCH (bp:BusinessProcess)-[:HAS_STEP]->(ws:WorkflowStep {stereotype: "Автоматизируется"})
OPTIONAL MATCH (ws)-[:PERFORMED_BY]->(r:BusinessRole)
RETURN bp.id AS bp_id, bp.name AS bp_name,
ws.id AS ws_id, ws.function_name AS ws_function,
r.full_name AS role_name
ORDER BY bp.id, ws.step_number
Шаг 0.4: Загрузить существующие предложения по модулям (если есть)
// mcp__neo4j__read-cypher
MATCH (gpr:ProcessGroup)-[:SUGGESTS]->(m:Module)
RETURN gpr.id AS gpr_id, gpr.name AS gpr_name,
m.id AS module_id, m.name AS module_name
Шаг 0.5: Загрузить бизнес-роли
// mcp__neo4j__read-cypher
MATCH (r:BusinessRole)
OPTIONAL MATCH (r)-[:OWNS]->(owned:BusinessProcess)
OPTIONAL MATCH (r)-[:PARTICIPATES_IN]->(part:BusinessProcess)
RETURN r.id AS id, r.full_name AS name,
collect(DISTINCT owned.name) AS owns_processes,
collect(DISTINCT part.name) AS participates_in
Шаг 0.6: Загрузить бизнес-правила
// mcp__neo4j__read-cypher
MATCH (brq:BusinessRule)
OPTIONAL MATCH (brq)-[:CONSTRAINS]->(be:BusinessEntity)
OPTIONAL MATCH (brq)-[:APPLIES_IN]->(bp:BusinessProcess)
RETURN brq.id AS id, brq.name AS name, brq.description AS description,
collect(DISTINCT be.name) AS constrains_entities,
collect(DISTINCT bp.name) AS applies_in_processes
Вывод Phase 0
Покажи пользователю сводку:
**Phase 0: Импорт BA-контекста из графа**
BA-подграф загружен:
- Группы процессов: {N} (содержат {M} бизнес-процессов)
- Бизнес-объекты: {N}
- Шаги для автоматизации: {N} (из {M} бизнес-процессов)
- Бизнес-роли: {N}
- Бизнес-правила: {N}
- Предложения по модулям: {N} (из SUGGESTS-рёбер)
Эти данные будут использованы как основа для проектирования.
Переходим к Phase 1 для верификации и уточнения.
Если BA-подграф пуст
Пропусти Phase 0, перейди к Phase 1. Работай в стандартном режиме --- собирай всю информацию от пользователя.
Phase 1: Бизнес-контекст
Цель: Верифицировать и уточнить бизнес-контекст на основе BA-данных из графа.
Если BA-данные загружены (Phase 0 выполнена)
Предложи пользователю верификацию, а не задавай вопросы с нуля:
На основе BA-подграфа я вижу:
**Бизнес-процессы:** {список процессов по группам}
**Scope автоматизации:** {N} шагов подлежат автоматизации
**Ключевые объекты:** {список бизнес-объектов}
**Роли:** {список ролей}
Вопросы для уточнения:
1. Все ли процессы должны быть покрыты системой?
2. Есть ли внешние системы для интеграции, не отражённые в графе?
3. Есть ли ограничения scope, которые нужно учесть?
Если BA-данных нет
Задавай вопросы как в стандартном sa-architect:
- Какую бизнес-задачу решает система?
- Кто целевые пользователи?
- Какие основные функциональные области?
- Что НЕ входит в scope?
- Есть ли внешние системы для интеграции?
Действия после получения ответов
- Сформулируй бизнес-цели (2--3 пункта)
- Определи success criteria
- Опиши scope (что входит, что не входит)
- Опиши целевых пользователей на основе BusinessRole из графа
Артефакт
В отличие от sa-architect, НЕ создавай markdown-файлы в docs/. Данные Phase 1 хранятся в памяти диалога и используются в последующих фазах для создания графовых узлов.
Переход
После подтверждения пользователем -> Phase 2
Phase 2: Модульная декомпозиция
Цель: Разбить систему на 3--8 функциональных модулей (Bounded Contexts) и записать их в граф.
Принципы декомпозиции
- Single Responsibility: Каждый модуль решает одну бизнес-задачу
- High Cohesion: Сущности и UC внутри модуля тесно связаны
- Low Coupling: Минимум зависимостей между модулями
- Testable Boundary: Модуль можно описать 1--2 предложениями без "и/или"
- Balanced Size: Каждый модуль содержит 3--15 UC
- BA Alignment: Модули должны коррелировать с ProcessGroup из BA-подграфа
Построение предложения
Если в Phase 0 загружены SUGGESTS-рёбра --- используй их как стартовую точку. Иначе --- используй ProcessGroup как основу для группировки:
На основе BA-подграфа я предлагаю разбить систему на следующие модули:
1. **{Название}** (mod-{code}) --- {назначение}
- Источник: ProcessGroup "{gpr_name}"
- UC range: UC100-UC199
- Процессы: {список BP из этой группы}
- Бизнес-объекты: {список BE, связанных с процессами}
2. **{Название}** (mod-{code}) --- {назначение}
...
Вопросы:
1. Согласны с такой структурой модулей?
2. Нужно ли добавить/убрать/объединить модули?
3. Есть ли общесистемные функции (авторизация, настройки)?
- Если да, они идут в модуль mod-common (UC001-UC099)
Правила декомпозиции
- Система = 3--8 модулей
- Если модуль содержит > 15 UC --- разделить
- Если модуль содержит < 3 UC --- объединить с другим
- Каждый модуль = один Bounded Context
- UC001--UC099 зарезервированы для общесистемных функций (mod-common)
- Каждый модуль получает блок из 100 номеров
Артефакт: Создание Module узлов в графе
После подтверждения пользователем --- создай Module узлы.
Шаг 2.1: Генерация ID для модулей
Формат ID: mod-{code} (код в snake_case, напр. mod-orders, mod-catalog).
Шаг 2.2: Создание каждого Module узла
Для каждого модуля выполни:
// mcp__neo4j__write-cypher
MERGE (m:Module {id: $id})
SET m.name = $name,
m.description = $description,
m.uc_range_start = $uc_range_start,
m.uc_range_end = $uc_range_end,
m.status = 'draft',
m.created = datetime()
Параметры:
$id--- например"mod-orders"$name--- человекочитаемое название, например"Управление заказами"$description--- 1--2 предложения о назначении$uc_range_start--- начало диапазона UC (int), например100$uc_range_end--- конец диапазона UC (int), например199
Шаг 2.3: Создание SUGGESTS-рёбер (если есть ProcessGroup-источник)
Для каждого модуля, который соответствует ProcessGroup:
// mcp__neo4j__write-cypher
MATCH (gpr:ProcessGroup {id: $gpr_id})
MATCH (m:Module {id: $module_id})
MERGE (gpr)-[:SUGGESTS]->(m)
Шаг 2.4: Верификация созданных модулей
// mcp__neo4j__read-cypher
MATCH (m:Module)
OPTIONAL MATCH (gpr:ProcessGroup)-[:SUGGESTS]->(m)
RETURN m.id AS id, m.name AS name, m.description AS description,
m.uc_range_start AS uc_start, m.uc_range_end AS uc_end,
collect(gpr.name) AS source_process_groups
ORDER BY m.uc_range_start
Покажи пользователю таблицу:
**Phase 2: Модули записаны в граф**
| Модуль | ID | UC Range | Источник (ProcessGroup) |
|--------|----|----------|-------------------------|
| {name} | {id} | UC{start}-UC{end} | {gpr_names} |
| ... | ... | ... | ... |
Всего модулей: {N}
Переход
После подтверждения модульной декомпозиции -> Phase 3
Phase 3: Context Map
Цель: Определить межмодульные зависимости и записать их в граф.
Действия
На основе BA-данных из Phase 0 и модулей из Phase 2:
-
Определи направления зависимостей:
- Какой модуль от какого зависит?
- Кто владеет данными (CRUD)?
- Кто только читает данные?
-
Определи типы связей:
data_read--- модуль читает справочники/сущности другого модуляoperation_call--- модуль инициирует бизнес-процесс в другом модулеevent--- модуль реагирует на изменения в другом модуле
-
Определи общие сущности:
- Какие BusinessEntity из графа используются несколькими ProcessGroup?
- Определи владельца и читателей
Для анализа общих сущностей выполни:
// mcp__neo4j__read-cypher
MATCH (ws:WorkflowStep)-[:READS|PRODUCES|MODIFIES]->(be:BusinessEntity)
MATCH (bp:BusinessProcess)-[:HAS_STEP]->(ws)
MATCH (gpr:ProcessGroup)-[:CONTAINS]->(bp)
RETURN be.id AS entity_id, be.name AS entity_name,
collect(DISTINCT {gpr_id: gpr.id, gpr_name: gpr.name,
rel_type: type((ws)-[:READS|PRODUCES|MODIFIES]->(be))}) AS used_by_groups
Вопросы для пользователя
Я построил карту зависимостей между модулями:
{Текстовая таблица зависимостей}
Вопросы:
1. Правильно ли я определил направления зависимостей?
2. Есть ли зависимости, которые я пропустил?
3. Правильно ли определены владельцы общих сущностей?
Артефакт: Создание межмодульных рёбер в графе
Шаг 3.1: Создание DEPENDS_ON рёбер между модулями
Для каждой зависимости:
// mcp__neo4j__write-cypher
MATCH (m1:Module {id: $source_module_id})
MATCH (m2:Module {id: $target_module_id})
MERGE (m1)-[r:DEPENDS_ON]->(m2)
SET r.type = $dep_type,
r.description = $description
Параметры:
$source_module_id--- модуль-потребитель$target_module_id--- модуль-поставщик$dep_type---"data_read","operation_call", или"event"$description--- что передаётся (напр."Читает данные клиентов")
Шаг 3.2: Предварительное распределение BusinessEntity по модулям
На основе анализа владения --- создай предварительные CONTAINS_ENTITY-рёбра:
// mcp__neo4j__write-cypher
MATCH (m:Module {id: $module_id})
MERGE (de:DomainEntity {id: $entity_id})
SET de.name = $entity_name,
de.module = $module_id,
de.status = 'draft',
de.created = datetime()
MERGE (m)-[:CONTAINS_ENTITY]->(de)
Также создай BA-to-SA handoff-ребро:
// mcp__neo4j__write-cypher
MATCH (be:BusinessEntity {id: $ba_entity_id})
MATCH (de:DomainEntity {id: $sa_entity_id})
MERGE (be)-[:REALIZED_AS]->(de)
Шаг 3.3: Верификация Context Map
// mcp__neo4j__read-cypher
MATCH (m1:Module)-[r:DEPENDS_ON]->(m2:Module)
RETURN m1.name AS source, m2.name AS target,
r.type AS dep_type, r.description AS description
ORDER BY m1.name, m2.name
// mcp__neo4j__read-cypher
MATCH (m:Module)-[:CONTAINS_ENTITY]->(de:DomainEntity)
RETURN m.name AS module, collect({id: de.id, name: de.name}) AS entities
ORDER BY m.name
Покажи пользователю результат:
**Phase 3: Context Map записан в граф**
Зависимости:
| Источник | Приёмник | Тип | Описание |
|----------|----------|-----|----------|
| {source} | {target} | {type} | {desc} |
Распределение сущностей:
| Модуль | Сущности |
|--------|----------|
| {module} | {entity_list} |
Переход
После подтверждения Context Map -> Phase 3.5 (External Contracts)
Phase 3.5: External Contracts
Цель: Зафиксировать каждый внешний провайдер и каждый wire-протокол как отдельный артефакт-контракт, который читает граф SA, и который консумируют downstream-скилы (nacl-tl-plan, nacl-tl-sync wire-evidence gate, nacl-tl-dev-be, nacl-tl-dev-fe, nacl-tl-qa).
Why this phase exists. 13 из ~60 сигналов в постмортемах двух проектов — это сбои внешних API / wire-протоколов: kie.ai (оба проекта), TUS upload, обратный прокси https-схема, ffmpeg/ffprobe runtime, SSE frame envelope. Локально всё компилировалось, типы совпадали, но реальный wire не работал. См. docs/retrospectives/project-beta-runtime-baseline.md §§ A1–A9, B1–B7 и сводку §I "Provider/external-API contracts" и "Wire-envelope protocols". Цель этой фазы — превратить эту негативную плоскость в позитивный артефакт.
Артефакт
Один Markdown-файл на провайдер ИЛИ на протокол:
.tl/external-contracts/<slug>.md
Примеры файлов (этот скил их создаёт по результатам диалога с пользователем):
| Тип | Slug | Описание |
|---|---|---|
| provider | kie.md | kie.ai — LLM endpoint (Anthropic-shape), async polling lifecycle, model namespace без google/-префикса. См. baseline § A1–A3, A5, A7–A9. |
| provider | deepgram.md | Deepgram (ASR) — sync provider; API key обязателен; pre-provider стадии (storage fetch, ffmpeg extract) НЕ требуют ключа (см. baseline § A4, F2). |
| provider | anthropic.md | Anthropic Claude (прямой вызов) — отличный envelope от kie.ai. |
| protocol | tus.md | TUS upload — Location header public-origin; Caddy respectForwardedHeaders + X-Forwarded-Proto; Fastify addContentTypeParser для application/offset+octet-stream (см. baseline § A6, B1, B2, B4). |
| protocol | sse.md | Server-Sent Events — frame envelope event: <type>\ndata: <json>\n\n; без event:-строки браузер дефолтит на 'message' (см. baseline § B3). |
| protocol | multipart-presigned.md | Прямой S3 presigned multipart upload (когда TUS заменён — см. baseline § B5). |
| protocol | reverse-proxy-url-scheme.md | Общие правила https-public-URL за прокси (cross-cutting между TUS, presigned, и любыми Location-headers). |
| protocol | ffmpeg-ffprobe-runtime.md | ffmpeg/ffprobe runtime — seekable stdin для MP4 demux (см. baseline § C5); ffprobe не принимает s3://-URI (§ B6). |
Шаблон с обязательными и опциональными полями: .tl/external-contracts/_template.md (создаётся в W6 как сам артефакт скила).
Required fields (для каждого файла)
| # | Поле | Описание |
|---|---|---|
| 1 | Identity | Имя, `kind: provider |
| 2 | Endpoint | Полный URL включая version-path (НЕ только base host, если адаптер добавляет /api/v1); все вызываемые эндпоинты (method + path + назначение); механизм discovery (static-catalog или http-list-endpoint); версионирование. |
| 3 | Auth | Scheme (Bearer / x-api-key / none / ...); имя переменной окружения с секретом; поведение при отсутствии секрета (требование к nacl-tl-qa stage decomposition — см. W3). |
| 4 | Request shape | Content-Type; обязательные заголовки; тело с литеральными именами полей (не "соответствует TS-типу", а именно прописанные строки — это был тройной mismatch TUS mime_type vs filetype, baseline § B1); query params. |
| 5 | Response shape | Success-status; тело с литеральными именами полей; точная цепочка accessor для извлечения load-bearing значения (kie.ai Anthropic envelope: response.content[0].text, НЕ response.choices[0].message.content — baseline § A1, A5); обязательные ответные заголовки (Location, Tus-Resumable, ...). |
| 6 | Lifecycle: sync vs async | Явно sync или async. Если async — submit endpoint, poll endpoint, polling cadence (min/max/backoff), polling timeout (что surface'ит FAILED), cancellation. (См. baseline § A2 — kie.ai image_gen переехал sync → async mid-build.) |
| 7 | File URL reachability | Когда контракт возвращает или потребляет URL: ожидаемая схема (https:// для browser-facing; s3:// отвергается ffprobe — baseline § B6); кто проставляет X-Forwarded-Proto; включён ли respectForwardedHeaders; public origin vs origin server; TTL; toolchain-совместимость. |
| 8 | Failure codes | Перечисление HTTP-кодов с трактовкой и обязательным consumer action: 4xx auth, 4xx model/endpoint, 4xx envelope, 429 rate-limit, 5xx transient. |
| 9 | Model namespace / catalog (только для kind: provider) | Источник каталога (static-list-in-this-file или http-list-endpoint); политика namespace-префикса (NONE / <vendor>/ / <vendor>: — БЫТЬ ТОЧНЫМ; baseline § A3 — google/-префикс к nano-banana дал 400); используемые модели verbatim. |
| 10 | Fixture-test path | Repo-relative путь к runnable-тесту, который загружает записанный response fixture и парсит его через production code-path без моков парсера. Это именно то, что Wire-Evidence Gate (W2) ищет как wire-evidence:fixture:<path>. |
| 11 | Smoke-test path | Repo-relative путь к runnable smoke-тесту, который ходит в реальный sandbox/staging провайдер. Может требовать network и переменные окружения; обязан быть запускаемым по требованию nacl-tl-qa (см. W3 Stage Decomposition Gate). |
Optional fields
Включаются ТОЛЬКО когда интеграция использует соответствующую surface: webhook callback shape; stream / SSE frame envelope (если используется streaming); multi-tenant routing header; idempotency-key header; pagination shape; concurrency / per-key rate-limits; region pinning; vendor SDK version pin; framework-specific gotchas (Fastify, Caddy, ffmpeg) — см. полный список в .tl/external-contracts/_template.md.
Worked example 1: kie.ai (provider)
Этот пример иллюстрирует все обязательные поля для канонического provider'а с async-lifecycle:
File: .tl/external-contracts/kie.md
Kind: provider
Endpoint:
Base URL: https://kie-ai.redpandaai.co
All endpoints:
POST /api/v1/jobs/createTask - async generation (image_gen)
GET /api/v1/jobs/recordInfo - poll task status
POST /api/v1/messages - Anthropic-shape LLM (sync)
Discovery: static-catalog (this file)
Versioning: path segment /api/v1; pinned to v1 as of 2026-05-19
Auth:
Scheme: x-api-key
Secret env var: KIE_API_KEY
Missing-secret behavior: nacl-tl-qa decomposes pipeline; pre-provider
stages run, provider stage marks PROVIDER_QA
NOT_RUN; cannot be silently SKIP
Request (Anthropic-shape):
Content-Type: application/json
Headers: x-api-key, anthropic-version: 2023-06-01
Body literal: { model, max_tokens, messages: [{role, content}] }
Response (Anthropic-shape):
Success status: 200
Body literal: { id, type, role, model, content: [{type, text}], ... }
Parsing path: response.content[0].text
(NOT response.choices[0].message.content)
Lifecycle:
Mode (LLM call): sync
Mode (image_gen): async
Submit: POST /api/v1/jobs/createTask -> { task_id }
Poll: GET /api/v1/jobs/recordInfo?id -> { status, result }
Cadence: start 2s; backoff x1.5; max 30s; cap 5min total
Timeout: on 5min -> surface FAILED, not silent hang
File URL reachability: N/A (no file URLs in/out)
Failure codes:
401: bad x-api-key -> halt AUTH_FAILED
404: model namespace -> halt MODEL_NOT_FOUND (see "Model namespace")
400: bad envelope shape -> halt CONTRACT_FAILED
429: rate limit -> backoff 2s/4s/8s; max 3 retries
5xx: transient -> backoff; budget 5 retries; then surface
Model namespace:
Catalog: http-list at GET /api/v1/models
Namespace prefix: NONE (pass model id verbatim; NO "google/" prefix)
Models in use: claude-3-5-sonnet-20241022, nano-banana-v1, ...
Fixture-test path:
Fixture file: tests/fixtures/kie-ai/protocol-response.json
Test file: tests/wire/kie-ai.fixture.test.ts
Asserts: KieAiProtocolProvider.parse(fixture) extracts content[0].text
without mocking the response shape.
Run command: pnpm -F api test tests/wire/kie-ai.fixture.test.ts
Smoke-test path:
File: tests/smoke/kie-ai.smoke.test.ts
Env vars: KIE_API_KEY (sandbox), KIE_BASE_URL
Sandbox/prod: sandbox
Run command: pnpm -F api smoke:kie
Stage decomposition: PROVIDER_QA
Worked example 2: TUS upload (protocol)
Этот пример иллюстрирует обязательные поля для канонического протокола с реверс-прокси-чувствительными URL-схемами:
File: .tl/external-contracts/tus.md
Kind: protocol
Endpoint:
Base URL: https://<public-origin>/tus
All endpoints:
POST /tus -> 201 + Location: <upload-url-public-origin>
HEAD /tus/<id> -> 200 + Upload-Offset
PATCH /tus/<id> -> 204; Content-Type: application/offset+octet-stream
(MUST be registered with Fastify via
fastify.addContentTypeParser; else 415 — § B2)
Discovery: protocol spec https://tus.io/protocols/resumable-upload
Versioning: Tus-Resumable: 1.0.0 header
Auth:
Scheme: Bearer (project JWT) on POST + PATCH
Secret env var: (client-bearer; not a vendor secret)
Missing-secret behavior: returns 401 before any storage I/O
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 27
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
nacl-sa-architect- Source
- github.com/itsalt/nacl