docs: define platform boundaries and contracts

This commit is contained in:
Ochenstarik 2026-09-10 23:29:28 +07:00
parent fcf014cf71
commit 10968eacf7
17 changed files with 373 additions and 0 deletions

25
AGENTS.md Normal file
View file

@ -0,0 +1,25 @@
# AGENTS.md
## Назначение
Krona Staff — автономно развёртываемый блок Krona Business Platform, а не отдельный конкурирующий продукт. Предметные функции добавляются только по подтверждённым требованиям владельца.
## Неизменяемые инварианты
- Core владеет Tenant, User, Role, Permission, SSO, platform settings, files, notifications и event bus. Staff владеет Employee и HR-доменом. `User != Employee`.
- Все tenant-данные имеют `tenant_id`; запросы идут через tenant-scoped transaction; новые таблицы включают и форсируют PostgreSQL RLS. Добавляйте тест утечки между двумя tenant.
- ID — ULID в `VARCHAR(26)`. Деньги — `BIGINT` minor units + ISO 4217 currency. Временные метки — UTC; локальная интерпретация — IANA timezone tenant.
- Межмодульные вызовы только через публичные порты/platform gateway или versioned events. Прямые импорты и запись в чужие таблицы запрещены.
- События имеют version, tenant, subject, actor, correlation/causation; публикация через transactional outbox; consumer через inbox и идемпотентность.
- Локальный диск не является постоянным хранилищем. Вложения — через `FileStoragePort`/platform Files/S3.
- Standalone adapters совместимы, мигрируемы и отключаемы; они не становятся вторым постоянным source of truth.
## Перед изменением
Прочитайте `docs/109-PLATFORM-BLOCK-CONTRACT.md` и связанные актуальные контракты в `ochenstarik/business-platform`. Если контракт изменился, сначала синхронизируйте manifest/ADR и тесты. Не копируйте архитектуру Omnichannel механически.
## Проверка
Запускайте `npm run check`. Для изменений БД дополнительно проверьте up/down на чистой PostgreSQL и tenant isolation. Не объявляйте Docker/restore/CI проверенными, если соответствующая среда реально не запускалась.
Файлы UTF-8 без BOM, LF. Коммиты небольшие, объясняют причину. Ветка задачи: `codex/bootstrap-platform-foundation` или отдельная ASCII feature-ветка.

5
LICENSE Normal file
View file

@ -0,0 +1,5 @@
Copyright (c) 2026 Krona. All rights reserved.
This software and its documentation are proprietary. No permission is granted to use, copy,
modify, distribute, sublicense, or disclose them except under a separate written agreement
with the copyright holder.

25
README.md Normal file
View file

@ -0,0 +1,25 @@
# Krona Staff
Архитектурная основа блока сотрудников для Krona Business Platform. Это не готовая HRM: предметные правила табеля, графиков, отпусков, расчёта зарплаты, кадровых документов и отчётности намеренно не реализованы до требований владельца.
Сейчас репозиторий содержит совместимое техническое ядро: два runtime-режима, порты платформы, PostgreSQL/RLS, инфраструктуру outbox/inbox/idempotency/audit, проверяемые OpenAPI и module manifest, health endpoints, контейнеризацию и CI.
## Быстрый старт
```bash
cp .env.example .env
docker compose --profile tools run --rm migrate
docker compose up --build app postgres redis
```
Открыть: shell `http://localhost:8080/`, OpenAPI JSON `/openapi.json`, проверки `/live`, `/ready`, `/version`.
Без Docker: Node.js 22+, PostgreSQL 17 и Redis 7; затем `npm ci`, `npm run db:migrate`, `npm run dev`.
## Команды
- `npm run check` — lint, typecheck, tests, миграции, контракты и build.
- `npm run db:migrate` / `npm run db:migrate:down` — применить/откатить миграции.
- `npm run check:contracts` — проверить manifest/settings/OpenAPI.
Архитектурные решения: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), интеграция: [docs/PLATFORM-INTEGRATION.md](docs/PLATFORM-INTEGRATION.md), будущий объём: [docs/ROADMAP.md](docs/ROADMAP.md).

11
SECURITY.md Normal file
View file

@ -0,0 +1,11 @@
# Security Policy
Сообщайте об уязвимостях приватно владельцу Forgejo, не создавая публичный issue и не прикладывая персональные/зарплатные данные.
## Baseline
Core SSO/JWT и RBAC в platform mode; fail-closed adapters; tenant scope + forced PostgreSQL RLS; least privilege permissions; audit trail; TLS; security headers; rate limits на будущих write endpoints; idempotency; validation; parameterized SQL; encrypted backups; S3 outside webroot; secrets только env/secret store.
PII и payroll относятся к особо чувствительным данным. До их появления должны быть утверждены classification, field-level permissions, masking, encryption/key rotation, audit access, retention/deletion и incident response. Логи не содержат токены, зарплатные значения или полные персональные payload.
Dependencies проверяются `npm audit` в CI; container и SBOM/image scan должны быть подключены к доступному registry scanner перед production release.

84
api/openapi.yaml Normal file
View file

@ -0,0 +1,84 @@
openapi: 3.1.0
info:
title: Krona Staff API
version: 0.1.0
description: Operational API for the Krona Staff foundation. HR domain endpoints are intentionally absent until owner requirements are approved.
license:
name: Proprietary
url: https://git.ochenstarik.ru/ochenstarik/krona-staff/src/branch/main/LICENSE
servers:
- url: /api/v1/staff
security: []
paths:
/live:
get:
summary: Check process liveness
operationId: getLiveness
tags: [Operations]
responses:
'200':
description: Process is alive
content:
application/json:
schema: { $ref: '#/components/schemas/Health' }
'400': { $ref: '#/components/responses/BadRequest' }
/ready:
get:
summary: Check dependency readiness
operationId: getReadiness
tags: [Operations]
responses:
'200':
description: Required dependencies are ready
content:
application/json:
schema: { $ref: '#/components/schemas/Health' }
'503': { description: A required dependency is unavailable }
'400': { $ref: '#/components/responses/BadRequest' }
/version:
get:
summary: Read runtime build identity
operationId: getVersion
tags: [Operations]
responses:
'200':
description: Runtime build identity
content:
application/json:
schema: { $ref: '#/components/schemas/Version' }
'400': { $ref: '#/components/responses/BadRequest' }
components:
responses:
BadRequest:
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/Problem' }
schemas:
Ulid:
type: string
pattern: '^[0-9A-HJKMNP-TV-Z]{26}$'
examples: [01J00000000000000000000000]
Health:
type: object
additionalProperties: false
required: [status]
properties:
status: { type: string }
Problem:
type: object
additionalProperties: false
required: [type, title, status, trace_id]
properties:
type: { type: string, format: uri-reference }
title: { type: string }
status: { type: integer, minimum: 400, maximum: 599 }
trace_id: { $ref: '#/components/schemas/Ulid' }
Version:
type: object
additionalProperties: false
required: [name, version, mode]
properties:
name: { const: krona-staff }
version: { type: string }
mode: { enum: [standalone, platform] }

22
docs/ARCHITECTURE.md Normal file
View file

@ -0,0 +1,22 @@
# Архитектура
Krona Staff — deployable modular service с hexagonal boundaries. HTTP и workers вызывают application layer; domain contexts зависят только от ports; adapters обеспечивают standalone либо platform integration; PostgreSQL, Redis/BullMQ и object storage находятся на внешнем краю.
```text
HTTP / worker -> application -> bounded-context contracts
|-> Identity / Authorization / Settings / Files / Notifications / EventBus ports
|-> tenant-scoped PostgreSQL repositories + transactional outbox
ports <- standalone adapters | platform-gateway adapters
```
Текущий код — foundation: health/version, конфигурация, ULID/event contracts, tenant transaction и инфраструктурная миграция. Доменный API отсутствует намеренно.
## Контексты
Предусмотрены границы: Employee Directory, Employment, Organization Structure, Scheduling, Time & Attendance, Leave, Payroll, Documents, Audit/Reporting. Между ними допускаются команды/события и стабильные ID, но не общий «богатый» HR-aggregate. Пакеты будут созданы по мере утверждения требований, чтобы пустые интерфейсы не стали ложным контрактом.
## Данные и надёжность
Shared database/shared schema, `tenant_id` в каждой tenant-таблице, application scope и forced RLS. Запись доменного состояния и outbox должна быть одной транзакцией. Inbox `(tenant_id,event_id)` подавляет повторную обработку; HTTP idempotency хранит request hash и результат. Сервис stateless, поэтому scale-out позже не требует переноса локальных файлов.
См. [ADR-001](adr/ADR-001-technology-and-runtime-modes.md).

9
docs/BACKUP-RESTORE.md Normal file
View file

@ -0,0 +1,9 @@
# Backup и restore
Production baseline: ежедневный `pg_dump` custom format, непрерывный WAL/PITR когда RPO этого требует, versioned S3 object copies и отдельный экспорт manifest/checksums. Копии шифруются recipient-based ключом вне сервера; retention задаётся политикой владельца. Секреты и ключ расшифрования в архив не входят.
Restore runbook: остановить записи, проверить checksum/decrypt, восстановить в новую БД, применить только необходимые миграции, сверить tenant counts/outbox/inbox/audit, проверить `/ready`, выполнить выборочную сверку вложений, переключить connection string, сохранить audit evidence. Никогда не восстанавливать поверх единственной production БД.
Тест восстановления обязателен по расписанию в изолированной среде. До принятия RPO/RTO и retention документ является архитектурным шаблоном, а не обещанием показателей.
Tenant export/import использует versioned archive, ULID, schema version, object references и checksum. Import идемпотентен и не может перезаписать другой tenant.

9
docs/DATA-OWNERSHIP.md Normal file
View file

@ -0,0 +1,9 @@
# Владение данными
Core — system of record для tenants, user accounts, roles, permissions, platform settings, file metadata/storage access, notifications и event bus. Staff хранит только ссылки-ULID на Core records и не применяет foreign key через границу автономного deployment.
Staff станет system of record для Employee, Employment и утверждённых HR aggregates. Scheduling/attendance/leave/payroll ownership уточняется с владельцем до схемы. Внешние кадровые/бухгалтерские системы подключаются через adapters; authority и направление sync фиксируются отдельным ADR.
Каждая tenant-таблица: `tenant_id VARCHAR(26) NOT NULL`, составные unique/index keys с tenant, application scoping и forced RLS. Cross-tenant операции запрещены; системный export выполняется отдельной привилегированной ролью с audit.
PII/payroll fields получают classification, field-level permissions, шифрование и retention после определения состава. Секреты не являются settings и поступают из secret store/env.

9
docs/DEPLOYMENT.md Normal file
View file

@ -0,0 +1,9 @@
# Развёртывание
Одна команда локально: скопировать `.env.example` в `.env`, затем `docker compose --profile tools run --rm migrate` и `docker compose up --build`. Compose поднимает приложение, PostgreSQL и Redis. Object storage не включён, пока нет файлового сценария; production использует Platform Files или S3-compatible adapter.
Production: один stateless app container/process, отдельные PostgreSQL и Redis, TLS/reverse proxy, secret manager, external object storage, централизованные logs/metrics/traces. `/live` проверяет процесс, `/ready` — обязательные зависимости, `/version` — release и runtime mode. Container работает non-root, read-only filesystem.
HA сейчас не требуется. Для будущего scale-out: общий PostgreSQL/Redis/S3, несколько API/worker nodes, leader election для scheduler, outbox relay и идемпотентные consumers. Локальные вложения запрещены. Linux и Windows deployment сохраняют паритет на уровне Node/container contract.
Миграции запускаются отдельным one-shot job до rollout. Down migration существует, но удаление данных требует отдельного backup и явного решения оператора.

17
docs/DOMAIN-BOUNDARIES.md Normal file
View file

@ -0,0 +1,17 @@
# Доменные границы
| Контекст | Будущее владение Staff | Сейчас |
|---|---|---|
| Employee Directory | Карточка сотрудника и связь с Core User по nullable reference | Только manifest |
| Employment | Трудовые отношения и их жизненный цикл | Ожидает требований |
| Organization Structure | HR-представление подразделений/позиций | Ожидает решения о source of truth |
| Scheduling | Плановые смены/графики | Ожидает требований |
| Time & Attendance | Факты времени/явки и корректировки | Ожидает требований |
| Leave | Заявки и остатки отпусков/отсутствий | Ожидает требований |
| Payroll | Расчётные входы/результаты и защищённый доступ | Ожидает требований и юрисдикции |
| Documents | HR-документы и ссылки на Platform Files | Ожидает требований |
| Audit/Reporting | HR audit projections и разрешённые отчёты | Базовый audit transport |
Core User — учётная запись для входа и прав. Employee — предметная карточка человека в трудовом контексте. Они могут быть связаны, но ни один не является другим: сотрудник может не иметь аккаунта, аккаунт может не быть сотрудником.
Запрещено до требований: придумывать ставки, формулы зарплаты, нормы времени, статусы кадровых процессов, юридические реквизиты, виды отпусков или фиктивные CRUD endpoints.

View file

@ -0,0 +1,11 @@
# Интеграция с Business Platform
Основание: актуальная ветка `main` `ochenstarik/business-platform`, прежде всего Platform Block Contract 109, Module System 34, Lifecycle 54, Configuration Engine 51, Field Types 52, Integration 15 и deployment 93/97.
В `platform` режиме gateway/JWT adapter получает tenant, actor и permissions из Core SSO; Staff не создаёт tenant/user/role. Settings, Files, Notifications и Event Bus доступны через отдельные порты. Реализация JWT намеренно fail-closed до публикации точного Core gateway/JWKS runtime contract.
В `standalone` режиме совместимый adapter выдаёт один явно настроенный tenant/actor для разработки. Он не содержит таблиц пользователей и ролей и должен отключаться сменой `STAFF_RUNTIME_MODE=platform`. Миграция в platform означает сопоставление standalone ULID с Core references, проверку владения и отключение adapter.
Манифест `module.json` объявляет hard dependency только на `ru.krona.core`, permissions, entity ownership, settings schema, configurable entities и UI slots. События пока пусты: имена и payload нельзя утверждать до бизнес-требований. API versioned; будущие события также versioned и используют platform envelope.
Omnichannel использован только как существующий пример. Здесь заранее исключены его риски: параллельная identity/tenant модель, неоднозначный tenant-to-project mapping, доменные настройки вне platform schema и локально-незаменимое файловое состояние.

16
docs/ROADMAP.md Normal file
View file

@ -0,0 +1,16 @@
# Roadmap
## Готово: foundation
Runtime modes and ports, ULID/event envelope, PostgreSQL RLS infrastructure, outbox/inbox/idempotency/audit, manifest/settings/OpenAPI validation, health/version, Docker/Compose, CI and documentation.
## Следующий этап — только после требований владельца
1. Уточнить jurisdiction, terminology, data classification and ownership.
2. Согласовать Employee Directory и Employment workflows, затем создать первую vertical slice.
3. Отдельно описать Organization Structure, Scheduling, Time & Attendance, Leave, Payroll, Documents, Audit/Reporting.
4. Получить фактический Core gateway/JWT, Module SDK, Settings/Files/Notifications/Event Bus runtime contracts и реализовать platform adapters/conformance tests.
5. Утвердить event catalog/OpenAPI changes до публикации.
6. Утвердить backup RPO/RTO/retention и провести documented restore drill.
Никакой пункт не означает автоматически готовую бизнес-функцию или срок.

View file

@ -0,0 +1,20 @@
# ADR-001: TypeScript service and runtime adapters
- Status: Accepted for foundation
- Date: 2026-09-10
## Context
Platform contracts are TypeScript, baseline API is REST/OpenAPI 3.1, PostgreSQL/Redis/BullMQ are normative, blocks need independent deployment without duplicating Core identity. Staff business rules are unknown.
## Decision
Use Node.js 22 + strict TypeScript, Fastify, PostgreSQL 17, Redis/BullMQ, Zod, Vitest and a single stateless container. Keep domain code behind ports. Select either standalone adapters or platform-gateway adapters through configuration. Use ULID `VARCHAR(26)`, UTC, `BIGINT` minor money, transactional outbox/inbox and S3 abstraction.
Standalone provides only explicit development tenant/actor references; no durable user/role source. Platform mode fails closed until verified Core JWT/gateway contracts are implemented.
## Consequences
The module can deploy separately and share platform contract types with minimal translation. PostgreSQL RLS gives defense in depth. Runtime integration remains explicit work rather than a mocked claim. Fastify adds a different web runtime from Omnichannel, but platform compatibility is contractual, not framework inheritance.
Rejected: copy Omnichannel/Python mechanically; embed inside platform monolith; invent a local identity service; implement HR endpoints before requirements.

30
module.json Normal file
View file

@ -0,0 +1,30 @@
{
"$schema": "./schemas/module.schema.json",
"id": "ru.krona.staff",
"name": "Krona Staff",
"version": "0.1.0",
"min_platform_version": "3.2.0",
"dependencies": ["ru.krona.core"],
"optional_dependencies": [],
"permissions": [
"staff:employee:read",
"staff:employee:write",
"staff:employment:read",
"staff:employment:write",
"staff:sensitive:read",
"staff:sensitive:write",
"staff:audit:read"
],
"entities": ["Employee", "Employment", "OrganizationUnit"],
"events_emitted": [],
"events_consumed": [],
"settings_schema": "settings.schema.json",
"configurable_entities": ["Employee", "Employment", "OrganizationUnit"],
"promote_targets": [],
"api_prefix": "/api/v1/staff",
"routes": ["/staff"],
"ui_extensions": [
{ "slot": "navigation", "component": "StaffNavigation" },
{ "slot": "settings.module", "component": "PlatformGeneratedSettings" }
]
}

View file

@ -0,0 +1,26 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://krona.example/schemas/module.schema.json",
"type": "object",
"additionalProperties": false,
"required": ["id", "name", "version", "min_platform_version", "dependencies", "permissions", "entities", "events_emitted", "events_consumed", "settings_schema", "configurable_entities", "ui_extensions"],
"properties": {
"$schema": { "type": "string" },
"id": { "const": "ru.krona.staff" },
"name": { "type": "string", "minLength": 1 },
"version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
"min_platform_version": { "type": "string" },
"dependencies": { "type": "array", "contains": { "const": "ru.krona.core" }, "items": { "type": "string" }, "uniqueItems": true },
"optional_dependencies": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"permissions": { "type": "array", "items": { "type": "string", "pattern": "^staff:[a-z_]+:(read|write)$" }, "uniqueItems": true },
"entities": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"events_emitted": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"events_consumed": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"settings_schema": { "type": "string" },
"configurable_entities": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"promote_targets": { "type": "array", "items": { "type": "object" } },
"api_prefix": { "const": "/api/v1/staff" },
"routes": { "type": "array", "items": { "type": "string" } },
"ui_extensions": { "type": "array", "items": { "type": "object", "required": ["slot", "component"], "properties": { "slot": { "type": "string" }, "component": { "type": "string" } }, "additionalProperties": false } }
}
}

View file

@ -0,0 +1,36 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["groups"],
"properties": {
"$schema": { "type": "string" },
"groups": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["key", "label", "fields"],
"properties": {
"key": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
"label": { "type": "string" },
"fields": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["key", "type", "label"],
"properties": {
"key": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
"type": { "enum": ["text", "number", "boolean", "date", "datetime", "money", "select", "multiselect", "relation", "email", "phone", "url", "json"] },
"label": { "type": "string" },
"required": { "type": "boolean" },
"description": { "type": "string" }
}
}
}
}
}
}
}
}

18
settings.schema.json Normal file
View file

@ -0,0 +1,18 @@
{
"$schema": "./schemas/settings.schema.schema.json",
"groups": [
{
"key": "localization",
"label": "Локализация",
"fields": [
{
"key": "business_timezone",
"type": "text",
"label": "Часовой пояс организации",
"required": true,
"description": "IANA timezone; timestamps are persisted in UTC and interpreted using this tenant setting."
}
]
}
]
}