/nacl-sa-architect --- Архитектурная декомпозиция (Graph)

SkillDev tools

Decomposes 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.

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:

NodeKey PropertiesDescription
Moduleid, name, description, uc_range_start, uc_range_endFunctional module (Bounded Context)
Requirementid, description, type, priorityFunctional 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.  |
| (граф)   |    |          |    |          |    | (в граф) |    | (артефакт |    | (граф)   |
|          |    |          |    |          |    |          |    |  + граф)  |    |          |
+----------+    +----------+    +----------+    +----------+    +-----------+    +----------+

Каждая фаза завершается:

  1. Резюме --- что понято
  2. Подтверждение --- запрос верификации у пользователя
  3. Артефакт --- создание/обновление узлов и рёбер в Neo4j графе

Не переходи к следующей фазе без явного подтверждения пользователя!


Предварительная проверка

Режим full

  1. Проверь наличие Module узлов в графе:
// mcp__neo4j__read-cypher
MATCH (m:Module) RETURN count(m) AS module_count

Если module_count > 0 --- предупреди о возможной перезаписи.

  1. Проверь наличие BA-данных в графе:
// mcp__neo4j__read-cypher
MATCH (gpr:ProcessGroup) RETURN count(gpr) AS pg_count

Если pg_count = 0 --- предупреди, что BA-подграф пуст; Phase 0 будет пропущена.

Режим module

  1. Загрузи существующие модули:
// 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.

  1. Определи занятые имена и диапазоны UC.
  2. Определи свободный диапазон 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:

  1. Какую бизнес-задачу решает система?
  2. Кто целевые пользователи?
  3. Какие основные функциональные области?
  4. Что НЕ входит в scope?
  5. Есть ли внешние системы для интеграции?

Действия после получения ответов

  1. Сформулируй бизнес-цели (2--3 пункта)
  2. Определи success criteria
  3. Опиши scope (что входит, что не входит)
  4. Опиши целевых пользователей на основе BusinessRole из графа

Артефакт

В отличие от sa-architect, НЕ создавай markdown-файлы в docs/. Данные Phase 1 хранятся в памяти диалога и используются в последующих фазах для создания графовых узлов.

Переход

После подтверждения пользователем -> Phase 2


Phase 2: Модульная декомпозиция

Цель: Разбить систему на 3--8 функциональных модулей (Bounded Contexts) и записать их в граф.

Принципы декомпозиции

  1. Single Responsibility: Каждый модуль решает одну бизнес-задачу
  2. High Cohesion: Сущности и UC внутри модуля тесно связаны
  3. Low Coupling: Минимум зависимостей между модулями
  4. Testable Boundary: Модуль можно описать 1--2 предложениями без "и/или"
  5. Balanced Size: Каждый модуль содержит 3--15 UC
  6. 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:

  1. Определи направления зависимостей:

    • Какой модуль от какого зависит?
    • Кто владеет данными (CRUD)?
    • Кто только читает данные?
  2. Определи типы связей:

    • data_read --- модуль читает справочники/сущности другого модуля
    • operation_call --- модуль инициирует бизнес-процесс в другом модуле
    • event --- модуль реагирует на изменения в другом модуле
  3. Определи общие сущности:

    • Какие 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Описание
providerkie.mdkie.ai — LLM endpoint (Anthropic-shape), async polling lifecycle, model namespace без google/-префикса. См. baseline § A1–A3, A5, A7–A9.
providerdeepgram.mdDeepgram (ASR) — sync provider; API key обязателен; pre-provider стадии (storage fetch, ffmpeg extract) НЕ требуют ключа (см. baseline § A4, F2).
provideranthropic.mdAnthropic Claude (прямой вызов) — отличный envelope от kie.ai.
protocoltus.mdTUS upload — Location header public-origin; Caddy respectForwardedHeaders + X-Forwarded-Proto; Fastify addContentTypeParser для application/offset+octet-stream (см. baseline § A6, B1, B2, B4).
protocolsse.mdServer-Sent Events — frame envelope event: <type>\ndata: <json>\n\n; без event:-строки браузер дефолтит на 'message' (см. baseline § B3).
protocolmultipart-presigned.mdПрямой S3 presigned multipart upload (когда TUS заменён — см. baseline § B5).
protocolreverse-proxy-url-scheme.mdОбщие правила https-public-URL за прокси (cross-cutting между TUS, presigned, и любыми Location-headers).
protocolffmpeg-ffprobe-runtime.mdffmpeg/ffprobe runtime — seekable stdin для MP4 demux (см. baseline § C5); ffprobe не принимает s3://-URI (§ B6).

Шаблон с обязательными и опциональными полями: .tl/external-contracts/_template.md (создаётся в W6 как сам артефакт скила).

Required fields (для каждого файла)

#ПолеОписание
1IdentityИмя, `kind: provider
2EndpointПолный URL включая version-path (НЕ только base host, если адаптер добавляет /api/v1); все вызываемые эндпоинты (method + path + назначение); механизм discovery (static-catalog или http-list-endpoint); версионирование.
3AuthScheme (Bearer / x-api-key / none / ...); имя переменной окружения с секретом; поведение при отсутствии секрета (требование к nacl-tl-qa stage decomposition — см. W3).
4Request shapeContent-Type; обязательные заголовки; тело с литеральными именами полей (не "соответствует TS-типу", а именно прописанные строки — это был тройной mismatch TUS mime_type vs filetype, baseline § B1); query params.
5Response shapeSuccess-status; тело с литеральными именами полей; точная цепочка accessor для извлечения load-bearing значения (kie.ai Anthropic envelope: response.content[0].text, НЕ response.choices[0].message.content — baseline § A1, A5); обязательные ответные заголовки (Location, Tus-Resumable, ...).
6Lifecycle: 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.)
7File URL reachabilityКогда контракт возвращает или потребляет URL: ожидаемая схема (https:// для browser-facing; s3:// отвергается ffprobe — baseline § B6); кто проставляет X-Forwarded-Proto; включён ли respectForwardedHeaders; public origin vs origin server; TTL; toolchain-совместимость.
8Failure codesПеречисление HTTP-кодов с трактовкой и обязательным consumer action: 4xx auth, 4xx model/endpoint, 4xx envelope, 429 rate-limit, 5xx transient.
9Model namespace / catalog (только для kind: provider)Источник каталога (static-list-in-this-file или http-list-endpoint); политика namespace-префикса (NONE / <vendor>/ / <vendor>: — БЫТЬ ТОЧНЫМ; baseline § A3 — google/-префикс к nano-banana дал 400); используемые модели verbatim.
10Fixture-test pathRepo-relative путь к runnable-тесту, который загружает записанный response fixture и парсит его через production code-path без моков парсера. Это именно то, что Wire-Evidence Gate (W2) ищет как wire-evidence:fixture:<path>.
11Smoke-test pathRepo-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